├── .gitattributes ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── LICENSE ├── README.md ├── build.bat ├── convention.txt ├── gen.py ├── makefile ├── rjd.h ├── rjd_all.h ├── rjd_array.h ├── rjd_atomic.h ├── rjd_binrw.h ├── rjd_cmd.h ├── rjd_debug.h ├── rjd_dict.h ├── rjd_easing.h ├── rjd_enum.h ├── rjd_fio.h ├── rjd_geo.h ├── rjd_gfx.h ├── rjd_gfx_d3d11.h ├── rjd_gfx_metal.h ├── rjd_hash.h ├── rjd_input.h ├── rjd_math.h ├── rjd_mem.h ├── rjd_path.h ├── rjd_platform.h ├── rjd_procgeo.h ├── rjd_resource.h ├── rjd_resource_loader.h ├── rjd_resource_types.h ├── rjd_result.h ├── rjd_rng.h ├── rjd_slotmap.h ├── rjd_strbuf.h ├── rjd_stream.h ├── rjd_strhash.h ├── rjd_strpool.h ├── rjd_thread.h ├── rjd_timer.h ├── rjd_utf8.h ├── rjd_window.h ├── test_data ├── fio │ ├── readonly.txt │ └── writeable.txt └── resource │ ├── lib │ └── gfx │ │ ├── test.bmp │ │ ├── test.material │ │ └── test.shader │ └── loader_filesystem │ ├── bootstrap.lvl │ ├── gfx │ ├── invalid.bmp │ └── quad.shader │ ├── init.cfg │ └── levels │ ├── dungeon.lvl │ └── mainmenu.lvl ├── tests.c ├── tests_rjd.c ├── tests_rjd.m ├── tests_rjd_wrapped.h └── todo.txt /.gitattributes: -------------------------------------------------------------------------------- 1 | *.* text eol=lf -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build_osx: 11 | name: OSX Build & Test 12 | runs-on: macos-latest 13 | steps: 14 | - name: checkout 15 | uses: actions/checkout@v2 16 | - name: build 17 | run: make 18 | - name: run tests 19 | run: make test 20 | timeout-minutes: 1 21 | build_win32: 22 | name: Windows Build & Test 23 | runs-on: windows-latest 24 | steps: 25 | - name: checkout 26 | uses: actions/checkout@v2 27 | - name: build 28 | shell: cmd 29 | run: | 30 | call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat" 31 | ./build.bat 32 | - name: run tests 33 | run: .\tests.exe 34 | timeout-minutes: 1 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | xcode 2 | a.out 3 | *.exe 4 | *.ilk 5 | *.obj 6 | *.pdb 7 | *.stackdump 8 | .vs 9 | tags 10 | .DS_Store 11 | *.out* 12 | rjd.xcodeproj/ 13 | tests 14 | *.dSYM 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rjd 2 | Collection of cross-platform libraries you would need to make a game (or standalone C app). Adheres to the BYOE (Bring Your Own Engine) philosophy, where you can pick and choose which parts of rjd to enable for your project. 3 | -------------------------------------------------------------------------------- /build.bat: -------------------------------------------------------------------------------- 1 | @REM /Z7 old debug info format - don't use pdb 2 | @REM /Gv __vectorcall calling convention 3 | @REM /W4 warning level 4 4 | @REM /Oi use intrinsics 5 | @REM /D_CRT_SECURE_NO_WARNINGS disable msvc-specific "unsafe" function warninings 6 | @REM /DWINVER=_WIN32_WINNT_WIN10 /D_WIN32_WINNT=_WIN32_WINNT_WIN10 /DWDK_NTDDI_VERSION=NTDDI_WIN10_RS1 windows 10 SDK 7 | 8 | SET SOURCES=tests.c tests_rjd.c 9 | SET CFLAGS=/Z7 /Gv /W4 /Oi /std:c11 10 | SET DEFINES=/D_CRT_SECURE_NO_WARNINGS /DWINVER=_WIN32_WINNT_WIN10 /D_WIN32_WINNT=_WIN32_WINNT_WIN10 11 | SET LFLAGS=/MACHINE:X64 12 | SET LIBS=shell32.lib user32.lib dxgi.lib d3dcompiler.lib d3d11.lib dxguid.lib gdi32.lib 13 | 14 | cl %SOURCES% %CFLAGS% %DEFINES% /link %LFLAGS% %LIBS% /OUT:tests.exe 15 | -------------------------------------------------------------------------------- /convention.txt: -------------------------------------------------------------------------------- 1 | This doc is to help you remember the conventions of this codebase and to establish consistent naming across interfaces. 2 | 3 | struct / enum keywords 4 | * Avoid typedefing structs and enums. Just prefix the "tags" with the struct or enum keywords. 5 | 6 | rjd_result 7 | * The uniform interface for returning errors. Only static strings are allowed to be passed into RJD_RESULT() 8 | 9 | zero-initialization 10 | * Library code should always at least zero-initialize returned data unless explicitly requested not to 11 | 12 | create / destroy 13 | * Prefer using functions with these names to initialize struct pointers along with desc structs. For example: 14 | * rjd_foo_create(struct foo* foo, struct foo_desc desc) 15 | * rjd_foo_destroy(struct foo* foo) 16 | * Note that you don't have to specify a desc struct if no other parameters are needed. 17 | 18 | init / cleanup 19 | * Prefer using functions with these names to return fully-initialized structs by value and to clean them up. 20 | * Prefer to use the create/destroy naming convention unless the struct in question is trivial and doesn't use any external resources. 21 | 22 | Bad data should never crash these functions. Data is defined as "anything loaded from desk or another IO source outside the program, or defined in compile-time tables". 23 | 24 | Use asserts to check for programmer errors: 25 | * Incorrect API usage (e.g. call order, invalid struct inputs) 26 | * NULL pointers 27 | 28 | inlines are always declared static inline. 29 | static helpers are declared in the impl section, and implemented after the public interface implementation. 30 | -------------------------------------------------------------------------------- /gen.py: -------------------------------------------------------------------------------- 1 | includes = [ 2 | "", 3 | "", 4 | "", 5 | "", 6 | "", 7 | "", 8 | "", 9 | " // SSE2", 10 | " // SSE3", 11 | " // SSE4", 12 | ] 13 | 14 | includes_impl = [ 15 | "", 16 | "", 17 | ] 18 | 19 | files = [ 20 | "rjd_platform.h", 21 | "rjd_debug.h", 22 | "rjd_result.h", 23 | "rjd_enum.h", 24 | "rjd_hash.h", 25 | "rjd_atomic.h", 26 | "rjd_mem.h", 27 | "rjd_rng.h", 28 | "rjd_array.h", 29 | "rjd_math.h", 30 | "rjd_geo.h", 31 | "rjd_procgeo.h", 32 | "rjd_easing.h", 33 | "rjd_strbuf.h", 34 | "rjd_timer.h", 35 | "rjd_cmd.h", 36 | "rjd_dict.h", 37 | "rjd_fio.h", 38 | "rjd_thread.h", 39 | "rjd_strpool.h", 40 | "rjd_slotmap.h", 41 | "rjd_utf8.h", 42 | "rjd_path.h", 43 | "rjd_stream.h", 44 | "rjd_binrw.h", 45 | "rjd_strhash.h", 46 | "rjd_resource_types.h", 47 | "rjd_resource_loader.h", 48 | "rjd_resource.h", 49 | "rjd_window.h", 50 | "rjd_input.h", 51 | "rjd_gfx.h", 52 | "rjd_gfx_metal.h", 53 | "rjd_gfx_d3d11.h", 54 | ] 55 | 56 | concat = "#pragma once\n\n" 57 | 58 | concat += "////////////////////////////////////////////////////////////////////////////////\n" 59 | concat += "// autogenerated rjd.h\n" 60 | concat += "////////////////////////////////////////////////////////////////////////////////\n\n" 61 | for include in includes: 62 | concat += "#include " + include + "\n" 63 | 64 | concat += "\n#if RJD_IMPL\n" 65 | 66 | for include in includes_impl: 67 | concat += "\t#include " + include + "\n" 68 | 69 | concat += "#endif\n\n" 70 | 71 | for filename in files: 72 | with open(filename, "r") as file: 73 | data = file.read() 74 | concat += "////////////////////////////////////////////////////////////////////////////////\n" 75 | concat += "// " + filename + "\n" 76 | concat += "////////////////////////////////////////////////////////////////////////////////\n" 77 | concat += "\n" 78 | concat += data; 79 | concat += "\n" 80 | 81 | with open("rjd.h", "w", newline='\n') as file: 82 | file.write(concat) 83 | 84 | with open("rjd_all.h", "w") as file: 85 | allh = "#pragma once\n\n" 86 | for include in includes: 87 | allh += "#include " + include + "\n" 88 | for include in includes_impl: 89 | allh += "#include " + include + "\n" 90 | allh += "\n" 91 | for include in files: 92 | allh += "#include \"" + include + "\"\n" 93 | file.write(allh) 94 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | ifeq ($(OS), Windows_NT) 2 | PLATFORM_FILES := tests_rjd.c 3 | PLATFORM_CFLAGS := -D WINVER=_WIN32_WINNT_WIN10 -D _WIN32_WINNT=_WIN32_WINNT_WIN10 -D WDK_NTDDI_VERSION=NTDDI_WIN10_RS1 4 | PLATFORM_LFLAGS := -ld3d11 -ld3dcompiler -ldxgi -ldxguid 5 | OUTPUT_FILE := tests.exe 6 | else 7 | #SHELL_NAME := $(shell uname -s) 8 | #ifeq($(SHELL_NAME), Darwin) #OSX 9 | #endif 10 | #ifeq($(SHELL_NAME), Linux) #Linux 11 | #endif 12 | 13 | PLATFORM_FILES := tests_rjd.m 14 | PLATFORM_CFLAGS := -fsanitize=undefined -fsanitize=address 15 | PLATFORM_LFLAGS := -framework Foundation -framework AppKit -framework Metal -framework MetalKit 16 | OUTPUT_FILE := tests 17 | endif 18 | 19 | CFLAGS := --std=c11 -pedantic -Wall -Wextra -g -march=native -Wno-unused-local-typedefs -Wno-missing-braces 20 | 21 | all: 22 | @# -Wno-unused-local-typedefs to suppress locally defined typedefs coming from RJD_STATIC_ASSERT 23 | gcc $(CFLAGS) $(PLATFORM_CFLAGS) tests.c $(PLATFORM_FILES) -o $(OUTPUT_FILE) $(PLATFORM_LFLAGS) 24 | 25 | tags: 26 | ctags -f tags * 27 | 28 | clean: 29 | rm *.exe* 30 | rm *.ilk 31 | rm *.obj 32 | rm *.pdb 33 | rm -r Debug 34 | rm *.stackdump 35 | rm tests 36 | rm -r tests.dSYM 37 | 38 | test: 39 | ./$(OUTPUT_FILE) 40 | -------------------------------------------------------------------------------- /rjd_all.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include // SSE2 11 | #include // SSE3 12 | #include // SSE4 13 | #include 14 | #include 15 | 16 | #include "rjd_platform.h" 17 | #include "rjd_debug.h" 18 | #include "rjd_result.h" 19 | #include "rjd_enum.h" 20 | #include "rjd_hash.h" 21 | #include "rjd_atomic.h" 22 | #include "rjd_mem.h" 23 | #include "rjd_rng.h" 24 | #include "rjd_array.h" 25 | #include "rjd_math.h" 26 | #include "rjd_geo.h" 27 | #include "rjd_procgeo.h" 28 | #include "rjd_easing.h" 29 | #include "rjd_strbuf.h" 30 | #include "rjd_timer.h" 31 | #include "rjd_cmd.h" 32 | #include "rjd_dict.h" 33 | #include "rjd_fio.h" 34 | #include "rjd_thread.h" 35 | #include "rjd_strpool.h" 36 | #include "rjd_slotmap.h" 37 | #include "rjd_utf8.h" 38 | #include "rjd_path.h" 39 | #include "rjd_stream.h" 40 | #include "rjd_binrw.h" 41 | #include "rjd_strhash.h" 42 | #include "rjd_resource_types.h" 43 | #include "rjd_resource_loader.h" 44 | #include "rjd_resource.h" 45 | #include "rjd_window.h" 46 | #include "rjd_input.h" 47 | #include "rjd_gfx.h" 48 | #include "rjd_gfx_metal.h" 49 | #include "rjd_gfx_d3d11.h" 50 | -------------------------------------------------------------------------------- /rjd_array.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define RJD_ARRAY_H 1 4 | 5 | // static array count 6 | // Note that GCC is awesome and has a warning if buf is a pointer. See -Wsizeof-pointer-div 7 | #define rjd_countof(buf) (sizeof(buf) / sizeof(*(buf))) 8 | 9 | enum 10 | { 11 | RJD_ARRAY_DEFAULT_CAPACITY = 0, 12 | RJD_ARRAY_NOT_FOUND = -1, 13 | }; 14 | 15 | // dyanmic array 16 | #define rjd_array_alloc(type, capacity, allocator) ((type*)(rjd_array_alloc_impl((capacity), (allocator), sizeof(type)))) 17 | #define rjd_array_clone(buf, allocator) rjd_array_clone_impl((buf), allocator, sizeof(*(buf))) 18 | #define rjd_array_free(buf) rjd_array_free_impl(buf) 19 | #define rjd_array_capacity(buf) ((buf)?(const uint32_t)(*rjd_array_capacity_impl(buf)):0) 20 | #define rjd_array_count(buf) ((buf)?(const uint32_t)(*rjd_array_count_impl(buf)):0) 21 | #define rjd_array_clear(buf) (*rjd_array_count_impl(buf) = 0) 22 | #define rjd_array_reserve(buf, capacity) (buf = rjd_array_reserve_impl((buf), capacity, sizeof(*(buf)))) 23 | #define rjd_array_resize(buf, size) (buf = rjd_array_resize_impl((buf), size, sizeof(*(buf)))) 24 | #define rjd_array_trim(buf) (buf = rjd_array_trim_impl((buf), sizeof(*(buf)))) 25 | #define rjd_array_erase(buf, index) rjd_array_erase_impl((buf), index, sizeof(*(buf))) 26 | #define rjd_array_erase_unordered(buf, index) rjd_array_erase_unordered_impl((buf), index, sizeof(*(buf))) 27 | #define rjd_array_empty(buf) (rjd_array_count(buf) == 0) 28 | #define rjd_array_full(buf) (rjd_array_count(buf) == rjd_array_capacity(buf)) 29 | #define rjd_array_push(buf, value) (buf = rjd_array_push_impl((buf), \ 30 | sizeof(*(buf))), (buf)[rjd_array_count(buf) - 1] = value) 31 | #define rjd_array_pop(buf) (rjd_array_pop_impl(buf), \ 32 | --*rjd_array_count_impl(buf), \ 33 | *(buf + rjd_array_count(buf))) 34 | #define rjd_array_insert(buf, ptr, index) (buf = rjd_array_insert_impl((buf), (ptr), sizeof(*(buf)), index)) 35 | #define rjd_array_get(buf, index) (rjd_array_get_validate((buf), (index)), (buf + index)) 36 | #define rjd_array_first(buf) (rjd_array_get_validate((buf), 0), (buf)[0]) 37 | #define rjd_array_last(buf) (rjd_array_get_validate((buf), 0), (buf)[rjd_array_count(buf) - 1]) 38 | 39 | // searching/sorting 40 | typedef int32_t RJD_COMPILER_MSVC_ONLY(__cdecl) rjd_array_compare_func(const void* left, const void* right); 41 | typedef int32_t RJD_COMPILER_MSVC_ONLY(__cdecl) rjd_array_compare_c_func(void* context, const void* left, const void* right); 42 | 43 | #define rjd_array_find(buf, ptr) rjd_array_find_impl((buf), (ptr), sizeof(*(buf)), RJD_MUST_BE_SAME_TYPE_TEST((buf), (ptr))) 44 | #define rjd_array_contains(buf, ptr) rjd_array_contains_impl((buf), (ptr), sizeof(*(buf)), RJD_MUST_BE_SAME_TYPE_TEST((buf), (ptr))) 45 | 46 | // These functions sort or deal with sorted data 47 | #define rjd_array_sort(buf, comparer) \ 48 | rjd_array_sort_impl((buf), sizeof(*(buf)), comparer) 49 | #define rjd_array_lowerbound(buf, ptr, comparer) \ 50 | rjd_array_lowerbound_impl((buf), ptr, sizeof(*(buf)), comparer, RJD_MUST_BE_SAME_TYPE_TEST((buf), (ptr))) 51 | #define rjd_array_find_sorted(buf, ptr, comparer) \ 52 | rjd_array_find_sorted_impl((buf), (ptr), sizeof(*(buf)), comparer, RJD_MUST_BE_SAME_TYPE_TEST((buf), (ptr))) 53 | #define rjd_array_contains_sorted(buf, ptr, comparer) \ 54 | rjd_array_contains_sorted_impl((buf), (ptr), sizeof(*(buf)), comparer, RJD_MUST_BE_SAME_TYPE_TEST((buf), (ptr))) 55 | 56 | // These versions of the sort/find sorted functions allow you to provide a content parameter that 57 | // is passed into the compare func. 58 | #define rjd_array_sort_c(buf, comparer, context) \ 59 | rjd_array_sort_c_impl((buf), sizeof(*(buf)), comparer, context) 60 | #define rjd_array_lowerbound_c(buf, ptr, comparer, context) \ 61 | rjd_array_lowerbound_c_impl((buf), (ptr), sizeof(*(buf)), comparer, context, RJD_MUST_BE_SAME_TYPE_TEST((buf), (ptr))) 62 | #define rjd_array_find_sorted_c(buf, ptr, comparer, context) \ 63 | rjd_array_find_sorted_c_impl((buf), (ptr), sizeof(*(buf)), comparer, context, RJD_MUST_BE_SAME_TYPE_TEST((buf), (ptr))) 64 | #define rjd_array_contains_sorted_c(buf, ptr, comparer, context) \ 65 | rjd_array_contains_sorted_c_impl((buf), (ptr), sizeof(*(buf)), comparer, context, RJD_MUST_BE_SAME_TYPE_TEST((buf), (ptr))) 66 | 67 | // predicate helpers for the functional-style interface 68 | #define rjd_array_sum_predicate(acc, element) (acc + element) 69 | 70 | // functional-style helpers 71 | #define rjd_array_filter(buf, predicate, context) for(int _i = (int)rjd_array_count(buf) - 1; _i >= 0; --_i) { \ 72 | if (!(predicate((buf)[_i]))) { rjd_array_erase((buf), _i); } \ 73 | } 74 | #define rjd_array_map(in, out, predicate) rjd_array_resize((out), rjd_array_count(in)); \ 75 | for (size_t _i = 0; _i < rjd_array_count(in); ++_i) { \ 76 | out[_i] = predicate(in[_i]); \ 77 | } 78 | #define rjd_array_reduce(buf, acc, predicate) for(size_t _i = 0; _i < rjd_array_count(buf); ++_i) { \ 79 | (acc) = predicate(acc, ((buf)[_i])); \ 80 | } 81 | #define rjd_array_sum(buf, acc) for(size_t _i = 0; _i < rjd_array_count(buf); ++_i) { \ 82 | (acc) = rjd_array_sum_predicate((acc), ((buf)[_i])); \ 83 | } 84 | #define rjd_array_reverse(buf) rjd_array_reverse_impl(buf, sizeof(*buf)) 85 | 86 | // randomness helpers 87 | #define rjd_array_sample(buf, rng) ((buf)[rjd_rng_range32(rng, 0, rjd_array_count(buf))]) 88 | #define rjd_array_shuffle(buf, rng) rjd_array_shuffle_impl(buf, rng, sizeof(*buf)) 89 | 90 | struct rjd_mem_allocator; 91 | struct rjd_rng; 92 | 93 | void* rjd_array_alloc_impl(uint32_t capacity, struct rjd_mem_allocator* allocator, size_t sizeof_type); 94 | void* rjd_array_clone_impl(const void* array, struct rjd_mem_allocator* allocator, size_t sizeof_type); 95 | void rjd_array_free_impl(const void* array); 96 | uint32_t* rjd_array_capacity_impl(const void* array); 97 | uint32_t* rjd_array_count_impl(const void* array); 98 | void* rjd_array_reserve_impl(void* array, uint32_t newcapacity, size_t sizeof_type); 99 | void* rjd_array_resize_impl(void* array, uint32_t newcount, size_t sizeof_type); 100 | void* rjd_array_trim_impl(void* array, size_t sizeof_type); 101 | void rjd_array_erase_impl(void* array, uint32_t index, size_t sizeof_type); 102 | void rjd_array_erase_unordered_impl(void* array, uint32_t index, size_t sizeof_type); 103 | void* rjd_array_push_impl(void* array, size_t sizeof_type); 104 | void rjd_array_pop_impl(void* array); 105 | void* rjd_array_insert_impl(void* array, const void* insert, size_t sizeof_type, uint32_t index); 106 | void rjd_array_get_validate(void* array, uint32_t index); 107 | int32_t rjd_array_find_impl(const void* array, const void* search, size_t sizeof_type, int unused); 108 | bool rjd_array_contains_impl(const void* array, const void* search, size_t sizeof_type, int unused); 109 | void rjd_array_sort_impl(void* array, size_t sizeof_type, rjd_array_compare_func* comparer); 110 | int32_t rjd_array_lowerbound_impl(const void* array, const void* needle, size_t sizeof_type, rjd_array_compare_func* comparer, int unused); 111 | int32_t rjd_array_find_sorted_impl(const void* array, const void* needle, size_t sizeof_type, rjd_array_compare_func* comparer, int unused); 112 | bool rjd_array_contains_sorted_impl(const void* array, const void* needle, size_t sizeof_type, rjd_array_compare_func* comparer, int unused); 113 | void rjd_array_sort_c_impl(void* array, size_t sizeof_type, rjd_array_compare_c_func* comparer, void* context); 114 | int32_t rjd_array_lowerbound_c_impl(const void* array, const void* needle, size_t sizeof_type, rjd_array_compare_c_func* comparer, void* context, int unused); 115 | int32_t rjd_array_find_sorted_c_impl(const void* array, const void* needle, size_t sizeof_type, rjd_array_compare_c_func* comparer, void* context, int unused); 116 | bool rjd_array_contains_sorted_c_impl(const void* array, const void* needle, size_t sizeof_type, rjd_array_compare_c_func* comparer, void* context, int unused); 117 | void rjd_array_shuffle_impl(void* array, struct rjd_rng* rng, size_t sizeof_type); 118 | void rjd_array_reverse_impl(void* array, size_t sizeof_type); 119 | 120 | #if RJD_IMPL 121 | 122 | // Copied from stdlib.h. We want this extra platform functionality but don't want to turn on all the defines 123 | // to get us there. 124 | #if RJD_COMPILER_GCC 125 | void __bsd_qsort_r (void *__base, size_t __nmemb, size_t __size, void *__thunk, int (*_compar)(void *, const void *, const void *)); 126 | # define qsort_r __bsd_qsort_r 127 | #endif 128 | 129 | struct rjd_array_header 130 | { 131 | struct rjd_mem_allocator* allocator; 132 | uint32_t capacity; 133 | uint32_t count; 134 | uint32_t debug_sentinel; 135 | }; 136 | 137 | const uint32_t RJD_ARRAY_DEBUG_SENTINEL32 = 0xA7A7A7A7; 138 | 139 | static struct rjd_array_header* rjd_array_getheader(void* array); 140 | static struct rjd_mem_allocator* rjd_array_allocator(void* array); 141 | static inline void rjd_array_validate(const void* array); 142 | static void* rjd_array_grow(void* array, size_t sizeof_type); 143 | 144 | void* rjd_array_alloc_impl(uint32_t capacity, struct rjd_mem_allocator* allocator, size_t sizeof_type) 145 | { 146 | RJD_ASSERT(allocator); 147 | RJD_ASSERT(sizeof_type > 0); 148 | 149 | if (capacity == 0) { 150 | capacity = 128 / (uint32_t)sizeof_type; 151 | if (capacity < 4) { 152 | capacity = 4; 153 | } 154 | } 155 | 156 | size_t rawsize = sizeof(struct rjd_array_header) + (sizeof_type * capacity); 157 | char* raw = rjd_mem_alloc_array(char, rawsize, allocator); 158 | 159 | struct rjd_array_header* header = (struct rjd_array_header*)raw; 160 | header->allocator = allocator; 161 | header->capacity = capacity; 162 | header->count = 0; 163 | header->debug_sentinel = RJD_ARRAY_DEBUG_SENTINEL32; 164 | 165 | char* buf = raw + sizeof(struct rjd_array_header); 166 | return buf; 167 | } 168 | 169 | void* rjd_array_clone_impl(const void* array, struct rjd_mem_allocator* allocator, size_t sizeof_type) 170 | { 171 | uint32_t count = rjd_array_count(array); 172 | void* clone = rjd_array_alloc_impl(count, allocator, sizeof_type); 173 | clone = rjd_array_resize_impl(clone, count, sizeof_type); 174 | memcpy(clone, array, count * sizeof_type); 175 | return clone; 176 | } 177 | 178 | void rjd_array_free_impl(const void* array) 179 | { 180 | if (!array) { 181 | return; 182 | } 183 | rjd_array_validate(array); 184 | 185 | char* raw = (char*)array; 186 | rjd_mem_free(raw - sizeof(struct rjd_array_header)); 187 | } 188 | 189 | uint32_t* rjd_array_capacity_impl(const void* array) 190 | { 191 | RJD_ASSERT(array); 192 | 193 | rjd_array_validate(array); 194 | 195 | struct rjd_array_header* header = rjd_array_getheader((void*)array); 196 | return &header->capacity; 197 | } 198 | 199 | uint32_t* rjd_array_count_impl(const void* array) 200 | { 201 | RJD_ASSERT(array); 202 | 203 | rjd_array_validate(array); 204 | 205 | struct rjd_array_header* header = rjd_array_getheader((void*)array); 206 | return &header->count; 207 | } 208 | 209 | void* rjd_array_reserve_impl(void* array, uint32_t newcapacity, size_t sizeof_type) 210 | { 211 | RJD_ASSERT(array); 212 | RJD_ASSERT(sizeof_type > 0); 213 | 214 | rjd_array_validate(array); 215 | 216 | uint32_t oldcapacity = rjd_array_capacity(array); 217 | 218 | if (oldcapacity < newcapacity) { 219 | struct rjd_mem_allocator* allocator = rjd_array_allocator(array); 220 | char* newarray = rjd_array_alloc_impl(newcapacity, allocator, sizeof_type); 221 | 222 | uint32_t* count = rjd_array_count_impl(newarray); 223 | *count = rjd_array_count(array); 224 | 225 | memcpy(newarray, array, (*count * sizeof_type)); 226 | 227 | rjd_array_free(array); 228 | return newarray; 229 | } 230 | 231 | return array; 232 | } 233 | 234 | void* rjd_array_resize_impl(void* array, uint32_t newcount, size_t sizeof_type) 235 | { 236 | RJD_ASSERT(array); 237 | RJD_ASSERT(sizeof_type > 0); 238 | 239 | rjd_array_validate(array); 240 | 241 | array = rjd_array_reserve_impl(array, newcount, sizeof_type); 242 | uint32_t* count = rjd_array_count_impl(array); 243 | 244 | // zero new members 245 | if (newcount > *count) { 246 | memset((char*)array + (*count * sizeof_type), 0, (newcount - *count) * sizeof_type); 247 | } 248 | 249 | *count = newcount; 250 | return array; 251 | } 252 | 253 | void* rjd_array_trim_impl(void* array, size_t sizeof_type) 254 | { 255 | if (rjd_array_count(array) == rjd_array_capacity(array)) { 256 | return array; 257 | } 258 | 259 | void* newarray = rjd_array_alloc_impl(rjd_array_count(array), rjd_array_allocator(array), sizeof_type); 260 | memcpy(newarray, array, rjd_array_count(array) * sizeof_type); 261 | *rjd_array_count_impl(newarray) = rjd_array_count(array); 262 | 263 | rjd_array_free(array); 264 | 265 | return newarray; 266 | } 267 | 268 | void rjd_array_erase_impl(void* array, uint32_t index, size_t sizeof_type) 269 | { 270 | RJD_ASSERT(array); 271 | RJD_ASSERT(index < rjd_array_count(array)); 272 | RJD_ASSERT(sizeof_type > 0); 273 | 274 | rjd_array_validate(array); 275 | 276 | char* raw = array; 277 | size_t toshift = rjd_array_count(array) - index - 1; 278 | if (toshift > 0) { 279 | memmove(raw + index * sizeof_type, raw + (index + 1) * sizeof_type, toshift * sizeof_type); 280 | } 281 | --*rjd_array_count_impl(array); 282 | } 283 | 284 | void rjd_array_erase_unordered_impl(void* array, uint32_t index, size_t sizeof_type) 285 | { 286 | RJD_ASSERT(array); 287 | RJD_ASSERT(index < rjd_array_count(array)); 288 | RJD_ASSERT(sizeof_type > 0); 289 | 290 | rjd_array_validate(array); 291 | 292 | char* raw = array; 293 | 294 | uint32_t* count = rjd_array_count_impl(array); 295 | 296 | if (*count > 1) { 297 | char* erase = raw + index * sizeof_type; 298 | char* swap = raw + (*count - 1) * sizeof_type; 299 | memcpy(erase, swap, sizeof_type); 300 | } 301 | 302 | if (*count > 0) { 303 | --*rjd_array_count_impl(array); 304 | } 305 | } 306 | 307 | void* rjd_array_push_impl(void* array, size_t sizeof_type) 308 | { 309 | array = rjd_array_grow(array, sizeof_type); 310 | ++*rjd_array_count_impl(array); 311 | 312 | // skip init new element to 0 since it will be set in the push macro 313 | 314 | return array; 315 | } 316 | 317 | void rjd_array_pop_impl(void* array) 318 | { 319 | RJD_ASSERT(rjd_array_count(array) > 0); 320 | } 321 | 322 | void* rjd_array_insert_impl(void* array, const void* insert, size_t sizeof_type, uint32_t index) 323 | { 324 | array = rjd_array_grow(array, sizeof_type); 325 | uint32_t* count = rjd_array_count_impl(array); 326 | ++*count; 327 | 328 | RJD_ASSERT(*count > index); 329 | 330 | void* where_to_insert = (char*)array + sizeof_type * index; 331 | void* element_after_insert = (char*)where_to_insert + sizeof_type; 332 | memmove(element_after_insert, where_to_insert, sizeof_type * (*count - index)); 333 | memcpy(where_to_insert, insert, sizeof_type); 334 | 335 | return array; 336 | } 337 | 338 | void rjd_array_get_validate(void* array, uint32_t index) 339 | { 340 | rjd_array_validate(array); 341 | RJD_ASSERT(index < rjd_array_count(array)); 342 | } 343 | 344 | int32_t rjd_array_find_impl(const void* array, const void* search, size_t sizeof_type, int unused) 345 | { 346 | RJD_UNUSED_PARAM(unused); 347 | 348 | rjd_array_validate(array); 349 | 350 | if (!array) { 351 | return false; 352 | } 353 | 354 | char* raw = (char*)array; 355 | for (int32_t i = 0; i < (int32_t)rjd_array_count(array); ++i) { 356 | if (!memcmp(raw + i * sizeof_type, search, sizeof_type)) { 357 | return i; 358 | } 359 | } 360 | return RJD_ARRAY_NOT_FOUND; 361 | } 362 | 363 | bool rjd_array_contains_impl(const void* array, const void* search, size_t sizeof_type, int unused) 364 | { 365 | RJD_UNUSED_PARAM(unused); 366 | 367 | const int32_t index = rjd_array_find_impl(array, search, sizeof_type, 0); 368 | return index != RJD_ARRAY_NOT_FOUND; 369 | } 370 | 371 | void rjd_array_sort_impl(void* array, size_t sizeof_type, rjd_array_compare_func* comparer) 372 | { 373 | rjd_array_validate(array); 374 | size_t length = rjd_array_count(array); 375 | qsort(array, length, sizeof_type, comparer); 376 | } 377 | 378 | int32_t rjd_array_lowerbound_impl(const void* array, const void* needle, size_t sizeof_type, rjd_array_compare_func* comparer, int unused) 379 | { 380 | RJD_UNUSED_PARAM(unused); 381 | 382 | rjd_array_validate(array); 383 | 384 | uint32_t length = rjd_array_count(array); 385 | 386 | if (length == 0) { 387 | return 0; 388 | } 389 | 390 | int32_t first = 0; 391 | int32_t index = 0; 392 | 393 | while (length > 0) 394 | { 395 | int32_t step = length / 2; 396 | index = first + step; 397 | 398 | const void* value = (const char*)array + index * sizeof_type; 399 | const int32_t compared_value = comparer(value, needle); 400 | 401 | if (compared_value < 0) { 402 | ++index; 403 | first = index; 404 | length -= step + 1; 405 | } else if (compared_value > 0) { 406 | length = step; 407 | } else { 408 | break; 409 | } 410 | } 411 | return index; 412 | } 413 | 414 | int32_t rjd_array_find_sorted_impl(const void* array, const void* needle, size_t sizeof_type, rjd_array_compare_func* comparer, int unused) 415 | { 416 | RJD_UNUSED_PARAM(unused); 417 | 418 | const int32_t index = rjd_array_lowerbound_impl(array, needle, sizeof_type, comparer, 0); 419 | const void* value = (const char*)array + (index * sizeof_type); 420 | if (comparer(array, value) == 0) { 421 | return index; 422 | } 423 | return RJD_ARRAY_NOT_FOUND; 424 | } 425 | 426 | bool rjd_array_contains_sorted_impl(const void* array, const void* needle, size_t sizeof_type, rjd_array_compare_func* comparer, int unused) 427 | { 428 | RJD_UNUSED_PARAM(unused); 429 | 430 | const int32_t index = rjd_array_find_sorted_impl(array, needle, sizeof_type, comparer, 0); 431 | return index != RJD_ARRAY_NOT_FOUND; 432 | } 433 | 434 | void rjd_array_sort_c_impl(void* array, size_t sizeof_type, rjd_array_compare_c_func* comparer, void* context) 435 | { 436 | rjd_array_validate(array); 437 | const size_t length = rjd_array_count(array); 438 | #if RJD_COMPILER_MSVC 439 | qsort_s(array, length, sizeof_type, comparer, context); 440 | #else 441 | qsort_r(array, length, sizeof_type, context, comparer); 442 | #endif 443 | } 444 | 445 | int32_t rjd_array_lowerbound_c_impl(const void* array, const void* needle, size_t sizeof_type, rjd_array_compare_c_func* comparer, void* context, int unused) 446 | { 447 | RJD_UNUSED_PARAM(unused); 448 | 449 | rjd_array_validate(array); 450 | 451 | uint32_t length = rjd_array_count(array); 452 | 453 | if (length == 0) { 454 | return 0; 455 | } 456 | 457 | int32_t first = 0; 458 | int32_t index = 0; 459 | 460 | while (length > 0) 461 | { 462 | int32_t step = length / 2; 463 | index = first + step; 464 | 465 | const void* value = (const char*)array + index * sizeof_type; 466 | const int32_t compared_value = comparer(context, value, needle); 467 | 468 | if (compared_value < 0) { 469 | ++index; 470 | first = index; 471 | length -= step + 1; 472 | } else if (compared_value > 0) { 473 | length = step; 474 | } else { 475 | break; 476 | } 477 | } 478 | return index; 479 | } 480 | 481 | int32_t rjd_array_find_sorted_c_impl(const void* array, const void* needle, size_t sizeof_type, rjd_array_compare_c_func* comparer, void* context, int unused) 482 | { 483 | RJD_UNUSED_PARAM(unused); 484 | 485 | const int32_t index = rjd_array_lowerbound_c_impl(array, needle, sizeof_type, comparer, context, 0); 486 | const void* value = (const char*)array + (index * sizeof_type); 487 | if (comparer(context, array, value) == 0) { 488 | return index; 489 | } 490 | return RJD_ARRAY_NOT_FOUND; 491 | } 492 | 493 | bool rjd_array_contains_sorted_c_impl(const void* array, const void* needle, size_t sizeof_type, rjd_array_compare_c_func* comparer, void* context, int unused) 494 | { 495 | RJD_UNUSED_PARAM(unused); 496 | 497 | const int32_t index = rjd_array_find_sorted_c_impl(array, needle, sizeof_type, comparer, context, 0); 498 | return index != RJD_ARRAY_NOT_FOUND; 499 | } 500 | 501 | void rjd_array_shuffle_impl(void* array, struct rjd_rng* rng, size_t sizeof_type) 502 | { 503 | if (!array) { 504 | return; 505 | } 506 | 507 | rjd_array_validate(array); 508 | 509 | char tmp[512]; 510 | RJD_ASSERTMSG(sizeof_type <= sizeof(tmp), 511 | "tmp (%u bytes) must be greater than or equal to sizeof_type (%u bytes)", 512 | (unsigned) sizeof(tmp), (unsigned) sizeof_type); 513 | 514 | char* raw = (char*)array; 515 | for (uint32_t i = 0; i < rjd_array_count(array); ++i) { 516 | uint32_t k = rjd_rng_range32(rng, 0, rjd_array_count(array)); 517 | if (i == k) { 518 | continue; 519 | } 520 | 521 | char* a = raw + (i * sizeof_type); 522 | char* b = raw + (k * sizeof_type); 523 | 524 | memcpy(tmp, a, sizeof_type); 525 | memcpy(a, b, sizeof_type); 526 | memcpy(b, tmp, sizeof_type); 527 | } 528 | } 529 | 530 | void rjd_array_reverse_impl(void* array, size_t sizeof_type) 531 | { 532 | if (!array) { 533 | return; 534 | } 535 | rjd_array_validate(array); 536 | 537 | uint8_t* raw = array; 538 | for (uint8_t* begin = raw, *end = raw + (int32_t)sizeof_type * ((int32_t)rjd_array_count(array) - 1); 539 | begin < end; 540 | begin += sizeof_type, end -= sizeof_type) 541 | { 542 | rjd_mem_swap(begin, end, sizeof_type); 543 | } 544 | } 545 | 546 | //////////////////////////////////////////////////////////////////////////////// 547 | // local helpers 548 | 549 | static struct rjd_array_header* rjd_array_getheader(void* array) 550 | { 551 | if (!array) { 552 | return NULL; 553 | } 554 | rjd_array_validate(array); 555 | 556 | char* raw = array; 557 | char* raw_header = raw - sizeof(struct rjd_array_header); 558 | return (struct rjd_array_header*) raw_header; 559 | } 560 | 561 | static struct rjd_mem_allocator* rjd_array_allocator(void* array) 562 | { 563 | RJD_ASSERT(array); 564 | return rjd_array_getheader(array)->allocator; 565 | } 566 | 567 | static inline void rjd_array_validate(const void* array) 568 | { 569 | RJD_ASSERT(array); 570 | const char* raw = array; 571 | const struct rjd_array_header* header = (struct rjd_array_header*)(raw - sizeof(struct rjd_array_header)); 572 | RJD_ASSERTMSG(header->debug_sentinel == RJD_ARRAY_DEBUG_SENTINEL32, 573 | "Debug sentinel was either corrupted by an underrun or this is not an rjd_array."); 574 | RJD_UNUSED_PARAM(header); 575 | } 576 | 577 | static void* rjd_array_grow(void* array, size_t sizeof_type) 578 | { 579 | RJD_ASSERT(array); 580 | RJD_ASSERT(sizeof_type > 0); 581 | 582 | rjd_array_validate(array); 583 | 584 | uint32_t count = rjd_array_count(array); 585 | uint32_t capacity = rjd_array_capacity(array); 586 | if (count == capacity) { 587 | array = rjd_array_reserve_impl(array, capacity * 2, sizeof_type); 588 | } 589 | return array; 590 | } 591 | 592 | #endif //RJD_IMPL 593 | 594 | -------------------------------------------------------------------------------- /rjd_binrw.h: -------------------------------------------------------------------------------- 1 | #define RJD_BINRW_H 1 2 | 3 | struct rjd_istream; 4 | struct rjd_ostream; 5 | 6 | struct rjd_binrw_state 7 | { 8 | struct rjd_istream* istream; 9 | struct rjd_ostream* ostream; 10 | }; 11 | 12 | static inline struct rjd_result rjd_binrw_read_int8(struct rjd_istream* stream, int8_t* value); 13 | static inline struct rjd_result rjd_binrw_read_int16(struct rjd_istream* stream, int16_t* value); 14 | static inline struct rjd_result rjd_binrw_read_int32(struct rjd_istream* stream, int32_t* value); 15 | static inline struct rjd_result rjd_binrw_read_int64(struct rjd_istream* stream, int64_t* value); 16 | static inline struct rjd_result rjd_binrw_read_uint8(struct rjd_istream* stream, uint8_t* value); 17 | static inline struct rjd_result rjd_binrw_read_uint16(struct rjd_istream* stream, uint16_t* value); 18 | static inline struct rjd_result rjd_binrw_read_uint32(struct rjd_istream* stream, uint32_t* value); 19 | static inline struct rjd_result rjd_binrw_read_uint64(struct rjd_istream* stream, uint64_t* value); 20 | 21 | static inline struct rjd_result rjd_binrw_write_int8(struct rjd_ostream* stream, int8_t value); 22 | static inline struct rjd_result rjd_binrw_write_int16(struct rjd_ostream* stream, int16_t value); 23 | static inline struct rjd_result rjd_binrw_write_int32(struct rjd_ostream* stream, int32_t value); 24 | static inline struct rjd_result rjd_binrw_write_int64(struct rjd_ostream* stream, int64_t value); 25 | static inline struct rjd_result rjd_binrw_write_uint8(struct rjd_ostream* stream, uint8_t value); 26 | static inline struct rjd_result rjd_binrw_write_uint16(struct rjd_ostream* stream, uint16_t value); 27 | static inline struct rjd_result rjd_binrw_write_uint32(struct rjd_ostream* stream, uint32_t value); 28 | static inline struct rjd_result rjd_binrw_write_uint64(struct rjd_ostream* stream, uint64_t value); 29 | 30 | static inline struct rjd_result rjd_binrw_readwrite_int8(struct rjd_binrw_state* state, int8_t* value); 31 | static inline struct rjd_result rjd_binrw_readwrite_int16(struct rjd_binrw_state* state, int16_t* value); 32 | static inline struct rjd_result rjd_binrw_readwrite_int32(struct rjd_binrw_state* state, int32_t* value); 33 | static inline struct rjd_result rjd_binrw_readwrite_int64(struct rjd_binrw_state* state, int64_t* value); 34 | static inline struct rjd_result rjd_binrw_readwrite_uint8(struct rjd_binrw_state* state, uint8_t* value); 35 | static inline struct rjd_result rjd_binrw_readwrite_uint16(struct rjd_binrw_state* state, uint16_t* value); 36 | static inline struct rjd_result rjd_binrw_readwrite_uint32(struct rjd_binrw_state* state, uint32_t* value); 37 | static inline struct rjd_result rjd_binrw_readwrite_uint64(struct rjd_binrw_state* state, uint64_t* value); 38 | 39 | //////////////////////////////////////////////////////////////////////////////// 40 | // implementation 41 | 42 | #define RJD_BINRW_DEFINE_READ_IMPL(type, name) \ 43 | static inline struct rjd_result name(struct rjd_istream* stream, type* value) { \ 44 | RJD_ASSERT(stream && value); \ 45 | return rjd_istream_read(stream, value, sizeof(*value)); \ 46 | } 47 | 48 | #define RJD_BINRW_DEFINE_WRITE_IMPL(type, name) \ 49 | static inline struct rjd_result name(struct rjd_ostream* stream, type value) { \ 50 | RJD_ASSERT(stream); \ 51 | return rjd_ostream_write(stream, &value, sizeof(value)); \ 52 | } 53 | 54 | #define RJD_BINRW_DEFINE_READWRITE_IMPL(type, type2, name) \ 55 | static inline struct rjd_result name(struct rjd_binrw_state* state, type* value) { \ 56 | RJD_ASSERT(state && value && (state->istream || state->ostream)); \ 57 | if (state->istream) { \ 58 | return rjd_binrw_read_ ## type2(state->istream, value); \ 59 | } else { \ 60 | return rjd_binrw_write_ ## type2(state->ostream, *value); \ 61 | } \ 62 | } 63 | 64 | RJD_BINRW_DEFINE_READ_IMPL(int8_t, rjd_binrw_read_int8) 65 | RJD_BINRW_DEFINE_READ_IMPL(int16_t, rjd_binrw_read_int16) 66 | RJD_BINRW_DEFINE_READ_IMPL(int32_t, rjd_binrw_read_int32) 67 | RJD_BINRW_DEFINE_READ_IMPL(int64_t, rjd_binrw_read_int64) 68 | RJD_BINRW_DEFINE_READ_IMPL(uint8_t, rjd_binrw_read_uint8) 69 | RJD_BINRW_DEFINE_READ_IMPL(uint16_t, rjd_binrw_read_uint16) 70 | RJD_BINRW_DEFINE_READ_IMPL(uint32_t, rjd_binrw_read_uint32) 71 | RJD_BINRW_DEFINE_READ_IMPL(uint64_t, rjd_binrw_read_uint64) 72 | 73 | RJD_BINRW_DEFINE_WRITE_IMPL(int8_t, rjd_binrw_write_int8) 74 | RJD_BINRW_DEFINE_WRITE_IMPL(int16_t, rjd_binrw_write_int16) 75 | RJD_BINRW_DEFINE_WRITE_IMPL(int32_t, rjd_binrw_write_int32) 76 | RJD_BINRW_DEFINE_WRITE_IMPL(int64_t, rjd_binrw_write_int64) 77 | RJD_BINRW_DEFINE_WRITE_IMPL(uint8_t, rjd_binrw_write_uint8) 78 | RJD_BINRW_DEFINE_WRITE_IMPL(uint16_t, rjd_binrw_write_uint16) 79 | RJD_BINRW_DEFINE_WRITE_IMPL(uint32_t, rjd_binrw_write_uint32) 80 | RJD_BINRW_DEFINE_WRITE_IMPL(uint64_t, rjd_binrw_write_uint64) 81 | 82 | RJD_BINRW_DEFINE_READWRITE_IMPL(int8_t, int8, rjd_binrw_readwrite_int8) 83 | RJD_BINRW_DEFINE_READWRITE_IMPL(int16_t, int16, rjd_binrw_readwrite_int16) 84 | RJD_BINRW_DEFINE_READWRITE_IMPL(int32_t, int32, rjd_binrw_readwrite_int32) 85 | RJD_BINRW_DEFINE_READWRITE_IMPL(int64_t, int64, rjd_binrw_readwrite_int64) 86 | RJD_BINRW_DEFINE_READWRITE_IMPL(uint8_t, uint8, rjd_binrw_readwrite_uint8) 87 | RJD_BINRW_DEFINE_READWRITE_IMPL(uint16_t, uint16, rjd_binrw_readwrite_uint16) 88 | RJD_BINRW_DEFINE_READWRITE_IMPL(uint32_t, uint32, rjd_binrw_readwrite_uint32) 89 | RJD_BINRW_DEFINE_READWRITE_IMPL(uint64_t, uint64, rjd_binrw_readwrite_uint64) 90 | 91 | -------------------------------------------------------------------------------- /rjd_cmd.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define RJD_CMD_H 1 4 | 5 | struct rjd_mem_allocator; 6 | 7 | struct rjd_cmd_argv 8 | { 9 | const char* shortname; 10 | const char* longname; 11 | const char* argname; 12 | const char* description; 13 | }; 14 | 15 | struct rjd_cmd 16 | { 17 | int argc; 18 | const char** argv; 19 | 20 | struct rjd_cmd_argv* opts; 21 | struct rjd_cmd_argv* reqs; 22 | 23 | struct rjd_mem_allocator* allocator; 24 | }; 25 | 26 | struct rjd_cmd rjd_cmd_init(int argc, const char** argv, struct rjd_mem_allocator* allocator); 27 | void rjd_cmd_free(struct rjd_cmd* cmd); 28 | void rjd_cmd_add_opt(struct rjd_cmd* cmd, const char* shortname, const char* longname, const char* argname, const char* description); 29 | void rjd_cmd_add_req(struct rjd_cmd* cmd, const char* argname, const char* description); 30 | bool rjd_cmd_ok(const struct rjd_cmd* cmd); 31 | void rjd_cmd_usage(const struct rjd_cmd* cmd); 32 | void rjd_cmd_help(const struct rjd_cmd* cmd); 33 | 34 | int rjd_cmd_int(const struct rjd_cmd* cmd, const char* shortname, int _default); 35 | unsigned rjd_cmd_uint(const struct rjd_cmd* cmd, const char* shortname, unsigned _default); 36 | double rjd_cmd_float(const struct rjd_cmd* cmd, const char* shortname, double _default); 37 | bool rjd_cmd_bool(const struct rjd_cmd* cmd, const char* shortname); 38 | const char* rjd_cmd_str(const struct rjd_cmd* cmd, const char* shortname); 39 | 40 | #if RJD_IMPL 41 | 42 | struct rjd_cmd rjd_cmd_init(int argc, const char** argv, struct rjd_mem_allocator* allocator) 43 | { 44 | struct rjd_cmd cmd = {argc, argv, NULL, NULL, allocator}; 45 | cmd.opts = rjd_array_alloc(struct rjd_cmd_argv, 8, allocator); 46 | cmd.reqs = rjd_array_alloc(struct rjd_cmd_argv, 8, allocator); 47 | 48 | rjd_cmd_add_opt(&cmd, "-h", "--help", NULL, "Prints help"); 49 | return cmd; 50 | } 51 | 52 | void rjd_cmd_free(struct rjd_cmd* cmd) 53 | { 54 | rjd_array_free(cmd->opts); 55 | rjd_array_free(cmd->reqs); 56 | } 57 | 58 | void rjd_cmd_add_opt(struct rjd_cmd* cmd, const char* shortname, const char* longname, const char* argname, const char* description) 59 | { 60 | RJD_ASSERT(cmd); 61 | RJD_ASSERT(shortname); 62 | RJD_ASSERT(longname); 63 | RJD_ASSERT(description); 64 | 65 | struct rjd_cmd_argv opt = { shortname, longname, argname, description }; 66 | rjd_array_push(cmd->opts, opt); 67 | } 68 | 69 | void rjd_cmd_add_req(struct rjd_cmd* cmd, const char* argname, const char* description) 70 | { 71 | RJD_ASSERT(cmd); 72 | RJD_ASSERT(argname); 73 | RJD_ASSERT(description); 74 | 75 | struct rjd_cmd_argv req = { NULL, NULL, argname, description }; 76 | rjd_array_push(cmd->reqs, req); 77 | } 78 | 79 | static const struct rjd_cmd_argv* rjd_cmd_matchopt(const struct rjd_cmd* cmd, const char* argv); 80 | static int rjd_cmd_firstreq(const struct rjd_cmd* cmd); 81 | static const struct rjd_cmd_argv* rjd_cmd_getopt(const struct rjd_cmd* cmd, const char* shortname); 82 | static const char* rjd_cmd_findopt(const struct rjd_cmd* cmd, const char* shortname); 83 | static const char* rjd_cmd_findreq(const struct rjd_cmd* cmd, const char* argname); 84 | 85 | bool rjd_cmd_ok(const struct rjd_cmd* cmd) 86 | { 87 | RJD_ASSERT(cmd); 88 | 89 | int count = rjd_array_count(cmd->reqs); 90 | if (cmd->argc - 1 < count) { 91 | return false; 92 | } 93 | 94 | const int firstreq = rjd_cmd_firstreq(cmd); 95 | 96 | for (int i = 1; i < firstreq; ++i) { 97 | const struct rjd_cmd_argv* opt = rjd_cmd_matchopt(cmd, cmd->argv[i]); 98 | 99 | if (!opt) { 100 | return false; 101 | } 102 | 103 | if (opt->argname) { 104 | if (!strcmp(cmd->argv[i], opt->shortname)) { 105 | // since we're expecting an argument, this shouldn't match any other options 106 | if (rjd_cmd_matchopt(cmd, cmd->argv[i+1])) { 107 | return false; 108 | } 109 | } else { 110 | const char* eq = strstr(cmd->argv[i], "="); 111 | if (!eq) { 112 | return false; 113 | } 114 | const char* arg = eq + 1; 115 | if (*arg == 0) { 116 | return false; 117 | } 118 | } 119 | ++i; 120 | } 121 | } 122 | 123 | return (cmd->argc - 1 - firstreq) == (int) rjd_array_count(cmd->reqs); 124 | } 125 | 126 | void rjd_cmd_usage(const struct rjd_cmd* cmd) 127 | { 128 | // TODO rjd_stringbuilder 129 | 130 | size_t offset = 0; 131 | 132 | char optString[4096]; 133 | for (size_t i = 0; i < rjd_array_count(cmd->opts); ++i) { 134 | offset += snprintf(optString + offset, sizeof(optString) - offset, "%s", cmd->opts[i].shortname); 135 | if (i < rjd_array_count(cmd->opts) - 1) { 136 | offset += snprintf(optString + offset, sizeof(optString) - offset, " "); 137 | } 138 | } 139 | optString[offset] = 0; 140 | 141 | offset = 0; 142 | 143 | char reqString[4096]; 144 | for (size_t i = 0; i < rjd_array_count(cmd->reqs); ++i) { 145 | offset += snprintf(reqString + offset, sizeof(reqString) - offset, "%s", cmd->reqs[i].argname); 146 | if (i < rjd_array_count(cmd->opts) - 1) { 147 | offset += snprintf(reqString + offset, sizeof(reqString) - offset, " "); 148 | } 149 | } 150 | reqString[offset] = 0; 151 | 152 | RJD_LOG("Usage: %s [%s] %s", cmd->argv[0], optString, reqString); 153 | } 154 | 155 | void rjd_cmd_help(const struct rjd_cmd* cmd) 156 | { 157 | rjd_cmd_usage(cmd); 158 | 159 | for (size_t i = 0; i < rjd_array_count(cmd->reqs); ++i) { 160 | RJD_LOG("%s\n\t%s", cmd->reqs[i].argname, cmd->reqs[i].description); 161 | } 162 | 163 | for (size_t i = 0; i < rjd_array_count(cmd->opts); ++i) { 164 | const struct rjd_cmd_argv* arg = cmd->opts + i; 165 | if (arg->argname) { 166 | RJD_LOG("%s %s, %s=%s\n\t%s", arg->shortname, arg->argname, arg->longname, arg->argname, arg->description); 167 | } else { 168 | RJD_LOG("%s, %s\n\t%s", arg->shortname, arg->longname, arg->description); 169 | } 170 | } 171 | } 172 | 173 | int rjd_cmd_int(const struct rjd_cmd* cmd, const char* name, int _default) 174 | { 175 | return (int)rjd_cmd_float(cmd, name, _default); 176 | } 177 | 178 | unsigned rjd_cmd_uint(const struct rjd_cmd* cmd, const char* name, unsigned _default) 179 | { 180 | double v = rjd_cmd_float(cmd, name, _default); 181 | if (v < 0) { 182 | return _default; 183 | } 184 | return (unsigned)v; 185 | } 186 | 187 | double rjd_cmd_float(const struct rjd_cmd* cmd, const char* name, double _default) 188 | { 189 | const char* str = rjd_cmd_str(cmd, name); 190 | if (!str) { 191 | return _default; 192 | } 193 | 194 | char* end = NULL; 195 | double v = strtod(str, &end); 196 | if (v == 0 && end != NULL) { 197 | return _default; 198 | } 199 | return v; 200 | } 201 | 202 | bool rjd_cmd_bool(const struct rjd_cmd* cmd, const char* name) 203 | { 204 | const char* str = rjd_cmd_str(cmd, name); 205 | if (!str) { 206 | return false; 207 | } 208 | 209 | if (!strcmp(str, "true")) { 210 | return true; 211 | } else if (!strcmp(str, "false")) { 212 | return false; 213 | } 214 | 215 | const struct rjd_cmd_argv* opt = rjd_cmd_getopt(cmd, name); 216 | return opt && !strcmp(opt->shortname, name); 217 | } 218 | 219 | const char* rjd_cmd_str(const struct rjd_cmd* cmd, const char* name) 220 | { 221 | const char* opt = rjd_cmd_findopt(cmd, name); 222 | if (opt) { 223 | return opt; 224 | } 225 | 226 | const char* req = rjd_cmd_findreq(cmd, name); 227 | if (req) { 228 | return req; 229 | } 230 | 231 | return NULL; 232 | } 233 | 234 | static const struct rjd_cmd_argv* rjd_cmd_matchopt(const struct rjd_cmd* cmd, const char* argv) 235 | { 236 | if (!argv) { 237 | return NULL; 238 | } 239 | 240 | for (uint32_t i = 0; i < rjd_array_count(cmd->opts); ++i) { 241 | const char* shortname = cmd->opts[i].shortname; 242 | const char* longname = cmd->opts[i].longname; 243 | if (!strcmp(shortname, argv)) { 244 | return cmd->opts + i; 245 | } 246 | if (strstr(argv, longname) == argv) { 247 | return cmd->opts + i; 248 | } 249 | } 250 | 251 | return NULL; 252 | } 253 | 254 | static int rjd_cmd_firstreq(const struct rjd_cmd* cmd) 255 | { 256 | int index = 0; 257 | for (int i = 1; i < cmd->argc; ++i) { 258 | const struct rjd_cmd_argv* opt = rjd_cmd_matchopt(cmd, cmd->argv[i]); 259 | if (opt) { 260 | // skip the argument (assuming the format is ok) 261 | if (!strcmp(cmd->argv[i], opt->shortname) && opt->argname) { 262 | ++i; 263 | } 264 | index = i; 265 | } else { 266 | break; 267 | } 268 | } 269 | 270 | return index + 1; 271 | } 272 | 273 | static const struct rjd_cmd_argv* rjd_cmd_getopt(const struct rjd_cmd* cmd, const char* shortname) 274 | { 275 | for (uint32_t i = 0; i < rjd_array_count(cmd->opts); ++i) { 276 | if (!strcmp(cmd->opts[i].shortname, shortname)) { 277 | return cmd->opts + i; 278 | } 279 | } 280 | return NULL; 281 | } 282 | 283 | static const char* rjd_cmd_findopt(const struct rjd_cmd* cmd, const char* shortname) 284 | { 285 | for (int i = 0; i < cmd->argc; ++i) { 286 | const struct rjd_cmd_argv* opt = rjd_cmd_matchopt(cmd, cmd->argv[i]); 287 | if (opt && !strcmp(opt->shortname, shortname)) { 288 | if (!opt->argname) { 289 | return cmd->argv[i]; 290 | } 291 | if (!strcmp(cmd->argv[i], opt->shortname)) { 292 | return cmd->argv[i + 1]; 293 | } 294 | const char* eq = strstr(cmd->argv[i], "="); 295 | if (eq) { 296 | return eq + 1; 297 | } 298 | break; 299 | } 300 | } 301 | return NULL; 302 | } 303 | 304 | static const char* rjd_cmd_findreq(const struct rjd_cmd* cmd, const char* argname) 305 | { 306 | int reqindex = -1; 307 | for (int i = 0; i < (int)rjd_array_count(cmd->reqs); ++i) { 308 | if (!strcmp(cmd->reqs[i].argname, argname)) { 309 | reqindex = i; 310 | break; 311 | } 312 | } 313 | 314 | if (reqindex == -1) { 315 | return NULL; 316 | } 317 | 318 | int optindex = rjd_cmd_firstreq(cmd) - 1; // -1 to get to first opt index 319 | 320 | int argvindex = optindex + reqindex + 1; // +1 to skip exe arg 321 | RJD_ASSERT(argvindex < cmd->argc); 322 | 323 | return cmd->argv[argvindex]; 324 | } 325 | 326 | #endif 327 | 328 | -------------------------------------------------------------------------------- /rjd_debug.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define RJD_DEBUG_H 1 4 | 5 | enum rjd_log_verbosity 6 | { 7 | RJD_LOG_VERBOSITY_LOW, 8 | RJD_LOG_VERBOSITY_MED, 9 | RJD_LOG_VERBOSITY_HIGH, 10 | }; 11 | 12 | struct rjd_logchannel 13 | { 14 | enum rjd_log_verbosity verbosity; 15 | bool enabled; 16 | const char* name; 17 | void (*hook)(const char* formatted, size_t length); 18 | }; 19 | 20 | #if RJD_ENABLE_LOGGING 21 | #define RJD_LOG_CHANNEL(channel, verbosity, ...) rjd_log_impl(__FILE__, __LINE__, channel, verbosity, __VA_ARGS__) 22 | #define RJD_LOG(...) RJD_LOG_CHANNEL(g_rjd_global_logchannel, RJD_LOG_VERBOSITY_MED, __VA_ARGS__) 23 | #else 24 | #define RJD_LOG_CHANNEL(channel, ...) 25 | #define RJD_LOG(...) 26 | #endif 27 | 28 | 29 | #define RJD_NAMEGEN2(a, b) a##b 30 | #define RJD_NAMEGEN(a, b) RJD_NAMEGEN2(a, b) 31 | 32 | #if RJD_COMPILER_MSVC 33 | #define RJD_STATIC_ASSERTMSG(condition, message) typedef int RJD_NAMEGEN(rjd_staticassert_fail, __COUNTER__)[(condition) ? 1 : -1] 34 | #else 35 | #define RJD_STATIC_ASSERTMSG(condition, message) _Static_assert(condition, message) 36 | #endif 37 | #define RJD_STATIC_ASSERT(condition) RJD_STATIC_ASSERTMSG(condition, #condition) 38 | 39 | #if RJD_COMPILER_CLANG || RJD_COMPILER_GCC 40 | #define RJD_SAME_TYPE_TEST(a, b) (__builtin_types_compatible_p(__typeof__(a), __typeof__(b))) 41 | #else 42 | #define RJD_SAME_TYPE_TEST(a, b) (1) 43 | #endif 44 | #define RJD_STATIC_TEST(a) (sizeof(int[(a)?1:-1]) * 0) 45 | #define RJD_MUST_BE_SAME_TYPE_TEST(a,b) RJD_STATIC_TEST(RJD_SAME_TYPE_TEST(a,b)) 46 | 47 | #if RJD_COMPILER_MSVC 48 | #define RJD_TRAP() __debugbreak() 49 | #elif RJD_COMPILER_GCC || RJD_COMPILER_CLANG 50 | #define RJD_TRAP() __builtin_trap() 51 | #endif 52 | 53 | #if RJD_ENABLE_ASSERT 54 | #define RJD_ASSERT(condition) RJD_ASSERTMSG(condition, #condition) 55 | #define RJD_ASSERTMSG(condition, ...) if (!(condition)) { RJD_LOG(__VA_ARGS__); RJD_TRAP(); } 56 | #define RJD_ASSERTFAIL(...) { RJD_LOG(__VA_ARGS__); RJD_TRAP(); } 57 | #else 58 | #define RJD_ASSERT(condition, ...) 59 | #define RJD_ASSERTMSG(condition, ...) 60 | #define RJD_ASSERTFAIL(...) 61 | #endif 62 | 63 | #define RJD_UNUSED_PARAM(param) ((void)(param)) 64 | 65 | void rjd_log_impl(const char* file, unsigned line, const struct rjd_logchannel* channel, enum rjd_log_verbosity verbosity, const char* format, ...); 66 | void rjd_log_resetglobal(void); 67 | 68 | extern const struct rjd_logchannel* g_rjd_global_logchannel; 69 | 70 | #if RJD_IMPL 71 | 72 | const struct rjd_logchannel rjd_global_logchannel = { 73 | .verbosity = RJD_LOG_VERBOSITY_MED, 74 | .enabled = true, 75 | .name = "Default Global", 76 | }; 77 | const struct rjd_logchannel* g_rjd_global_logchannel = &rjd_global_logchannel; 78 | 79 | void rjd_log_impl(const char* file, unsigned line, const struct rjd_logchannel* channel, enum rjd_log_verbosity verbosity, const char* format, ...) 80 | { 81 | if (!channel || !channel->enabled) { 82 | return; 83 | } 84 | 85 | if (verbosity > channel->verbosity) { 86 | return; 87 | } 88 | 89 | va_list args; 90 | va_start(args, format); 91 | 92 | char rawMessage[4096]; 93 | 94 | int written = vsnprintf(rawMessage, sizeof(rawMessage), format, args); 95 | va_end(args); 96 | 97 | if (written < 0) 98 | { 99 | printf("Failed to format message.\n"); 100 | RJD_TRAP(); 101 | } 102 | 103 | static const char* formattedLog = "%s(%u): %s\n"; 104 | static const uint32_t logLength = sizeof("%s(%u): %s\n"); 105 | 106 | char formatted[4096]; 107 | 108 | if (sizeof(formatted) <= logLength + written) 109 | { 110 | printf("Static buffer not large enough.\n"); 111 | RJD_TRAP(); 112 | } 113 | 114 | const int size = sprintf(formatted, formattedLog, file, line, rawMessage); 115 | 116 | if (channel->hook) { 117 | channel->hook(formatted, size); 118 | } else { 119 | RJD_COMPILER_MSVC_ONLY(OutputDebugString(formatted)); 120 | fwrite(formatted, 1, size, stdout); 121 | fflush(stdout); 122 | } 123 | } 124 | 125 | void rjd_log_resetglobal() 126 | { 127 | g_rjd_global_logchannel = &rjd_global_logchannel; 128 | } 129 | 130 | #endif // RJD_IMPL 131 | 132 | -------------------------------------------------------------------------------- /rjd_dict.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define RJD_DICT_H 1 4 | 5 | struct rjd_mem_allocator; 6 | 7 | struct rjd_dict 8 | { 9 | uint32_t count; 10 | struct rjd_hash64* hashes; 11 | void** values; 12 | struct rjd_mem_allocator* allocator; 13 | }; 14 | 15 | struct rjd_dict rjd_dict_init(struct rjd_mem_allocator* allocator, size_t initial_capacity); 16 | void rjd_dict_insert(struct rjd_dict* dict, struct rjd_hash64 hash, void* item); 17 | void* rjd_dict_erase(struct rjd_dict* dict, struct rjd_hash64 hash); 18 | void* rjd_dict_get(const struct rjd_dict* dict, struct rjd_hash64 hash); 19 | bool rjd_dict_has(const struct rjd_dict* dict, struct rjd_hash64 hash); 20 | void rjd_dict_free(struct rjd_dict* dict); 21 | 22 | static inline void rjd_dict_insert_hashstr(struct rjd_dict* dict, const char* key, void* item); 23 | static inline void* rjd_dict_erase_hashstr(struct rjd_dict* dict, const char* key); 24 | static inline void* rjd_dict_get_hashstr(const struct rjd_dict* dict, const char* key); 25 | static inline bool rjd_dict_has_hashstr(const struct rjd_dict* dict, const char* key); 26 | 27 | static inline void rjd_dict_insert_hashstr(struct rjd_dict* dict, const char* key, void* item) 28 | { 29 | rjd_dict_insert(dict, rjd_hash64_data((uint8_t*)key, -1), item); 30 | } 31 | 32 | static inline void* rjd_dict_erase_hashstr(struct rjd_dict* dict, const char* key) 33 | { 34 | return rjd_dict_erase(dict, rjd_hash64_data((uint8_t*)key, -1)); 35 | } 36 | 37 | static inline void* rjd_dict_get_hashstr(const struct rjd_dict* dict, const char* key) 38 | { 39 | return rjd_dict_get(dict, rjd_hash64_data((uint8_t*)key, -1)); 40 | } 41 | 42 | static inline bool rjd_dict_has_hashstr(const struct rjd_dict* dict, const char* key) 43 | { 44 | return rjd_dict_has(dict, rjd_hash64_data((uint8_t*)key, -1)); 45 | } 46 | 47 | #if RJD_IMPL 48 | 49 | enum rjd_dict_findmode 50 | { 51 | RJD_DICT_FINDMODE_INSERTION, 52 | RJD_DICT_FINDMODE_EXISTING, 53 | }; 54 | 55 | static void rjd_dict_grow(struct rjd_dict* dict, size_t capacity); 56 | static int32_t rjd_dict_findindex(const struct rjd_hash64* hashes, struct rjd_hash64 hash, enum rjd_dict_findmode mode); 57 | 58 | struct rjd_dict rjd_dict_init(struct rjd_mem_allocator* allocator, size_t initial_capacity) 59 | { 60 | RJD_ASSERT(allocator); 61 | 62 | struct rjd_dict dict = { 0, NULL, NULL, allocator }; 63 | 64 | if (initial_capacity > 0) { 65 | rjd_dict_grow(&dict, initial_capacity); 66 | } 67 | 68 | return dict; 69 | } 70 | 71 | void rjd_dict_insert(struct rjd_dict* dict, struct rjd_hash64 hash, void* value) 72 | { 73 | RJD_ASSERT(dict); 74 | RJD_ASSERT(rjd_hash64_valid(hash)); 75 | 76 | const float load = dict->hashes ? (dict->count + 1) / (float)rjd_array_capacity(dict->hashes) : 1; 77 | if (load > 0.6) { 78 | uint32_t capacity = dict->hashes ? rjd_array_capacity(dict->hashes) * 2 : 32; 79 | rjd_dict_grow(dict, capacity); 80 | } 81 | 82 | int32_t i = rjd_dict_findindex(dict->hashes, hash, RJD_DICT_FINDMODE_INSERTION); 83 | RJD_ASSERT(i >= 0); 84 | RJD_ASSERT(!rjd_hash64_valid(dict->hashes[i])); 85 | 86 | dict->hashes[i] = hash; 87 | dict->values[i] = value; 88 | ++dict->count; 89 | } 90 | 91 | void* rjd_dict_erase(struct rjd_dict* dict, struct rjd_hash64 hash) 92 | { 93 | RJD_ASSERT(dict); 94 | 95 | int32_t index = rjd_dict_findindex(dict->hashes, hash, RJD_DICT_FINDMODE_EXISTING); 96 | if (index < 0) { 97 | return NULL; 98 | } 99 | 100 | void* v = dict->values[index]; 101 | 102 | dict->hashes[index] = rjd_hash64_data(NULL, 0); 103 | dict->values[index] = NULL; 104 | --dict->count; 105 | 106 | return v; 107 | } 108 | 109 | void* rjd_dict_get(const struct rjd_dict* dict, struct rjd_hash64 hash) 110 | { 111 | RJD_ASSERT(dict); 112 | 113 | int32_t index = rjd_dict_findindex(dict->hashes, hash, RJD_DICT_FINDMODE_EXISTING); 114 | if (index < 0) { 115 | return NULL; 116 | } 117 | 118 | return dict->values[index]; 119 | } 120 | 121 | bool rjd_dict_has(const struct rjd_dict* dict, struct rjd_hash64 hash) 122 | { 123 | RJD_ASSERT(dict); 124 | 125 | if (!rjd_hash64_valid(hash)) { 126 | return false; 127 | } 128 | 129 | int32_t index = rjd_dict_findindex(dict->hashes, hash, RJD_DICT_FINDMODE_EXISTING); 130 | if (index >= 0) { 131 | return rjd_hash64_valid(dict->hashes[index]); 132 | } 133 | 134 | return false; 135 | } 136 | 137 | void rjd_dict_free(struct rjd_dict* dict) 138 | { 139 | RJD_ASSERT(dict); 140 | 141 | rjd_array_free(dict->hashes); 142 | rjd_array_free(dict->values); 143 | dict->hashes = NULL; 144 | dict->values = NULL; 145 | dict->count = 0; 146 | } 147 | 148 | static void rjd_dict_grow(struct rjd_dict* dict, size_t capacity) 149 | { 150 | RJD_ASSERT(capacity > 0); 151 | RJD_ASSERT(dict); 152 | 153 | struct rjd_hash64* hashes = rjd_array_alloc(struct rjd_hash64, (uint32_t)capacity, dict->allocator); 154 | void** values = rjd_array_alloc(void*, (uint32_t)capacity, dict->allocator); 155 | 156 | rjd_array_resize(hashes, (uint32_t)capacity); 157 | rjd_array_resize(values, (uint32_t)capacity); 158 | 159 | for (uint32_t i = 0; i < rjd_array_count(dict->hashes); ++i) { 160 | if (rjd_hash64_valid(dict->hashes[i])) { 161 | int32_t k = rjd_dict_findindex(hashes, dict->hashes[i], RJD_DICT_FINDMODE_INSERTION); 162 | RJD_ASSERT(k >= 0); 163 | hashes[k] = dict->hashes[i]; 164 | values[k] = dict->values[i]; 165 | } 166 | } 167 | 168 | rjd_array_free(dict->hashes); 169 | rjd_array_free(dict->values); 170 | 171 | dict->hashes = hashes; 172 | dict->values = values; 173 | } 174 | 175 | static int32_t rjd_dict_findindex(const struct rjd_hash64* hashes, struct rjd_hash64 hash, enum rjd_dict_findmode mode) 176 | { 177 | if (!hashes) { 178 | return -1; 179 | } 180 | 181 | const uint32_t capacity = rjd_array_capacity(hashes); 182 | const uint32_t start = hash.value % capacity; 183 | uint32_t i = start; 184 | do { 185 | if (mode == RJD_DICT_FINDMODE_INSERTION && !rjd_hash64_valid(hashes[i])) { 186 | return (int32_t)i; 187 | } else if (mode == RJD_DICT_FINDMODE_EXISTING && hashes[i].value == hash.value) { 188 | return (int32_t)i; 189 | } 190 | 191 | i = (i + 1) % capacity; 192 | } while (i != start); 193 | 194 | return -1; 195 | } 196 | 197 | #endif 198 | 199 | -------------------------------------------------------------------------------- /rjd_easing.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define RJD_EASING_H 1 4 | 5 | enum rjd_ease_type 6 | { 7 | RJD_EASE_TYPE_LINE, // linear 8 | RJD_EASE_TYPE_SINE, // sine 9 | RJD_EASE_TYPE_CUBE, // cubic 10 | RJD_EASE_TYPE_QUAD, // quadratic 11 | RJD_EASE_TYPE_QUAR, // quartic 12 | RJD_EASE_TYPE_QUIN, // quintic 13 | RJD_EASE_TYPE_EXPO, // exponential 14 | RJD_EASE_TYPE_CIRC, // circular 15 | RJD_EASE_TYPE_BACK, // back 16 | RJD_EASE_TYPE_ELAS, // elastic 17 | RJD_EASE_TYPE_BOUN, // bounce 18 | RJD_EASE_TYPE_MAX, 19 | }; 20 | 21 | enum rjd_ease_dir 22 | { 23 | RJD_EASE_DIR_INOUT, 24 | RJD_EASE_DIR_IN, 25 | RJD_EASE_DIR_OUT, 26 | RJD_EASE_DIR_MAX, 27 | }; 28 | 29 | typedef float (rjd_ease_func)(float t); 30 | 31 | static inline float rjd_ease(float t, enum rjd_ease_type type, enum rjd_ease_dir dir); 32 | static inline float rjd_ease_between(float t, float min, float max, rjd_ease_func* f); 33 | static inline float rjd_ease_line(float t); 34 | static inline float rjd_ease_in_sine(float t); 35 | static inline float rjd_ease_in_quad(float t); 36 | static inline float rjd_ease_in_cube(float t); 37 | static inline float rjd_ease_in_quar(float t); 38 | static inline float rjd_ease_in_quin(float t); 39 | static inline float rjd_ease_in_expo(float t); 40 | static inline float rjd_ease_in_circ(float t); 41 | static inline float rjd_ease_in_back(float t); 42 | static inline float rjd_ease_in_elas(float t); 43 | static inline float rjd_ease_in_boun(float t); 44 | static inline float rjd_ease_out_sine(float t); 45 | static inline float rjd_ease_out_quad(float t); 46 | static inline float rjd_ease_out_cube(float t); 47 | static inline float rjd_ease_out_quar(float t); 48 | static inline float rjd_ease_out_quin(float t); 49 | static inline float rjd_ease_out_expo(float t); 50 | static inline float rjd_ease_out_circ(float t); 51 | static inline float rjd_ease_out_back(float t); 52 | static inline float rjd_ease_out_elas(float t); 53 | static inline float rjd_ease_out_boun(float t); 54 | static inline float rjd_ease_inout_sine(float t); 55 | static inline float rjd_ease_inout_quad(float t); 56 | static inline float rjd_ease_inout_cube(float t); 57 | static inline float rjd_ease_inout_quar(float t); 58 | static inline float rjd_ease_inout_quin(float t); 59 | static inline float rjd_ease_inout_expo(float t); 60 | static inline float rjd_ease_inout_circ(float t); 61 | static inline float rjd_ease_inout_back(float t); 62 | static inline float rjd_ease_inout_elas(float t); 63 | static inline float rjd_ease_inout_boun(float t); 64 | 65 | // impl 66 | 67 | static inline float rjd_ease(float t, enum rjd_ease_type type, enum rjd_ease_dir dir) 68 | { 69 | switch (dir) { 70 | case RJD_EASE_DIR_IN: { 71 | switch(type) { 72 | case RJD_EASE_TYPE_LINE: return rjd_ease_line(t); 73 | case RJD_EASE_TYPE_SINE: return rjd_ease_in_sine(t); 74 | case RJD_EASE_TYPE_CUBE: return rjd_ease_in_cube(t); 75 | case RJD_EASE_TYPE_QUAD: return rjd_ease_in_quad(t); 76 | case RJD_EASE_TYPE_QUAR: return rjd_ease_in_quar(t); 77 | case RJD_EASE_TYPE_QUIN: return rjd_ease_in_quin(t); 78 | case RJD_EASE_TYPE_EXPO: return rjd_ease_in_expo(t); 79 | case RJD_EASE_TYPE_CIRC: return rjd_ease_in_circ(t); 80 | case RJD_EASE_TYPE_BACK: return rjd_ease_in_back(t); 81 | case RJD_EASE_TYPE_ELAS: return rjd_ease_in_elas(t); 82 | case RJD_EASE_TYPE_BOUN: return rjd_ease_in_boun(t); 83 | case RJD_EASE_TYPE_MAX: break; 84 | } 85 | } 86 | 87 | case RJD_EASE_DIR_OUT: { 88 | switch(type) { 89 | case RJD_EASE_TYPE_LINE: return rjd_ease_line(t); 90 | case RJD_EASE_TYPE_SINE: return rjd_ease_out_sine(t); 91 | case RJD_EASE_TYPE_CUBE: return rjd_ease_out_cube(t); 92 | case RJD_EASE_TYPE_QUAD: return rjd_ease_out_quad(t); 93 | case RJD_EASE_TYPE_QUAR: return rjd_ease_out_quar(t); 94 | case RJD_EASE_TYPE_QUIN: return rjd_ease_out_quin(t); 95 | case RJD_EASE_TYPE_EXPO: return rjd_ease_out_expo(t); 96 | case RJD_EASE_TYPE_CIRC: return rjd_ease_out_circ(t); 97 | case RJD_EASE_TYPE_BACK: return rjd_ease_out_back(t); 98 | case RJD_EASE_TYPE_ELAS: return rjd_ease_out_elas(t); 99 | case RJD_EASE_TYPE_BOUN: return rjd_ease_out_boun(t); 100 | case RJD_EASE_TYPE_MAX: break; 101 | } 102 | } 103 | 104 | case RJD_EASE_DIR_INOUT: { 105 | switch(type) { 106 | case RJD_EASE_TYPE_LINE: return rjd_ease_line(t); 107 | case RJD_EASE_TYPE_SINE: return rjd_ease_inout_sine(t); 108 | case RJD_EASE_TYPE_CUBE: return rjd_ease_inout_cube(t); 109 | case RJD_EASE_TYPE_QUAD: return rjd_ease_inout_quad(t); 110 | case RJD_EASE_TYPE_QUAR: return rjd_ease_inout_quar(t); 111 | case RJD_EASE_TYPE_QUIN: return rjd_ease_inout_quin(t); 112 | case RJD_EASE_TYPE_EXPO: return rjd_ease_inout_expo(t); 113 | case RJD_EASE_TYPE_CIRC: return rjd_ease_inout_circ(t); 114 | case RJD_EASE_TYPE_BACK: return rjd_ease_inout_back(t); 115 | case RJD_EASE_TYPE_ELAS: return rjd_ease_inout_elas(t); 116 | case RJD_EASE_TYPE_BOUN: return rjd_ease_inout_boun(t); 117 | case RJD_EASE_TYPE_MAX: break; 118 | } 119 | } 120 | 121 | default: break; 122 | } 123 | 124 | RJD_ASSERTFAIL("type (%d) or dir (%d) was invalid.\n", type, dir); 125 | return 0; 126 | } 127 | 128 | static inline float rjd_ease_between(float t, float min, float max, rjd_ease_func* f) { 129 | return f(t) * (max - min) + min; 130 | } 131 | 132 | static inline float rjd_ease_line(float t) { 133 | return t; 134 | } 135 | 136 | static inline float rjd_ease_in_sine(float t) { 137 | return sinf((t - 1) * RJD_MATH_PI/2.0f) + 1; 138 | } 139 | 140 | static inline float rjd_ease_in_quad(float t) { 141 | return t*t; 142 | } 143 | 144 | static inline float rjd_ease_in_cube(float t) { 145 | return t*t*t; 146 | } 147 | 148 | static inline float rjd_ease_in_quar(float t) { 149 | return t*t*t*t; 150 | } 151 | 152 | static inline float rjd_ease_in_quin(float t) { 153 | return t*t*t*t*t; 154 | } 155 | 156 | static inline float rjd_ease_in_expo(float t) { 157 | return t == 0 ? t : powf(2, 10*(t - 1)); 158 | } 159 | 160 | static inline float rjd_ease_in_circ(float t) { 161 | return -sqrtf(1-t*t) + 1; 162 | } 163 | 164 | static inline float rjd_ease_in_back(float t) { 165 | return t * t * t - t * sinf(t * RJD_MATH_PI); 166 | } 167 | 168 | static inline float rjd_ease_in_elas(float t) { 169 | return sinf(13.0f * RJD_MATH_PI / 2.0f * t) * powf(2, 10 * (t - 1)); 170 | } 171 | 172 | static inline float rjd_ease_in_boun(float t) { 173 | return 1 - rjd_ease_out_boun(1 - t); 174 | } 175 | 176 | static inline float rjd_ease_out_sine(float t) { 177 | return sinf(t*RJD_MATH_PI/2.0f); 178 | } 179 | 180 | static inline float rjd_ease_out_quad(float t) { 181 | float tt = t - 1; 182 | return 1 - (tt * tt); 183 | } 184 | 185 | static inline float rjd_ease_out_cube(float t) { 186 | float tt = t - 1; 187 | return tt * tt * tt + 1; 188 | } 189 | 190 | static inline float rjd_ease_out_quar(float t) { 191 | float tt = t - 1; 192 | return 1 - (tt * tt * tt * tt); 193 | } 194 | 195 | static inline float rjd_ease_out_quin(float t) { 196 | float tt = t - 1; 197 | return tt * tt * tt * tt * tt + 1; 198 | } 199 | 200 | static inline float rjd_ease_out_expo(float t) { 201 | return t == 1 ? t : -powf(2, -10*t) + 1; 202 | } 203 | 204 | static inline float rjd_ease_out_circ(float t) { 205 | float tt = t - 1; 206 | return sqrtf(1 - tt * tt); 207 | } 208 | 209 | static inline float rjd_ease_out_back(float t) { 210 | float tt = 1 - t; 211 | return 1 - (tt*tt*tt - tt*sinf(tt*RJD_MATH_PI)); 212 | } 213 | 214 | static inline float rjd_ease_out_elas(float t) { 215 | return sinf(-13.0f * RJD_MATH_PI / 2.0f * (t + 1)) * powf(2, -10 * t) + 1; 216 | } 217 | 218 | static inline float rjd_ease_out_boun(float t) { 219 | if (t < 4.0f/11.0f) { 220 | return 121 * t * t / 16.0f; 221 | } else if (t < 8.0f/11.0f) { 222 | return 363.0f/40.0f*t*t - 99.0f/10.0f*t + 17.0f/5.0f; 223 | } else if (t < 9.0f/10.0f) { 224 | return 4356.0f/361.0f*t*t - 35442.0f/1805.0f*t + 16061.0f/1805.0f; 225 | } else { 226 | return 54.0f/5.0f*t*t - 513.0f/25.0f*t + 268.0f/25.0f; 227 | } 228 | } 229 | 230 | static inline float rjd_ease_inout_sine(float t) { 231 | return sinf(t*RJD_MATH_PI - RJD_MATH_PI/2.0f) / 2.0f + 0.5f; 232 | } 233 | 234 | static inline float rjd_ease_inout_quad(float t) { 235 | float tt = t * 2.0f; 236 | return ((tt < 1) ? rjd_ease_in_quad(tt) : rjd_ease_out_quad(tt - 1) + 1) / 2; 237 | } 238 | 239 | static inline float rjd_ease_inout_cube(float t) { 240 | float tt = t * 2.0f; 241 | return ((tt < 1) ? rjd_ease_in_cube(tt) : rjd_ease_out_cube(tt - 1) + 1) / 2; 242 | } 243 | 244 | static inline float rjd_ease_inout_quar(float t) { 245 | float tt = t * 2.0f; 246 | return ((tt < 1) ? rjd_ease_in_quar(tt) : rjd_ease_out_quar(tt - 1) + 1) / 2; 247 | } 248 | 249 | static inline float rjd_ease_inout_quin(float t) { 250 | float tt = t * 2.0f; 251 | return ((tt < 1) ? rjd_ease_in_quin(tt) : rjd_ease_out_quin(tt - 1) + 1) / 2; 252 | } 253 | 254 | static inline float rjd_ease_inout_expo(float t) { 255 | float tt = t * 2.0f; 256 | return ((tt < 1) ? rjd_ease_in_expo(tt) : rjd_ease_out_expo(tt - 1) + 1) / 2; 257 | } 258 | 259 | static inline float rjd_ease_inout_circ(float t) { 260 | float tt = t * 2.0f; 261 | return ((tt < 1) ? rjd_ease_in_circ(tt) : rjd_ease_out_circ(tt - 1) + 1) / 2; 262 | } 263 | 264 | static inline float rjd_ease_inout_back(float t) { 265 | float tt = t * 2.0f; 266 | return ((tt < 1) ? rjd_ease_in_back(tt) : rjd_ease_out_back(tt - 1) + 1) / 2; 267 | } 268 | 269 | static inline float rjd_ease_inout_elas(float t) { 270 | float tt = t * 2.0f; 271 | return ((tt < 1) ? rjd_ease_in_elas(tt) : rjd_ease_out_elas(tt - 1) + 1) / 2; 272 | } 273 | 274 | static inline float rjd_ease_inout_boun(float t) { 275 | float tt = t * 2.0f; 276 | return ((tt < 1) ? rjd_ease_in_boun(tt) : rjd_ease_out_boun(tt - 1) + 1) / 2; 277 | } 278 | -------------------------------------------------------------------------------- /rjd_enum.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define RJD_ENUM_H 1 4 | 5 | // TODO support RJD_ENABLE_ENUM_TYPEDEF 6 | 7 | // To use these macros, you need to define an xmacro list, then the use the appropriate *DECLARE 8 | // in a header and *DEFINE in source files. By using these macros, you get: 9 | // * The enum definition (as if you defined it yourself) 10 | // * A count of members in the enum named k__count 11 | // * An enumname_tostring() function that takes the enum and returns a static string (no mem allocation) 12 | // * An enumname_parse() function that takes a const char* and pointer to enum and returns success/fail 13 | // * An array of the enum values' string representation 14 | // 15 | // For example, let's define a Result enum with the values Success and Fail. Note to not forget the 16 | // backslash for extending the macro in the real version. 17 | // 18 | // #define MY_ENUM_LIST(macro) 19 | // macro(RESULT_FAIL) 20 | // macro(RESULT_SUCCESS) 21 | // RJD_ENUM_DECLARE(Result, MY_ENUM_LIST); 22 | // RJD_ENUM_DEFINE(Result, MY_ENUM_LIST); 23 | // 24 | // The generated interface for the Result enum above would be: 25 | // enum Result { RESULT_FAIL, RESULT_SUCCESS }; 26 | // enum { k_Result_count = 2 }; 27 | // const char* Result_tostring(enum Result v); 28 | // bool Result_parse(const char* s, enum Result v); 29 | // const char* s_Result_strings[] = { "RESULT_FAIL", "RESULT_SUCCESS" }; 30 | // 31 | // You can also specify custom strings if you want to override the default tostring/parse: 32 | // 33 | // #define MY_ENUM_LIST2(macro) 34 | // macro(MY_ENUM_LIST2_V1, "CustomStringRep1") 35 | // macro(MY_ENUM_LIST2_V2, "CustomStringRep2") 36 | // macro(MY_ENUM_LIST2_V3, "CustomStringRep3") 37 | // RJD_ENUM_DECLARE_WITH_STRINGS(CoolEnum, MY_ENUM_LIST); 38 | // RJD_ENUM_DEFINE_WITH_STRINGS(CoolEnum, MY_ENUM_LIST); 39 | // 40 | 41 | #define RJD_ENUM_IMPL_TOSTRING(name) name ## _tostring 42 | #define RJD_ENUM_IMPL_COUNT(name) k_ ## name ## _count 43 | #define RJD_ENUM_IMPL_PARSE(name) name ## _parse 44 | #define RJD_ENUM_IMPL_STRINGS(name) s_ ## name ## _strings 45 | 46 | #define RJD_ENUM_IMPL_MEMBER(item) item, 47 | #define RJD_ENUM_IMPL_SUM(item) 1 + 48 | #define RJD_ENUM_IMPL_TOSTRING_ITEM(item) #item, 49 | #define RJD_ENUM_IMPL_TOSTRING_CASE(item) case item: return #item; 50 | 51 | #define RJD_ENUM_IMPL_MEMBER_WITH_STRING(item, str) item, 52 | #define RJD_ENUM_IMPL_SUM_WITH_STRING(item, str) 1 + 53 | #define RJD_ENUM_IMPL_WITH_STRING_ITEM(item, str) str, 54 | #define RJD_ENUM_IMPL_WITH_STRING_CASE(item, str) case item: return str; 55 | 56 | #define RJD_ENUM_DECLARE(name, macrolist) \ 57 | enum name { \ 58 | macrolist(RJD_ENUM_IMPL_MEMBER) \ 59 | }; \ 60 | enum { RJD_ENUM_IMPL_COUNT(name) = macrolist(RJD_ENUM_IMPL_SUM) 0 }; \ 61 | const char* RJD_ENUM_IMPL_TOSTRING(name)(enum name v); \ 62 | bool RJD_ENUM_IMPL_PARSE(name)(const char* s, enum name* out); \ 63 | extern const char* RJD_ENUM_IMPL_STRINGS(name)[] 64 | 65 | #define RJD_ENUM_DEFINE(name, macrolist) \ 66 | const char* RJD_ENUM_IMPL_TOSTRING(name)(enum name v) { \ 67 | switch(v) { \ 68 | macrolist(RJD_ENUM_IMPL_TOSTRING_CASE) \ 69 | } \ 70 | return "invalid value"; \ 71 | } \ 72 | bool RJD_ENUM_IMPL_PARSE(name)(const char* s, enum name* out) { \ 73 | RJD_ASSERT(out); \ 74 | for (size_t i = 0; i < RJD_ENUM_IMPL_COUNT(name); ++i) { \ 75 | if (!strcmp(RJD_ENUM_IMPL_STRINGS(name)[i], s)) { \ 76 | *out = (enum name)i; \ 77 | return true; \ 78 | } \ 79 | } \ 80 | return false; \ 81 | } \ 82 | const char* RJD_ENUM_IMPL_STRINGS(name)[] = { \ 83 | macrolist(RJD_ENUM_IMPL_TOSTRING_ITEM) \ 84 | } 85 | 86 | #define RJD_ENUM_DECLARE_WITH_STRINGS(name, macrolist) \ 87 | enum name { \ 88 | macrolist(RJD_ENUM_IMPL_MEMBER_WITH_STRING) \ 89 | }; \ 90 | enum { RJD_ENUM_IMPL_COUNT(name) = macrolist(RJD_ENUM_IMPL_SUM_WITH_STRING) 0 };\ 91 | const char* RJD_ENUM_IMPL_TOSTRING(name)(enum name v); \ 92 | bool RJD_ENUM_IMPL_PARSE(name)(const char* s, enum name* out); \ 93 | extern const char* RJD_ENUM_IMPL_STRINGS(name)[] 94 | 95 | #define RJD_ENUM_DEFINE_WITH_STRINGS(name, macrolist) \ 96 | const char* RJD_ENUM_IMPL_TOSTRING(name)(enum name v) { \ 97 | switch(v) { \ 98 | macrolist(RJD_ENUM_IMPL_WITH_STRING_CASE) \ 99 | } \ 100 | return "invalid value"; \ 101 | } \ 102 | bool RJD_ENUM_IMPL_PARSE(name)(const char* s, enum name* out) { \ 103 | RJD_ASSERT(out); \ 104 | for (size_t i = 0; i < RJD_ENUM_IMPL_COUNT(name); ++i) { \ 105 | if (!strcmp(RJD_ENUM_IMPL_STRINGS(name)[i], s)) { \ 106 | *out = (enum name)i; \ 107 | return true; \ 108 | } \ 109 | } \ 110 | return false; \ 111 | } \ 112 | const char* RJD_ENUM_IMPL_STRINGS(name)[] = { \ 113 | macrolist(RJD_ENUM_IMPL_WITH_STRING_ITEM) \ 114 | } 115 | 116 | -------------------------------------------------------------------------------- /rjd_fio.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define RJD_FILEIO_H 1 4 | 5 | enum rjd_fio_writemode 6 | { 7 | RJD_FIO_WRITEMODE_REPLACE, 8 | RJD_FIO_WRITEMODE_APPEND, 9 | }; 10 | 11 | enum rjd_fio_attributes 12 | { 13 | RJD_FIO_ATTRIBUTES_READONLY = 0x1, 14 | RJD_FIO_ATTRIBUTES_DIRECTORY = 0x2, 15 | }; 16 | 17 | // use rjd_array_free() to free *buffer after use 18 | struct rjd_result rjd_fio_read(const char* path, char** buffer, struct rjd_mem_allocator* allocator); 19 | struct rjd_result rjd_fio_write(const char* path, const char* data, size_t length, enum rjd_fio_writemode mode); 20 | struct rjd_result rjd_fio_size(const char* path, size_t* out_size); 21 | struct rjd_result rjd_fio_attributes_get(const char* path, enum rjd_fio_attributes* attribute_flags); 22 | struct rjd_result rjd_fio_attributes_set_readonly(const char* path, bool readonly); 23 | struct rjd_result rjd_fio_delete(const char* path); 24 | struct rjd_result rjd_fio_mkdir(const char* path); 25 | bool rjd_fio_exists(const char* path); 26 | 27 | #if RJD_IMPL 28 | 29 | static inline struct rjd_result rjd_fio_mkdir_platform(const char* foldername); 30 | 31 | struct rjd_result rjd_fio_read(const char* path, char** buffer, struct rjd_mem_allocator* allocator) 32 | { 33 | FILE* file = fopen(path, "rb"); 34 | if (!file) { 35 | return RJD_RESULT("Failed to open the path for reading"); 36 | } 37 | 38 | if (fseek(file, 0, SEEK_END)) { 39 | fclose(file); 40 | return RJD_RESULT("Failed to seek to the end of the file"); 41 | } 42 | 43 | long int length = ftell(file); 44 | if (length < 0) { 45 | length = 0; 46 | } 47 | 48 | rewind(file); 49 | 50 | *buffer = rjd_array_alloc(char, (uint32_t)length, allocator); 51 | if (!*buffer) { 52 | return RJD_RESULT("Not enough memory in the allocator"); 53 | } 54 | rjd_array_resize(*buffer, (uint32_t)length); 55 | 56 | size_t read_length = fread(*buffer, 1, length, file); 57 | fclose(file); 58 | 59 | if (read_length < (size_t)length) { 60 | rjd_array_free(*buffer); 61 | *buffer = NULL; 62 | return RJD_RESULT("Failed to read the entire file into memory"); 63 | } 64 | 65 | return RJD_RESULT_OK(); 66 | } 67 | 68 | struct rjd_result rjd_fio_write(const char* path, const char* data, size_t length, enum rjd_fio_writemode mode) 69 | { 70 | const char* m = (mode == RJD_FIO_WRITEMODE_REPLACE) ? "wb" : "ab"; 71 | FILE* file = fopen(path, m); 72 | if (!file) { 73 | return RJD_RESULT("Failed to open a writable handle"); 74 | } 75 | 76 | const size_t written = fwrite(data, 1, length, file); 77 | 78 | fclose(file); 79 | 80 | if (written == 0) { 81 | return RJD_RESULT("Failed to write any bytes"); 82 | } else if (written < length) { 83 | return RJD_RESULT("Failed to write the entire file"); 84 | } else { 85 | return RJD_RESULT_OK(); 86 | } 87 | } 88 | 89 | struct rjd_result rjd_fio_size(const char* path, size_t* out_size) 90 | { 91 | FILE* file = fopen(path, "rb"); 92 | if (!file) { 93 | return RJD_RESULT("Failed to open the path for reading"); 94 | } 95 | 96 | if (fseek(file, 0, SEEK_END)) { 97 | fclose(file); 98 | return RJD_RESULT("Failed to seek to the end of the file"); 99 | } 100 | 101 | long int length = ftell(file); 102 | fclose(file); 103 | 104 | *out_size = (size_t) length; 105 | return RJD_RESULT_OK(); 106 | } 107 | 108 | struct rjd_result rjd_fio_mkdir(const char* path) 109 | { 110 | RJD_ASSERT(path); 111 | RJD_ASSERT(*path); 112 | 113 | struct rjd_result result = RJD_RESULT_OK(); 114 | 115 | const char* next = path; 116 | const char* end = next; 117 | do 118 | { 119 | end = strstr(end, "/"); 120 | // skip directory separator 121 | if (end) { 122 | ++end; 123 | } 124 | 125 | char stackbuffer[256]; 126 | const char* subpath = NULL; 127 | 128 | if (end) { 129 | size_t length = end - next; 130 | RJD_ASSERT(length < (ptrdiff_t)sizeof(stackbuffer)); 131 | memcpy(stackbuffer, next, length); 132 | stackbuffer[length] = '\0'; 133 | if (stackbuffer[length - 1] == '/') { 134 | stackbuffer[length - 1] = '\0'; 135 | } 136 | subpath = stackbuffer; 137 | } else { 138 | subpath = next; 139 | } 140 | 141 | if (rjd_fio_exists(subpath)) { 142 | result = RJD_RESULT("Path already exists"); 143 | } else { 144 | result = rjd_fio_mkdir_platform(subpath); 145 | if (!rjd_result_isok(result)) { 146 | break; 147 | } 148 | } 149 | } while (end != NULL); 150 | 151 | return result; 152 | } 153 | 154 | //////////////////////////////////////////////////////////////////////////////// 155 | //////////////////////////////////////////////////////////////////////////////// 156 | //////////////////////////////////////////////////////////////////////////////// 157 | 158 | #if RJD_COMPILER_MSVC 159 | 160 | #include 161 | 162 | static struct rjd_result rjd_fio_delete_folder_recursive(const wchar_t* directory_path); 163 | 164 | #define RJD_FIO_UTF8_TO_UTF16(utf8, out_utf16_name) \ 165 | const size_t length_utf16 = mbstowcs(NULL, utf8, INT_MAX); \ 166 | wchar_t* out_utf16_name = rjd_mem_alloc_stack_array_noclear(wchar_t, length_utf16 + 1); \ 167 | mbstowcs(out_utf16_name, utf8, INT_MAX); 168 | 169 | struct rjd_result rjd_fio_attributes_get(const char* path, enum rjd_fio_attributes* attribute_flags) 170 | { 171 | *attribute_flags = 0; 172 | 173 | RJD_FIO_UTF8_TO_UTF16(path, path_wide); 174 | DWORD attributes = GetFileAttributesW(path_wide); 175 | if (attributes == INVALID_FILE_ATTRIBUTES) { 176 | return RJD_RESULT("Failed to get file attributes"); 177 | } 178 | 179 | *attribute_flags |= (attributes & FILE_ATTRIBUTE_DIRECTORY) ? RJD_FIO_ATTRIBUTES_DIRECTORY : 0; 180 | *attribute_flags |= (attributes & FILE_ATTRIBUTE_READONLY) ? RJD_FIO_ATTRIBUTES_READONLY : 0; 181 | 182 | return RJD_RESULT_OK(); 183 | } 184 | 185 | struct rjd_result rjd_fio_attributes_set_readonly(const char* path, bool readonly) 186 | { 187 | RJD_FIO_UTF8_TO_UTF16(path, path_wide); 188 | DWORD attributes = GetFileAttributesW(path_wide); 189 | if (attributes == INVALID_FILE_ATTRIBUTES) { 190 | return RJD_RESULT("Failed to get file attributes"); 191 | } 192 | 193 | if (readonly) { 194 | attributes |= FILE_ATTRIBUTE_READONLY; 195 | } else { 196 | attributes &= ~FILE_ATTRIBUTE_READONLY; 197 | } 198 | 199 | if (SetFileAttributesW(path_wide, attributes) == 0) { 200 | return RJD_RESULT("Failed to set file attributes"); 201 | } 202 | 203 | return RJD_RESULT_OK(); 204 | } 205 | 206 | struct rjd_result rjd_fio_delete(const char* path) 207 | { 208 | RJD_ASSERT(path && *path); 209 | RJD_FIO_UTF8_TO_UTF16(path, path_wide); 210 | 211 | DWORD attributes = GetFileAttributesW(path_wide); 212 | if (attributes == INVALID_FILE_ATTRIBUTES) 213 | { 214 | return RJD_RESULT("Failed getting path attributes. Check permissions and verify the path exists."); 215 | } 216 | 217 | if (attributes & FILE_ATTRIBUTE_READONLY) { 218 | return RJD_RESULT("Path is read-only"); 219 | } 220 | 221 | if (attributes & FILE_ATTRIBUTE_DIRECTORY) { 222 | return rjd_fio_delete_folder_recursive(path_wide); 223 | } 224 | 225 | if (!DeleteFileW(path_wide)) { 226 | return RJD_RESULT("Failed to delete file. Check GetLastError() for more info."); 227 | } 228 | 229 | return RJD_RESULT_OK(); 230 | } 231 | 232 | bool rjd_fio_exists(const char* path) 233 | { 234 | RJD_ASSERT(path); 235 | RJD_FIO_UTF8_TO_UTF16(path, path_wide); 236 | 237 | DWORD attributes = GetFileAttributesW(path_wide); 238 | if (attributes == INVALID_FILE_ATTRIBUTES) { 239 | DWORD err = GetLastError(); 240 | return err != ERROR_FILE_NOT_FOUND && err != ERROR_PATH_NOT_FOUND; 241 | } 242 | 243 | return true; 244 | } 245 | 246 | //////////////////////////////////////////////////////////////////////////////// 247 | 248 | static inline struct rjd_result rjd_fio_mkdir_platform(const char* foldername) 249 | { 250 | RJD_FIO_UTF8_TO_UTF16(foldername, foldername_wide); 251 | 252 | struct rjd_result result = RJD_RESULT_OK(); 253 | SECURITY_ATTRIBUTES security = { .nLength = sizeof(SECURITY_ATTRIBUTES) }; 254 | if (!CreateDirectoryW(foldername_wide, &security)) { 255 | const int error = GetLastError(); 256 | RJD_ASSERTMSG(error != ERROR_PATH_NOT_FOUND, "The rjd_fio_mkdir() code should handle this case."); 257 | switch (GetLastError()) 258 | { 259 | case ERROR_ALREADY_EXISTS: result = RJD_RESULT("Folder already exists"); break; 260 | default: result = RJD_RESULT("Unknown error creating subfolder"); break; 261 | } 262 | } 263 | 264 | return result; 265 | } 266 | 267 | struct rjd_result rjd_fio_delete_folder_recursive(const wchar_t* directory_path) 268 | { 269 | wchar_t* path_with_search_spec = NULL; 270 | const size_t path_length = wcslen(directory_path); 271 | { 272 | const wchar_t search_spec[] = L"/*"; 273 | path_with_search_spec = rjd_mem_alloc_stack_array_noclear(wchar_t, path_length + rjd_countof(search_spec) + 1); 274 | wcscpy(path_with_search_spec, directory_path); 275 | wcscpy(path_with_search_spec + path_length, search_spec); 276 | } 277 | 278 | WIN32_FIND_DATAW find_data = {0}; 279 | const HANDLE find_handle = FindFirstFileW(path_with_search_spec, &find_data); 280 | if (find_handle == INVALID_HANDLE_VALUE) { 281 | const DWORD err = GetLastError(); 282 | if (err == ERROR_FILE_NOT_FOUND || err == ERROR_PATH_NOT_FOUND) { 283 | return RJD_RESULT("Directory not found"); 284 | } 285 | return RJD_RESULT("Failed while enumerating directory contents. Check GetLastError() for more info"); 286 | } 287 | 288 | do 289 | { 290 | if (find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { 291 | if (wcscmp(find_data.cFileName, L".") != 0 && 292 | wcscmp(find_data.cFileName, L"..") != 0) { 293 | 294 | size_t nested_path_length = wcslen(find_data.cFileName); 295 | wchar_t* nested_path = _alloca((path_length + nested_path_length + 2) * sizeof(wchar_t)); // +2 for null and path separator 296 | wcscpy(nested_path, directory_path); 297 | wcscpy(nested_path + path_length, L"/"); 298 | wcscpy(nested_path + path_length + 1, find_data.cFileName); 299 | 300 | struct rjd_result result = rjd_fio_delete_folder_recursive(nested_path); 301 | if (!rjd_result_isok(result)) { 302 | return result; 303 | } 304 | } 305 | } else { 306 | if (!DeleteFileW(find_data.cFileName)) { 307 | FindClose(find_handle); 308 | return RJD_RESULT("Failed to delete file. Check GetLastError() for more info."); 309 | } 310 | } 311 | } while (FindNextFileW(find_handle, &find_data)); 312 | 313 | FindClose(find_handle); 314 | 315 | if (GetLastError() != ERROR_NO_MORE_FILES) { 316 | return RJD_RESULT("Failed while enumerating directory contents. Check GetLastError() for more info"); 317 | } 318 | 319 | if (!RemoveDirectoryW(directory_path)) { 320 | return RJD_RESULT("Failed to delete the directory. Check GetLastError() for more info"); 321 | } 322 | 323 | return RJD_RESULT_OK(); 324 | } 325 | 326 | //////////////////////////////////////////////////////////////////////////////// 327 | //////////////////////////////////////////////////////////////////////////////// 328 | //////////////////////////////////////////////////////////////////////////////// 329 | 330 | #elif RJD_COMPILER_GCC || RJD_COMPILER_CLANG 331 | 332 | #include 333 | #include 334 | #include 335 | 336 | static int rjd_delete_nftw_func(const char *path, const struct stat *sb, int typeflag, struct FTW *ftwbuf); 337 | 338 | struct rjd_result rjd_fio_attributes_get(const char* path, enum rjd_fio_attributes* attribute_flags) 339 | { 340 | *attribute_flags = 0; 341 | 342 | struct stat s = {0}; 343 | 344 | if (stat(path, &s) == 0) { 345 | const mode_t write_bits = S_IWUSR | S_IWGRP | S_IWOTH; 346 | 347 | *attribute_flags |= S_ISDIR(s.st_mode) ? RJD_FIO_ATTRIBUTES_DIRECTORY : 0; 348 | *attribute_flags |= (s.st_mode & write_bits) ? 0 : RJD_FIO_ATTRIBUTES_READONLY; 349 | return RJD_RESULT_OK(); 350 | } 351 | return RJD_RESULT("Failed to get attributes for path"); 352 | } 353 | 354 | struct rjd_result rjd_fio_attributes_set_readonly(const char* path, bool readonly) 355 | { 356 | struct stat s = {0}; 357 | 358 | if (stat(path, &s) == 0) { 359 | const mode_t write_bits = S_IWUSR | S_IWGRP | S_IWOTH; 360 | mode_t mode = s.st_mode; 361 | 362 | if (readonly) { 363 | mode &= ~write_bits; 364 | } else { 365 | mode |= write_bits; 366 | } 367 | 368 | if (chmod(path, mode) != 0) { 369 | return RJD_RESULT("Failed to change access modifier"); 370 | } 371 | return RJD_RESULT_OK(); 372 | } 373 | 374 | return RJD_RESULT("Failed to get existing access modifiers"); 375 | } 376 | 377 | struct rjd_result rjd_fio_delete(const char* path) 378 | { 379 | if (remove(path)) { 380 | if (nftw(path, rjd_delete_nftw_func, 64, FTW_DEPTH | FTW_PHYS)) { 381 | return RJD_RESULT("delete failed"); 382 | } 383 | } 384 | 385 | return RJD_RESULT_OK(); 386 | } 387 | 388 | bool rjd_fio_exists(const char* path) 389 | { 390 | struct stat unused; 391 | return !stat(path, &unused); 392 | } 393 | 394 | //////////////////////////////////////////////////////////////////////////////// 395 | 396 | static int rjd_delete_nftw_func(const char *path, const struct stat *sb, int typeflag, struct FTW *ftwbuf) 397 | { 398 | RJD_UNUSED_PARAM(sb); 399 | RJD_UNUSED_PARAM(typeflag); 400 | RJD_UNUSED_PARAM(ftwbuf); 401 | return remove(path); 402 | } 403 | 404 | static inline struct rjd_result rjd_fio_mkdir_platform(const char* foldername) 405 | { 406 | const mode_t mode = S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH; 407 | const int error = mkdir(foldername, mode); 408 | 409 | struct rjd_result result = RJD_RESULT_OK(); 410 | switch (error) 411 | { 412 | case 0: break; 413 | case EACCES: result = RJD_RESULT("The parent directory does not allow write permission to the process, or one of the directories in pathname did not allow search permission."); break; 414 | case EDQUOT: result = RJD_RESULT("The user's quota of disk blocks or inodes on the file system has been exhausted."); break; 415 | case EEXIST: result = RJD_RESULT("pathname already exists (not necessarily as a directory). This includes the case where pathname is a symbolic link, dangling or not."); break; 416 | case EFAULT: result = RJD_RESULT("pathname points outside your accessible address space."); break; 417 | case ELOOP: result = RJD_RESULT("Too many symbolic links were encountered in resolving pathname."); break; 418 | case EMLINK: result = RJD_RESULT("The number of links to the parent directory would exceed LINK_MAX."); break; 419 | case ENAMETOOLONG: result = RJD_RESULT("pathname was too long."); break; 420 | case ENOENT: result = RJD_RESULT("A directory component in pathname does not exist or is a dangling symbolic link."); break; 421 | case ENOMEM: result = RJD_RESULT("Insufficient kernel memory was available."); break; 422 | case ENOSPC: result = RJD_RESULT("The device has no room for the new directory (user's disk quota may be exhausted)."); break; 423 | case ENOTDIR: result = RJD_RESULT("A component used as a directory in pathname is not, in fact, a directory."); break; 424 | case EPERM: result = RJD_RESULT("The file system containing pathname does not support the creation of directories."); break; 425 | case EROFS: result = RJD_RESULT("pathname refers to a file on a read-only file system."); break; 426 | default: result = RJD_RESULT("Unknown error creating subpath"); break; 427 | } 428 | 429 | return result; 430 | } 431 | 432 | #endif 433 | 434 | #endif // RJD_IMPL 435 | 436 | -------------------------------------------------------------------------------- /rjd_geo.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define RJD_GEO_H 1 4 | 5 | typedef struct { 6 | rjd_math_vec4 minmax; // xy is the min, zw is the max 7 | } rjd_geo_rect; 8 | 9 | typedef struct { 10 | rjd_math_vec3 xyr; // z is the radius 11 | } rjd_geo_circle; 12 | 13 | typedef struct { 14 | rjd_math_vec3 min; 15 | rjd_math_vec3 max; 16 | } rjd_geo_box; 17 | 18 | typedef struct { 19 | rjd_math_vec4 xyzr; // w is the radius 20 | } rjd_geo_sphere; 21 | 22 | typedef struct { 23 | rjd_math_vec3 p; 24 | rjd_math_vec3 d; 25 | } rjd_geo_ray; 26 | 27 | rjd_geo_rect rjd_geo_rect_minmax(float minx, float miny, float maxx, float maxy); 28 | rjd_geo_circle rjd_geo_circle_xyr(float x, float y, float r); 29 | rjd_geo_box rjd_geo_box_minmax(rjd_math_vec3 min, rjd_math_vec3 max); 30 | rjd_geo_sphere rjd_geo_sphere_xyzr(float x, float y, float z, float r); 31 | rjd_geo_ray rjd_geo_ray_pd(rjd_math_vec3 p, rjd_math_vec3 d); 32 | 33 | bool rjd_geo_point_rect(rjd_math_vec3 p, rjd_geo_rect r); 34 | bool rjd_geo_point_box(rjd_math_vec3 p, rjd_geo_box b); 35 | bool rjd_geo_point_circle(rjd_math_vec3 p, rjd_geo_circle c); 36 | bool rjd_geo_point_sphere(rjd_math_vec3 p, rjd_geo_sphere s); 37 | bool rjd_geo_circle_circle(rjd_geo_circle c1, rjd_geo_circle c2); 38 | bool rjd_geo_circle_rect(rjd_geo_circle c, rjd_geo_rect r); 39 | bool rjd_geo_rect_rect(rjd_geo_rect a, rjd_geo_rect b); 40 | bool rjd_geo_sphere_sphere(rjd_geo_sphere b, rjd_geo_sphere a); 41 | bool rjd_geo_sphere_box(rjd_geo_sphere s, rjd_geo_box b); 42 | bool rjd_geo_box_box(rjd_geo_box a, rjd_geo_box b); 43 | bool rjd_geo_ray_point(rjd_geo_ray r, rjd_math_vec3 p, float* t_out); 44 | bool rjd_geo_ray_sphere(rjd_geo_ray r, rjd_geo_sphere s, float* t_out); 45 | bool rjd_geo_ray_box(rjd_geo_ray r, rjd_geo_box b, float* t_out); 46 | bool rjd_geo_ray_boxfast(rjd_math_vec3 ray_pos, rjd_math_vec3 ray_inv_dir, rjd_geo_box b, float* t_out); 47 | 48 | #if RJD_IMPL 49 | 50 | rjd_geo_rect rjd_geo_rect_minmax(float minx, float miny, float maxx, float maxy) { 51 | RJD_ASSERT(minx <= maxx); 52 | RJD_ASSERT(miny <= maxy); 53 | rjd_geo_rect r = { rjd_math_vec4_xyzw(minx, miny, maxx, maxy) }; 54 | return r; 55 | } 56 | 57 | rjd_geo_circle rjd_geo_circle_xyr(float x, float y, float r) { 58 | rjd_geo_circle c = { rjd_math_vec3_xyz(x,y,r) }; 59 | return c; 60 | } 61 | 62 | rjd_geo_box rjd_geo_box_minmax(rjd_math_vec3 min, rjd_math_vec3 max) { 63 | rjd_geo_box b = { min, max }; 64 | return b; 65 | } 66 | 67 | rjd_geo_sphere rjd_geo_sphere_xyzr(float x, float y, float z, float r) { 68 | rjd_geo_sphere s = { rjd_math_vec4_xyzw(x,y,z,r) }; 69 | return s; 70 | } 71 | 72 | rjd_geo_ray rjd_geo_ray_pd(rjd_math_vec3 p, rjd_math_vec3 d) { 73 | rjd_geo_ray r = { p, d }; 74 | return r; 75 | } 76 | 77 | bool rjd_geo_point_rect(rjd_math_vec3 p, rjd_geo_rect r) { 78 | rjd_math_vec4 pp = rjd_math_vec3to4(p); 79 | pp = rjd_math_vec4_shuffle(pp,0,1,0,1); // { x,y,x,y } 80 | 81 | rjd_math_vec4 a = { _mm_unpackhi_ps(pp.v, r.minmax.v) }; // { x maxx y maxy } 82 | rjd_math_vec4 b = { _mm_unpacklo_ps(pp.v, r.minmax.v) }; // { x minx y miny } 83 | b = rjd_math_vec4_shuffle(b,1,0,3,2); // { minx x miny y } 84 | 85 | return rjd_math_vec4_ge(a,b); // { x >= minx, maxx >= x, y >= miny, maxy >= y } 86 | } 87 | 88 | bool rjd_geo_point_box(rjd_math_vec3 p, rjd_geo_box b) { 89 | return rjd_math_vec3_ge(p, b.min) && rjd_math_vec3_ge(b.max, p); 90 | } 91 | 92 | bool rjd_geo_point_circle(rjd_math_vec3 p, rjd_geo_circle c) { 93 | rjd_math_vec3 v = rjd_math_vec3_sub(p, rjd_math_vec3_setz(c.xyr,0)); 94 | return rjd_math_vec3_lengthsq(v) <= powf(rjd_math_vec3_z(c.xyr), 2); 95 | } 96 | 97 | bool rjd_geo_point_sphere(rjd_math_vec3 p, rjd_geo_sphere s) { 98 | rjd_math_vec4 pp = rjd_math_vec3to4(p); 99 | rjd_math_vec4 v = rjd_math_vec4_sub(pp, rjd_math_vec4_setw(s.xyzr, 0)); 100 | return rjd_math_vec4_lengthsq(v) <= powf(rjd_math_vec4_w(s.xyzr), 2); 101 | } 102 | 103 | bool rjd_geo_circle_circle(rjd_geo_circle c1, rjd_geo_circle c2) { 104 | rjd_math_vec3 v = rjd_math_vec3_setz(rjd_math_vec3_sub(c1.xyr, c2.xyr), 0); 105 | rjd_math_vec3 added = rjd_math_vec3_add(c1.xyr, c2.xyr); 106 | rjd_math_vec3 squared = rjd_math_vec3_mul(added, added); 107 | return rjd_math_vec3_lengthsq(v) <= rjd_math_vec3_z(squared); 108 | } 109 | 110 | bool rjd_geo_circle_rect(rjd_geo_circle c, rjd_geo_rect r) { 111 | rjd_math_vec3 min = rjd_math_vec4to3(r.minmax); 112 | rjd_math_vec3 max = { _mm_movehl_ps(r.minmax.v, r.minmax.v) }; 113 | 114 | rjd_math_vec3 center = rjd_math_vec3_setz(c.xyr, 0); 115 | rjd_math_vec3 p = rjd_math_vec3_max(min, rjd_math_vec3_min(max, center)); 116 | p = rjd_math_vec3_setz(p, 0); 117 | return rjd_geo_point_circle(p, c); 118 | } 119 | 120 | bool rjd_geo_rect_rect(rjd_geo_rect a, rjd_geo_rect b) { 121 | rjd_math_vec4 min = { _mm_unpacklo_ps(a.minmax.v, b.minmax.v) }; // a.x, b.x, a.y, b.y 122 | rjd_math_vec4 max = { _mm_unpackhi_ps(a.minmax.v, b.minmax.v) }; // a.x, b.x, a.y, b.y 123 | max = rjd_math_vec4_shuffle(max,1,0,3,2); // b.x, a.x, b.y, a.y 124 | 125 | return rjd_math_vec4_ge(max, min); 126 | } 127 | 128 | bool rjd_geo_sphere_sphere(rjd_geo_sphere a, rjd_geo_sphere b) { 129 | rjd_math_vec4 v = rjd_math_vec4_setw(rjd_math_vec4_sub(a.xyzr, b.xyzr), 0); 130 | rjd_math_vec4 squared = rjd_math_vec4_mul(a.xyzr, b.xyzr); 131 | return rjd_math_vec4_lengthsq(v) <= rjd_math_vec4_w(squared); 132 | } 133 | 134 | bool rjd_geo_sphere_box(rjd_geo_sphere s, rjd_geo_box b) { 135 | rjd_math_vec3 center = rjd_math_vec4to3w(s.xyzr); 136 | rjd_math_vec3 p = rjd_math_vec3_max(b.min, rjd_math_vec3_min(b.max, center)); 137 | return rjd_geo_point_sphere(p, s); 138 | } 139 | 140 | bool rjd_geo_box_box(rjd_geo_box a, rjd_geo_box b) { 141 | return rjd_math_vec3_ge(a.max, b.min) && rjd_math_vec3_ge(b.max, a.min); 142 | } 143 | 144 | bool rjd_geo_ray_point(rjd_geo_ray r, rjd_math_vec3 p, float* t_out) { 145 | rjd_math_vec3 to_p = rjd_math_vec3_sub(r.p, p); 146 | rjd_math_vec3 to_p_normalized = rjd_math_vec3_normalize(to_p); 147 | if (rjd_math_isequal(rjd_math_vec3_dot(to_p_normalized, r.d), 1)) { 148 | return false; 149 | } 150 | 151 | if (t_out) { 152 | *t_out = rjd_math_vec3_length(to_p); 153 | } 154 | 155 | return true; 156 | } 157 | 158 | bool rjd_geo_ray_sphere(rjd_geo_ray r, rjd_geo_sphere s, float* t_out) { 159 | rjd_math_vec3 center = rjd_math_vec4to3w(s.xyzr); 160 | rjd_math_vec3 r_to_s = rjd_math_vec3_sub(center, r.p); 161 | float dot = rjd_math_vec3_dot(r.d, r_to_s); 162 | if (dot < 0) { 163 | return false; 164 | } 165 | 166 | rjd_math_vec3 p = rjd_math_vec3_scale(r.d, dot); 167 | rjd_math_vec3 s_to_p = rjd_math_vec3_sub(p, center); 168 | bool colliding = rjd_math_vec3_lengthsq(s_to_p) <= powf(rjd_math_vec4_w(s.xyzr), 2); 169 | if (colliding && t_out) { 170 | *t_out = dot; 171 | } 172 | 173 | return colliding; 174 | } 175 | 176 | bool rjd_geo_ray_box(rjd_geo_ray r, rjd_geo_box b, float* t_out) { 177 | rjd_math_vec3 inv_dir = rjd_math_vec3_div(rjd_math_vec3_one(), r.d); 178 | 179 | float t_dummy = FLT_MAX; 180 | if (!t_out) { 181 | t_out = &t_dummy; 182 | } 183 | 184 | return rjd_geo_ray_boxfast(r.p, inv_dir, b, t_out); 185 | } 186 | 187 | bool rjd_geo_ray_boxfast(rjd_math_vec3 ray_pos, rjd_math_vec3 ray_inv_dir, rjd_geo_box b, float* t_out) { 188 | rjd_math_vec3 v0 = rjd_math_vec3_mul(rjd_math_vec3_sub(b.min, ray_pos), ray_inv_dir); 189 | rjd_math_vec3 v1 = rjd_math_vec3_mul(rjd_math_vec3_sub(b.max, ray_pos), ray_inv_dir); 190 | 191 | rjd_math_vec3 min = rjd_math_vec3_min(v0, v1); 192 | rjd_math_vec3 max = rjd_math_vec3_max(v0, v1); 193 | 194 | float tmin = rjd_math_vec3_hmax(min); 195 | float tmax = rjd_math_vec3_hmin(max); 196 | 197 | bool colliding = (tmax >= 0) && (tmax >= tmin) && (tmin <= *t_out); 198 | if (colliding) { 199 | *t_out = tmin; 200 | } 201 | return colliding; 202 | } 203 | 204 | #endif // RJD_IMPL 205 | 206 | -------------------------------------------------------------------------------- /rjd_hash.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define RJD_HASH_H 1 4 | 5 | struct rjd_hash32 6 | { 7 | uint32_t value; 8 | }; 9 | 10 | struct rjd_hash64 11 | { 12 | uint64_t value; 13 | }; 14 | 15 | enum 16 | { 17 | RJD_HASH_NULLTERMINATED_BUFFER = -1, 18 | }; 19 | 20 | extern const struct rjd_hash32 RJD_HASH32_INVALID; 21 | extern const struct rjd_hash64 RJD_HASH64_INVALID; 22 | 23 | // You can pass -1 as the length to indicate a NULL-terminated buffer (e.g. c-style string) 24 | struct rjd_hash32 rjd_hash32_data(const uint8_t* key, int length); 25 | struct rjd_hash64 rjd_hash64_data(const uint8_t* key, int length); 26 | static inline struct rjd_hash32 rjd_hash32_str(const char* key); 27 | static inline struct rjd_hash64 rjd_hash64_str(const char* key); 28 | bool rjd_hash32_valid(struct rjd_hash32 hash); 29 | bool rjd_hash64_valid(struct rjd_hash64 hash); 30 | 31 | //////////////////////////////////////////////////////////////////////////////// 32 | // Inline implementation 33 | 34 | static inline struct rjd_hash32 rjd_hash32_str(const char* key) 35 | { 36 | const void* data = key; 37 | return rjd_hash32_data(data, -1); 38 | } 39 | 40 | static inline struct rjd_hash64 rjd_hash64_str(const char* key) 41 | { 42 | const void* data = key; 43 | return rjd_hash64_data(data, -1); 44 | } 45 | 46 | #if RJD_IMPL 47 | 48 | const struct rjd_hash32 RJD_HASH32_INVALID = {0}; 49 | const struct rjd_hash64 RJD_HASH64_INVALID = {0}; 50 | 51 | // Code derived from: 52 | // Copyright (c) 2011 Stephan Brumme. All rights reserved. 53 | // see http://create.stephan-brumme.com/disclaimer.html 54 | // 55 | // originally developed by Fowler, Noll and Vo 56 | // http://isthe.com/chongo/tech/comp/fnv/ 57 | // 58 | // prime/seed from http://isthe.com/chongo/tech/comp/fnv/ 59 | 60 | struct rjd_hash32 rjd_hash32_data(const uint8_t* key, int length) 61 | { 62 | RJD_ASSERT(length >= -1); 63 | 64 | if (key == NULL || length == 0 || (length == -1 && *key == '\0')) { 65 | struct rjd_hash32 hash = {0}; 66 | return hash; 67 | } 68 | 69 | const uint32_t PRIME = 16777619; 70 | const uint32_t SEED = 2166136261; 71 | 72 | struct rjd_hash32 hash = { SEED }; 73 | if (length == -1) { 74 | while (*key) { 75 | hash.value = (*key++ ^ hash.value) * PRIME; 76 | } 77 | } else { 78 | while (length > 0) 79 | { 80 | --length; 81 | hash.value = (uint32_t)((*key++ ^ hash.value) * PRIME); 82 | } 83 | } 84 | return hash; 85 | } 86 | 87 | struct rjd_hash64 rjd_hash64_data(const uint8_t* key, int length) 88 | { 89 | RJD_ASSERT(length >= -1); 90 | 91 | if (key == NULL || length == 0 || (length == -1 && *key == '\0')) { 92 | struct rjd_hash64 hash = {0}; 93 | return hash; 94 | } 95 | 96 | const uint64_t PRIME = 1099511628211ull; 97 | const uint64_t SEED = 14695981039346656037ull; 98 | 99 | struct rjd_hash64 hash = { SEED }; 100 | if (length == -1) { 101 | while (*key) { 102 | hash.value = (*key++ ^ hash.value) * PRIME; 103 | } 104 | } else { 105 | while (length > 0) 106 | { 107 | --length; 108 | hash.value = (*key++ ^ hash.value) * PRIME; 109 | } 110 | } 111 | return hash; 112 | } 113 | 114 | bool rjd_hash32_valid(struct rjd_hash32 hash) 115 | { 116 | return hash.value != 0; 117 | } 118 | 119 | bool rjd_hash64_valid(struct rjd_hash64 hash) 120 | { 121 | return hash.value != 0; 122 | } 123 | 124 | #endif // RJD_IMPL 125 | 126 | -------------------------------------------------------------------------------- /rjd_mem.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define RJD_MEM_H 1 4 | 5 | #if RJD_COMPILER_MSVC 6 | #include // _alloca 7 | #else 8 | #include 9 | #endif 10 | 11 | struct rjd_mem_allocator_stats // TODO use atomics 12 | { 13 | struct rjd_atomic_uint64 total_size; 14 | struct { 15 | struct rjd_atomic_uint64 used; 16 | struct rjd_atomic_uint64 overhead; 17 | struct rjd_atomic_uint64 peak; 18 | struct rjd_atomic_uint64 unused; 19 | struct rjd_atomic_uint64 allocs; 20 | struct rjd_atomic_uint64 frees; 21 | } current; 22 | struct { 23 | struct rjd_atomic_uint64 peak; 24 | struct rjd_atomic_uint64 allocs; 25 | struct rjd_atomic_uint64 frees; 26 | struct rjd_atomic_uint64 resets; 27 | } lifetime; 28 | }; 29 | 30 | enum 31 | { 32 | RJD_KB = 1024, 33 | RJD_MB = 1024 * 1024, 34 | RJD_GB = 1024 * 1024 * 1024, 35 | }; 36 | 37 | // TODO realloc 38 | typedef const char* (*rjd_mem_allocator_type_func)(void); 39 | typedef void* (*rjd_mem_allocator_alloc_func)(size_t size, void* optional_heap); 40 | typedef void (*rjd_mem_allocator_free_func)(void* memory); 41 | typedef void (*rjd_mem_allocator_reset_func)(void* optional_heap); 42 | 43 | struct rjd_mem_allocator 44 | { 45 | rjd_mem_allocator_type_func type_func; // must return a static string 46 | rjd_mem_allocator_alloc_func alloc_func; 47 | rjd_mem_allocator_free_func free_func; 48 | rjd_mem_allocator_reset_func reset_func; // optional 49 | void* optional_heap; 50 | 51 | struct rjd_mem_allocator_stats stats; 52 | 53 | uint32_t debug_sentinel; 54 | }; 55 | 56 | struct rjd_mem_allocator rjd_mem_allocator_init_default(void); 57 | struct rjd_mem_allocator rjd_mem_allocator_init_linear(void* memblock, size_t size); 58 | 59 | const char* rjd_mem_allocator_type(struct rjd_mem_allocator* allocator); 60 | bool rjd_mem_allocator_reset(struct rjd_mem_allocator* allocator); 61 | struct rjd_mem_allocator_stats rjd_mem_allocator_getstats(const struct rjd_mem_allocator* allocator); 62 | 63 | void rjd_mem_free(const void* mem); 64 | void rjd_mem_swap(void* restrict mem1, void* restrict mem2, size_t size); 65 | 66 | #define rjd_mem_alloc(type, allocator) ((type*)rjd_mem_alloc_impl(sizeof(type), (allocator), 8, true)) 67 | #define rjd_mem_alloc_noclear(type, allocator) ((type*)rjd_mem_alloc_impl(sizeof(type), (allocator), 8, false)) 68 | #define rjd_mem_alloc_aligned(type, allocator, alignment) ((type*)rjd_mem_alloc_impl(sizeof(type), allocator, alignment, true)) 69 | #define rjd_mem_alloc_array(type, count, allocator) ((type*)rjd_mem_alloc_impl(sizeof(type) * count, allocator, 8, true)) 70 | #define rjd_mem_alloc_array_noclear(type, count, allocator) ((type*)rjd_mem_alloc_impl(sizeof(type) * count, allocator, 8, false)) 71 | #define rjd_mem_alloc_array_aligned(type, count, allocator, alignment) ((type*)rjd_mem_alloc_impl(sizeof(type) * count, allocator, alignment, true)) 72 | #define rjd_mem_alloc_array_aligned_noclear(type, count, allocator, alignment) ((type*)rjd_mem_alloc_impl(sizeof(type) * count, allocator, alignment, false)) 73 | 74 | #if RJD_COMPILER_MSVC 75 | #define rjd_mem_alloc_stack_noclear(type) (type*)_alloca(sizeof(type)) 76 | #define rjd_mem_alloc_stack_array_noclear(type, count) (type*)_alloca(sizeof(type) * count) 77 | #else 78 | #define rjd_mem_alloc_stack_noclear(type) (type*)alloca(sizeof(type)) 79 | #define rjd_mem_alloc_stack_array_noclear(type, count) (type*)alloca(sizeof(type) * count) 80 | #endif 81 | 82 | #define RJD_MEM_ISALIGNED(p, align) (((uintptr_t)(p) & ((align)-1)) == 0) 83 | #define RJD_MEM_ALIGN(size, align) ((size) + (RJD_MEM_ISALIGNED(size, align) ? 0 : ((align) - ((size) & ((align)-1))))) 84 | 85 | void* rjd_mem_alloc_impl(size_t size, struct rjd_mem_allocator* allocator, uint32_t alignment, bool clear); 86 | 87 | //////////////////////////////////////////////////////////////////////////////// 88 | 89 | #if RJD_IMPL 90 | 91 | struct rjd_mem_heap_linear 92 | { 93 | void* base; 94 | void* next; 95 | size_t size; 96 | uint32_t debug_sentinel; 97 | }; 98 | 99 | struct rjd_mem_allocation_header 100 | { 101 | struct rjd_mem_allocator* allocator; 102 | uint16_t offset_to_block_begin_from_user; 103 | uint32_t total_blocksize; 104 | uint32_t debug_sentinel; 105 | }; 106 | 107 | static const char* rjd_mem_allocator_global_type(void); 108 | static void* rjd_mem_allocator_global_alloc(size_t size, void* heap); 109 | static void rjd_mem_allocator_global_free(void* mem); 110 | 111 | static const char* rjd_mem_allocator_linear_type(void); 112 | static void* rjd_mem_allocator_linear_alloc(size_t size, void* heap); 113 | static void rjd_mem_allocator_linear_reset(void* heap); 114 | 115 | const uint32_t RJD_MEM_DEBUG_DEFAULT_SENTINEL32 = 0xA7A7A7A7u; 116 | const uint32_t RJD_MEM_DEBUG_LINEAR_SENTINEL32 = 0xA8A8A8A8u; 117 | const uint32_t RJD_MEM_STATS_UNKNOWN_UPPERBOUND = UINT32_MAX; 118 | 119 | struct rjd_mem_allocator rjd_mem_allocator_init_default() 120 | { 121 | struct rjd_mem_allocator allocator = { 122 | .type_func = rjd_mem_allocator_global_type, 123 | .alloc_func = NULL, 124 | .free_func = NULL, 125 | .reset_func = NULL, 126 | .optional_heap = NULL, 127 | .debug_sentinel = RJD_MEM_DEBUG_DEFAULT_SENTINEL32, 128 | }; 129 | 130 | rjd_atomic_uint64_set(&allocator.stats.total_size, RJD_MEM_STATS_UNKNOWN_UPPERBOUND); 131 | rjd_atomic_uint64_set(&allocator.stats.current.unused, RJD_MEM_STATS_UNKNOWN_UPPERBOUND); 132 | 133 | // MSVC has a slightly different signature for malloc/free so to avoid platform-specific 134 | // code, we just wrap them for all platforms here 135 | allocator.alloc_func = rjd_mem_allocator_global_alloc; 136 | allocator.free_func = rjd_mem_allocator_global_free; 137 | 138 | return allocator; 139 | } 140 | 141 | struct rjd_mem_allocator rjd_mem_allocator_init_linear(void* memblock, size_t size) 142 | { 143 | RJD_ASSERT(memblock); 144 | 145 | struct rjd_mem_heap_linear* heap = (struct rjd_mem_heap_linear*)RJD_MEM_ALIGN((uintptr_t)memblock, 64); 146 | uint32_t usable_size = 0; 147 | { 148 | char* usable_memory_start = (char*)RJD_MEM_ALIGN((uintptr_t)heap + sizeof(struct rjd_mem_heap_linear), 64); 149 | char* usable_memory_end = (char*)memblock + size; 150 | RJD_ASSERTMSG(usable_memory_start < usable_memory_end, 151 | "Given size was not large enough to make a heap. You need at least 128 bytes..."); 152 | 153 | usable_size = (uint32_t)(usable_memory_end - usable_memory_start); 154 | 155 | struct rjd_mem_heap_linear copy = { 156 | .base = usable_memory_start, 157 | .next = usable_memory_start, 158 | .size = usable_size, 159 | .debug_sentinel = RJD_MEM_DEBUG_LINEAR_SENTINEL32 160 | }; 161 | *heap = copy; 162 | } 163 | 164 | struct rjd_mem_allocator allocator = { 165 | .type_func = rjd_mem_allocator_linear_type, 166 | .alloc_func = rjd_mem_allocator_linear_alloc, 167 | .free_func = NULL, 168 | .reset_func = rjd_mem_allocator_linear_reset, 169 | .optional_heap = heap, 170 | .debug_sentinel = RJD_MEM_DEBUG_LINEAR_SENTINEL32, 171 | }; 172 | 173 | rjd_atomic_uint64_set(&allocator.stats.total_size, usable_size); 174 | rjd_atomic_uint64_set(&allocator.stats.current.unused, usable_size); 175 | 176 | return allocator; 177 | } 178 | 179 | const char* rjd_mem_allocator_type(struct rjd_mem_allocator* allocator) 180 | { 181 | RJD_ASSERT(allocator); 182 | return allocator->type_func(); 183 | } 184 | 185 | bool rjd_mem_allocator_reset(struct rjd_mem_allocator* allocator) 186 | { 187 | RJD_ASSERT(allocator); 188 | 189 | rjd_atomic_uint64_set(&allocator->stats.current.used, 0); 190 | rjd_atomic_uint64_set(&allocator->stats.current.overhead, 0); 191 | rjd_atomic_uint64_set(&allocator->stats.current.peak, 0); 192 | rjd_atomic_uint64_set(&allocator->stats.current.unused, rjd_atomic_uint64_get(&allocator->stats.total_size)); 193 | rjd_atomic_uint64_set(&allocator->stats.current.allocs, 0); 194 | rjd_atomic_uint64_set(&allocator->stats.current.frees, 0); 195 | rjd_atomic_uint64_inc(&allocator->stats.lifetime.resets); 196 | 197 | if (allocator->reset_func) { 198 | allocator->reset_func(allocator->optional_heap); 199 | return true; 200 | } 201 | return false; 202 | } 203 | 204 | struct rjd_mem_allocator_stats rjd_mem_allocator_getstats(const struct rjd_mem_allocator* allocator) 205 | { 206 | RJD_ASSERT(allocator); 207 | return allocator->stats; 208 | } 209 | 210 | void* rjd_mem_alloc_impl(size_t size, struct rjd_mem_allocator* allocator, uint32_t alignment, bool clear) 211 | { 212 | RJD_ASSERT(allocator); 213 | RJD_ASSERT(alignment >= 8); 214 | 215 | if (size == 0) { 216 | size = 8; 217 | } 218 | 219 | const uint32_t header_size = sizeof(struct rjd_mem_allocation_header); 220 | const uint32_t alignment_padding = alignment * 2; 221 | const uint32_t total_size = (uint32_t)size + header_size + alignment_padding; 222 | 223 | char* raw = allocator->alloc_func(total_size, allocator->optional_heap); 224 | if (raw == NULL) { 225 | return raw; 226 | } 227 | 228 | if (clear) { 229 | memset(raw, 0, total_size); 230 | } 231 | 232 | uintptr_t aligned_user = RJD_MEM_ALIGN((uintptr_t)raw + alignment + header_size, alignment); 233 | 234 | const ptrdiff_t offset_to_block_begin_from_user = aligned_user - (uintptr_t)raw; 235 | RJD_ASSERT(offset_to_block_begin_from_user < UINT16_MAX); 236 | 237 | struct rjd_mem_allocation_header* header = (void*)(aligned_user - header_size); 238 | header->allocator = allocator; 239 | header->total_blocksize = (uint32_t)total_size; 240 | header->offset_to_block_begin_from_user = (uint16_t)offset_to_block_begin_from_user; 241 | header->debug_sentinel = allocator->debug_sentinel; 242 | 243 | // stats tracking 244 | { 245 | rjd_atomic_uint64_add(&allocator->stats.current.used, total_size); 246 | rjd_atomic_uint64_add(&allocator->stats.current.overhead, total_size - size); 247 | 248 | while (true) { 249 | uint64_t old_peak = rjd_atomic_uint64_get(&allocator->stats.current.peak); 250 | uint64_t new_peak = rjd_atomic_uint64_get(&allocator->stats.current.used); 251 | 252 | if (new_peak <= old_peak || 253 | rjd_atomic_uint64_compare_exchange(&allocator->stats.current.peak, &old_peak, new_peak)) { 254 | break; 255 | } 256 | } 257 | 258 | while (true) { 259 | uint64_t old_peak = rjd_atomic_uint64_get(&allocator->stats.lifetime.peak); 260 | uint64_t new_peak = rjd_atomic_uint64_get(&allocator->stats.current.used); 261 | 262 | if (new_peak <= old_peak || 263 | rjd_atomic_uint64_compare_exchange(&allocator->stats.lifetime.peak, &old_peak, new_peak)) { 264 | break; 265 | } 266 | } 267 | 268 | uint64_t unused = rjd_atomic_uint64_get(&allocator->stats.current.unused); 269 | if (unused != RJD_MEM_STATS_UNKNOWN_UPPERBOUND) { 270 | RJD_ASSERT(unused >= total_size); 271 | rjd_atomic_uint64_sub(&allocator->stats.current.unused, total_size); 272 | } 273 | 274 | rjd_atomic_uint64_inc(&allocator->stats.current.allocs); 275 | rjd_atomic_uint64_inc(&allocator->stats.lifetime.allocs); 276 | } 277 | 278 | return (void*)aligned_user; 279 | } 280 | 281 | void rjd_mem_free(const void* mem) 282 | { 283 | if (!mem) { 284 | return; 285 | } 286 | 287 | char* raw = (void*)mem; 288 | struct rjd_mem_allocation_header* header = (void*)(raw - sizeof(struct rjd_mem_allocation_header)); 289 | struct rjd_mem_allocator* allocator = header->allocator; 290 | 291 | RJD_ASSERTMSG(header->debug_sentinel == allocator->debug_sentinel, "This memory was not allocated with rjd_mem_alloc."); 292 | 293 | if (allocator->free_func) { 294 | { 295 | uint64_t used = rjd_atomic_uint64_get(&allocator->stats.current.used); 296 | RJD_ASSERTMSG(used >= header->total_blocksize, "Allocator used (%llu) must be >= block size (%llu)", used, header->total_blocksize); 297 | } 298 | rjd_atomic_uint64_sub(&allocator->stats.current.used, header->total_blocksize); 299 | 300 | if (rjd_atomic_uint64_get(&allocator->stats.current.unused) != RJD_MEM_STATS_UNKNOWN_UPPERBOUND) { 301 | rjd_atomic_uint64_add(&allocator->stats.current.unused, header->total_blocksize); 302 | } 303 | rjd_atomic_uint64_inc(&allocator->stats.current.frees); 304 | rjd_atomic_uint64_inc(&allocator->stats.lifetime.frees); 305 | 306 | char* begin = raw - header->offset_to_block_begin_from_user; 307 | allocator->free_func(begin); 308 | } 309 | } 310 | 311 | void rjd_mem_swap(void* restrict mem1, void* restrict mem2, size_t size) 312 | { 313 | uint8_t tmp[1024]; 314 | RJD_ASSERTMSG(size < (uint32_t)sizeof(tmp), "Increase size of static buffer to at least %u", size); 315 | 316 | memcpy(tmp, mem1, size); 317 | memcpy(mem1, mem2, size); 318 | memcpy(mem2, tmp, size); 319 | } 320 | 321 | //////////////////////////////////////////////////////////////////////////////// 322 | // local helper definitions 323 | 324 | const char* rjd_mem_allocator_global_type(void) 325 | { 326 | return "rjd_global"; 327 | } 328 | 329 | void* rjd_mem_allocator_global_alloc(size_t size, void* unused_heap) 330 | { 331 | RJD_UNUSED_PARAM(unused_heap); 332 | 333 | return malloc(size); 334 | } 335 | 336 | void rjd_mem_allocator_global_free(void* mem) 337 | { 338 | free(mem); 339 | } 340 | 341 | const char* rjd_mem_allocator_linear_type(void) 342 | { 343 | return "rjd_linear"; 344 | } 345 | 346 | void* rjd_mem_allocator_linear_alloc(size_t size, void* optional_heap) 347 | { 348 | struct rjd_mem_heap_linear* heap = (struct rjd_mem_heap_linear*)optional_heap; 349 | RJD_ASSERT(heap->debug_sentinel == RJD_MEM_DEBUG_LINEAR_SENTINEL32); 350 | 351 | size_t align_diff = size % 8; 352 | if (align_diff != 0) { 353 | size += align_diff; 354 | } 355 | 356 | if ((char*)heap->next + size <= (char*)heap->base + heap->size) 357 | { 358 | void* mem = (char*)heap->next; 359 | heap->next = (char*)heap->next + size; 360 | 361 | return mem; 362 | } 363 | 364 | return NULL; 365 | } 366 | 367 | void rjd_mem_allocator_linear_reset(void* optional_heap) 368 | { 369 | struct rjd_mem_heap_linear* heap = (struct rjd_mem_heap_linear*)optional_heap; 370 | RJD_ASSERT(heap->debug_sentinel == RJD_MEM_DEBUG_LINEAR_SENTINEL32); 371 | 372 | heap->next = heap->base; 373 | } 374 | 375 | #endif // RJD_IMPL 376 | 377 | -------------------------------------------------------------------------------- /rjd_path.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define RJD_PATH_H 1 4 | 5 | #ifndef RJD_PATH_BUFFER_LENGTH 6 | #define RJD_PATH_BUFFER_LENGTH 256 7 | #endif 8 | 9 | struct rjd_path 10 | { 11 | int32_t length; 12 | char str[RJD_PATH_BUFFER_LENGTH]; 13 | }; 14 | 15 | struct rjd_path_enumerator_state 16 | { 17 | char impl[40]; 18 | }; 19 | 20 | enum RJD_PATH_ENUMERATE_MODE 21 | { 22 | RJD_PATH_ENUMERATE_MODE_RECURSIVE, 23 | RJD_PATH_ENUMERATE_MODE_FLAT, 24 | }; 25 | 26 | struct rjd_path rjd_path_init(void); 27 | struct rjd_path rjd_path_init_with(const char* path); 28 | void rjd_path_join(struct rjd_path* path1, const struct rjd_path* path2); 29 | void rjd_path_join_str(struct rjd_path* path, const char* str); 30 | void rjd_path_join_front(struct rjd_path* path, const char* str); 31 | void rjd_path_append(struct rjd_path* path, const char* str); 32 | void rjd_path_pop(struct rjd_path* path); 33 | void rjd_path_pop_extension(struct rjd_path* path); 34 | void rjd_path_pop_front(struct rjd_path* path); 35 | void rjd_path_pop_front_path(struct rjd_path* path, const struct rjd_path* ending); 36 | void rjd_path_pop_front_path_str(struct rjd_path* path, const char* str); 37 | const char* rjd_path_get(const struct rjd_path* path); 38 | void rjd_path_clear(struct rjd_path* path); 39 | 40 | const char* rjd_path_extension(const struct rjd_path* path); 41 | bool rjd_path_endswith(const struct rjd_path* path, const char* str); 42 | 43 | const char* rjd_path_str_extension(const char* path); 44 | bool rjd_path_str_endswith(const char* path, const char* str); 45 | 46 | struct rjd_path_enumerator_state rjd_path_enumerate_create(const char* path, struct rjd_mem_allocator* allocator, enum RJD_PATH_ENUMERATE_MODE mode); 47 | const char* rjd_path_enumerate_next(struct rjd_path_enumerator_state* state); 48 | void rjd_path_enumerate_destroy(struct rjd_path_enumerator_state* state); 49 | 50 | #if RJD_IMPL 51 | 52 | #if RJD_PLATFORM_WINDOWS 53 | #define RJD_PATH_SLASH ('/') 54 | #elif RJD_PLATFORM_OSX 55 | #define RJD_PATH_SLASH ('/') 56 | #else 57 | #error "Unknown platform" 58 | #endif 59 | 60 | static uint32_t rjd_path_normalize_slashes(char* path, uint32_t length); 61 | 62 | struct rjd_path rjd_path_init() 63 | { 64 | struct rjd_path path = {0}; 65 | return path; 66 | } 67 | 68 | struct rjd_path rjd_path_init_with(const char* initial_contents) 69 | { 70 | struct rjd_path path; 71 | path.length = (uint32_t)strlen(initial_contents); 72 | RJD_ASSERTMSG(path.length < RJD_PATH_BUFFER_LENGTH, 73 | "The static size of RJD_PATH_BUFFER_LENGTH (%u) is smaller than the passed string (%u).", 74 | RJD_PATH_BUFFER_LENGTH, path.length); 75 | 76 | strncpy(path.str, initial_contents, path.length); 77 | path.str[path.length] = 0; 78 | path.length = rjd_path_normalize_slashes(path.str, path.length); 79 | return path; 80 | } 81 | 82 | void rjd_path_join(struct rjd_path* path1, const struct rjd_path* path2) 83 | { 84 | rjd_path_join_str(path1, path2->str); 85 | } 86 | 87 | void rjd_path_join_str(struct rjd_path* path, const char* str) 88 | { 89 | RJD_ASSERT(path); 90 | RJD_ASSERT(str); 91 | 92 | const char slash = RJD_PATH_SLASH; 93 | 94 | uint32_t start = path->length; 95 | uint32_t max_length = RJD_PATH_BUFFER_LENGTH - 1; 96 | if (start > 0 && start < max_length && path->str[start - 1] != slash && str[0] != slash) { 97 | path->str[start] = slash; 98 | ++start; 99 | ++path->length; 100 | } 101 | 102 | size_t append_length = strlen(str); 103 | size_t new_length = append_length + path->length; 104 | RJD_ASSERTMSG(new_length < RJD_PATH_BUFFER_LENGTH - 1, 105 | "The static size of RJD_PATH_BUFFER_LENGTH (%u) is smaller than the concatenated length (%u). Path: '%s'. Appending: '%s'", 106 | RJD_PATH_BUFFER_LENGTH, new_length, path->str, str); 107 | strncpy(path->str + path->length, str, append_length); 108 | path->str[new_length] = 0; 109 | path->length = rjd_path_normalize_slashes(path->str, (uint32_t)new_length); 110 | } 111 | 112 | void rjd_path_join_front(struct rjd_path* path, const char* str) 113 | { 114 | RJD_ASSERT(path); 115 | RJD_ASSERT(str); 116 | 117 | struct rjd_path front = rjd_path_init_with(str); 118 | int32_t new_length = front.length + path->length + 1; // +1 for joining slash 119 | 120 | RJD_ASSERTMSG(new_length < RJD_PATH_BUFFER_LENGTH - 1, 121 | "The static size of RJD_PATH_BUFFER_LENGTH (%u) is smaller than the concatenated length (%u). Path: '%s'. Appending: '%s'", 122 | RJD_PATH_BUFFER_LENGTH, new_length, path->str, str); 123 | 124 | memmove(path->str + front.length + 1, path->str, path->length); 125 | memcpy(path->str, front.str, front.length); 126 | path->str[front.length] = RJD_PATH_SLASH; 127 | path->str[front.length + path->length + 1] = 0; 128 | path->length += front.length + 1; 129 | path->length = rjd_path_normalize_slashes(path->str, path->length); 130 | } 131 | 132 | void rjd_path_append(struct rjd_path* path, const char* str) 133 | { 134 | RJD_ASSERT(path); 135 | RJD_ASSERT(str); 136 | 137 | size_t length_str = strlen(str); 138 | size_t new_length = path->length + length_str; 139 | 140 | RJD_ASSERTMSG(new_length < RJD_PATH_BUFFER_LENGTH - 1, 141 | "The static size of RJD_PATH_BUFFER_LENGTH (%u) is smaller than the concatenated length (%u). Path: '%s'. Appending: '%s'", 142 | RJD_PATH_BUFFER_LENGTH, new_length, path->str, str); 143 | 144 | strncpy(path->str + path->length, str, length_str); 145 | path->length = (int32_t)new_length; 146 | path->str[new_length] = 0; 147 | } 148 | 149 | void rjd_path_pop(struct rjd_path* path) 150 | { 151 | RJD_ASSERT(path); 152 | 153 | for (int32_t i = (int32_t)path->length - 1; i >= 0; --i) { 154 | if (path->str[i] == RJD_PATH_SLASH && i < path->length - 1) { 155 | 156 | path->length = i; 157 | path->str[i] = 0; 158 | return; 159 | } 160 | } 161 | 162 | path->length = 0; 163 | path->str[0] = 0; 164 | } 165 | 166 | void rjd_path_pop_extension(struct rjd_path* path) 167 | { 168 | RJD_ASSERT(path); 169 | 170 | for (int32_t i = path->length - 1; i >= 0; --i) { 171 | if (path->str[i] == '.') { 172 | path->length = i; 173 | path->str[i] = 0; 174 | break; 175 | } 176 | 177 | if (path->str[i] == RJD_PATH_SLASH) { 178 | break; 179 | } 180 | } 181 | } 182 | 183 | void rjd_path_pop_front(struct rjd_path* path) 184 | { 185 | struct rjd_path copy = *path; 186 | for (int32_t i = 1; i < path->length; ++i) { 187 | if (path->str[i] == RJD_PATH_SLASH) { 188 | int32_t new_length = path->length - i; 189 | memcpy(path->str, copy.str + i, new_length); 190 | path->length = new_length; 191 | path->str[new_length] = 0; 192 | return; 193 | } 194 | } 195 | 196 | path->length = 0; 197 | path->str[0] = 0; 198 | } 199 | 200 | void rjd_path_pop_front_path_str_impl(struct rjd_path* path, const char* front, int32_t front_length) 201 | { 202 | if (path->length < front_length) { 203 | return; 204 | } 205 | 206 | if (!strncmp(path->str, front, front_length)) { 207 | path->length -= front_length; 208 | memmove(path->str, path->str + front_length, path->length); 209 | path->str[path->length] = 0; 210 | } 211 | } 212 | 213 | void rjd_path_pop_front_path(struct rjd_path* path, const struct rjd_path* front) 214 | { 215 | rjd_path_pop_front_path_str_impl(path, front->str, front->length); 216 | } 217 | 218 | void rjd_path_pop_front_path_str(struct rjd_path* path, const char* str) 219 | { 220 | rjd_path_pop_front_path_str_impl(path, str, (int32_t)strlen(str)); 221 | } 222 | 223 | const char* rjd_path_get(const struct rjd_path* path) 224 | { 225 | return path->str; 226 | } 227 | 228 | const char* rjd_path_extension(const struct rjd_path* path) 229 | { 230 | return rjd_path_str_extension(path->str); 231 | } 232 | 233 | bool rjd_path_endswith(const struct rjd_path* path, const char* str) 234 | { 235 | return rjd_path_str_endswith(path->str, str); 236 | } 237 | 238 | const char* rjd_path_str_extension(const char* path) 239 | { 240 | if (!path || !*path) { 241 | return NULL; 242 | } 243 | 244 | const char* extension = strrchr(path, '.'); 245 | if (!extension) { 246 | return NULL; 247 | } 248 | 249 | // If a path ends with a period, it doesn't have an extension 250 | if (extension[1] == '\0') { 251 | return NULL; 252 | } 253 | 254 | return extension; 255 | } 256 | 257 | bool rjd_path_str_endswith(const char* path, const char* str) 258 | { 259 | if (path == NULL || str == NULL) { 260 | return false; 261 | } 262 | 263 | if (*str == 0) { 264 | return true; 265 | } 266 | 267 | size_t length_string = strlen(path); 268 | size_t length_end = strlen(str); 269 | 270 | if (length_end > length_string) { 271 | return false; 272 | } 273 | 274 | const char* path_end = path + (length_string - length_end); 275 | return !strcmp(path_end, str); 276 | } 277 | 278 | void rjd_path_clear(struct rjd_path* path) 279 | { 280 | RJD_ASSERT(path); 281 | path->str[0] = '\0'; 282 | } 283 | 284 | // local helpers 285 | static uint32_t rjd_path_normalize_slashes(char* path, uint32_t length) 286 | { 287 | if (length == 0) { 288 | return length; 289 | } 290 | 291 | for (int32_t i = length - 1; i > 0 && path[i] == RJD_PATH_SLASH; --i) { 292 | path[i] = '\0'; 293 | --length; 294 | } 295 | 296 | char* end = path + length; 297 | for (char* start = end - 1; start > path; --start) 298 | { 299 | while (*start != RJD_PATH_SLASH && start > path) { 300 | --start; 301 | } 302 | 303 | char* slot = start; 304 | while (slot > path && *(slot - 1) == RJD_PATH_SLASH) { 305 | --slot; 306 | } 307 | 308 | if (start - slot > 0) { 309 | uint32_t sublength = (uint32_t)(end - start); 310 | memmove(slot, start, sublength + 1); 311 | 312 | uint32_t trimmed_size = (uint32_t)(start - slot); 313 | end -= trimmed_size; 314 | } 315 | start = slot; 316 | } 317 | 318 | size_t newlength = end - path; 319 | RJD_ASSERTMSG(newlength == strlen(path), "newlength (%d) != strlen(path) ('%s') (%d)", 320 | newlength, path, strlen(path)); 321 | return (uint32_t)(end - path); 322 | } 323 | 324 | #if RJD_PLATFORM_WINDOWS 325 | 326 | #include 327 | #include 328 | #include // INT_MAX 329 | 330 | struct rjd_path_enumerator_state_win32 331 | { 332 | struct rjd_mem_allocator* allocator; 333 | char* nextpath; 334 | wchar_t** root_dirs; 335 | HANDLE handle; 336 | bool is_recursive; 337 | }; 338 | RJD_STATIC_ASSERT(sizeof(struct rjd_path_enumerator_state_win32) <= sizeof(struct rjd_path_enumerator_state)); 339 | 340 | struct rjd_path_enumerator_state rjd_path_enumerate_create(const char* path, struct rjd_mem_allocator* allocator, enum RJD_PATH_ENUMERATE_MODE mode) 341 | { 342 | RJD_ASSERT(path); 343 | RJD_ASSERT(allocator); 344 | 345 | struct rjd_path_enumerator_state state = {0}; 346 | struct rjd_path_enumerator_state_win32* state_win32 = (struct rjd_path_enumerator_state_win32*)&state; 347 | state_win32->allocator = allocator; 348 | state_win32->nextpath = NULL; 349 | state_win32->root_dirs = rjd_array_alloc(wchar_t*, 16, allocator); 350 | state_win32->handle = INVALID_HANDLE_VALUE; 351 | state_win32->is_recursive = (RJD_PATH_ENUMERATE_MODE_RECURSIVE == mode); 352 | 353 | wchar_t* path_wide = NULL; 354 | { 355 | const size_t path_length = mbstowcs(NULL, path, INT_MAX); 356 | path_wide = rjd_mem_alloc_array_noclear(wchar_t, path_length + 1, allocator); 357 | mbstowcs(path_wide, path, INT_MAX); 358 | } 359 | rjd_array_push(state_win32->root_dirs, path_wide); 360 | 361 | return state; 362 | } 363 | 364 | static wchar_t* rjd_path_enumerate_concat_paths(const wchar_t* a, const wchar_t* b, struct rjd_mem_allocator* allocator) 365 | { 366 | const wchar_t path_separator[] = L"/"; 367 | 368 | size_t length_a = wcslen(a); 369 | size_t length_b = wcslen(b); 370 | size_t length_separator = wcslen(path_separator); 371 | 372 | size_t length_total = length_a + length_separator + length_b; 373 | 374 | wchar_t* concat = rjd_mem_alloc_array_noclear(wchar_t, length_total + 1, allocator); 375 | wcscpy(concat, a); 376 | wcscpy(concat + length_a, path_separator); 377 | wcscpy(concat + length_a + length_separator, b); 378 | return concat; 379 | } 380 | 381 | const char* rjd_path_enumerate_next(struct rjd_path_enumerator_state* state) 382 | { 383 | RJD_ASSERT(state); 384 | 385 | struct rjd_path_enumerator_state_win32* state_win32 = (struct rjd_path_enumerator_state_win32*)state; 386 | 387 | if (state_win32->nextpath) { 388 | rjd_mem_free(state_win32->nextpath); 389 | state_win32->nextpath = NULL; 390 | } 391 | 392 | WIN32_FIND_DATAW find_data = {0}; 393 | while (!*find_data.cFileName && rjd_array_count(state_win32->root_dirs) > 0) { 394 | 395 | if (state_win32->handle == INVALID_HANDLE_VALUE) { 396 | wchar_t* root_with_search_spec = rjd_path_enumerate_concat_paths(state_win32->root_dirs[0], L"*", state_win32->allocator); 397 | state_win32->handle = FindFirstFileW(root_with_search_spec, &find_data); 398 | rjd_mem_free(root_with_search_spec); 399 | 400 | if (state_win32->handle == INVALID_HANDLE_VALUE) { 401 | find_data.cFileName[0] = '\0'; 402 | rjd_mem_free(state_win32->root_dirs[0]); 403 | rjd_array_erase_unordered(state_win32->root_dirs, 0); 404 | } 405 | } 406 | 407 | if (state_win32->handle != INVALID_HANDLE_VALUE) { 408 | bool success = true; 409 | if (!*find_data.cFileName) { 410 | success = FindNextFileW(state_win32->handle, &find_data); 411 | } 412 | 413 | while (success && (!wcscmp(find_data.cFileName, L".") || !wcscmp(find_data.cFileName, L".."))) { 414 | success = FindNextFileW(state_win32->handle, &find_data); 415 | } 416 | 417 | if (!success) { 418 | find_data.cFileName[0] = '\0'; 419 | state_win32->handle = INVALID_HANDLE_VALUE; 420 | rjd_mem_free(state_win32->root_dirs[0]); 421 | rjd_array_erase_unordered(state_win32->root_dirs, 0); 422 | } 423 | } 424 | 425 | if (*find_data.cFileName && state_win32->is_recursive) { 426 | if (find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { 427 | wchar_t* new_root = rjd_path_enumerate_concat_paths(state_win32->root_dirs[0], find_data.cFileName, state_win32->allocator); 428 | rjd_array_push(state_win32->root_dirs, new_root); 429 | } 430 | } 431 | } 432 | 433 | if (*find_data.cFileName) 434 | { 435 | wchar_t* path = rjd_path_enumerate_concat_paths(state_win32->root_dirs[0], find_data.cFileName, state_win32->allocator); 436 | 437 | const size_t path_length = wcstombs(NULL, path, INT_MAX); 438 | state_win32->nextpath = rjd_mem_alloc_array_noclear(char, path_length + 1, state_win32->allocator); 439 | wcstombs(state_win32->nextpath, path, INT_MAX); 440 | 441 | rjd_mem_free(path); 442 | } 443 | 444 | return state_win32->nextpath; 445 | } 446 | 447 | void rjd_path_enumerate_destroy(struct rjd_path_enumerator_state* state) 448 | { 449 | RJD_ASSERT(state); 450 | 451 | struct rjd_path_enumerator_state_win32* state_win32 = (struct rjd_path_enumerator_state_win32*)state; 452 | 453 | for (uint32_t i = 0; i < rjd_array_count(state_win32->root_dirs); ++i) { 454 | rjd_mem_free(state_win32->root_dirs + i); 455 | } 456 | rjd_array_free(state_win32->root_dirs); 457 | rjd_mem_free(state_win32->nextpath); 458 | 459 | FindClose(state_win32->handle); 460 | } 461 | 462 | #elif RJD_PLATFORM_OSX 463 | 464 | #if !RJD_LANG_OBJC 465 | #error "rjd_path implementation on OSX uses Objective-C interfaces. You must #include this file in a .m file." 466 | #endif 467 | 468 | #import 469 | 470 | struct rjd_path_enumerator_state_osx 471 | { 472 | NSDirectoryEnumerator* enumerator; 473 | struct rjd_mem_allocator* allocator; 474 | const char* root; 475 | const char* next; 476 | bool no_recursion; 477 | }; 478 | RJD_STATIC_ASSERT(sizeof(struct rjd_path_enumerator_state_osx) <= sizeof(struct rjd_path_enumerator_state)); 479 | 480 | struct rjd_path_enumerator_state rjd_path_enumerate_create(const char* path, struct rjd_mem_allocator* allocator, enum RJD_PATH_ENUMERATE_MODE mode) 481 | { 482 | RJD_ASSERT(path); 483 | RJD_ASSERT(allocator); 484 | 485 | NSFileManager* manager = [NSFileManager defaultManager]; 486 | NSString* startingPath = [NSString stringWithUTF8String:path]; 487 | 488 | char* path_copy = NULL; 489 | { 490 | size_t len = strlen(path); 491 | path_copy = rjd_mem_alloc_array_noclear(char, len + 1, allocator); 492 | strcpy(path_copy, path); 493 | } 494 | 495 | // NSFileManager:enumeratorAtPath is threadsafe 496 | NSDirectoryEnumerator* enumerator = [manager enumeratorAtPath:startingPath]; 497 | 498 | struct rjd_path_enumerator_state state = {0}; 499 | struct rjd_path_enumerator_state_osx* state_osx = (struct rjd_path_enumerator_state_osx*)&state; 500 | state_osx->enumerator = enumerator; 501 | state_osx->allocator = allocator; 502 | state_osx->root = path_copy; 503 | state_osx->next = NULL; 504 | state_osx->no_recursion = (mode == RJD_PATH_ENUMERATE_MODE_FLAT); 505 | 506 | return state; 507 | } 508 | 509 | const char* rjd_path_enumerate_next(struct rjd_path_enumerator_state* state) 510 | { 511 | RJD_ASSERT(state); 512 | struct rjd_path_enumerator_state_osx* state_osx = (struct rjd_path_enumerator_state_osx*)state; 513 | 514 | rjd_mem_free(state_osx->next); 515 | state_osx->next = NULL; 516 | 517 | if (state_osx->no_recursion) { 518 | [state_osx->enumerator skipDescendants]; 519 | } 520 | 521 | NSString* next = (NSString*) [state_osx->enumerator nextObject]; 522 | if (next) { 523 | const char* relative = next.UTF8String; 524 | const char path_separator[] = "/"; 525 | 526 | size_t length_root = strlen(state_osx->root); 527 | size_t length_relative = strlen(relative); 528 | size_t length_separator = strlen(path_separator); 529 | 530 | size_t length_total = length_root + length_separator + length_relative; 531 | 532 | char* next_path = rjd_mem_alloc_array_noclear(char, length_total + 1, state_osx->allocator); 533 | strcpy(next_path, state_osx->root); 534 | strcpy(next_path + length_root, path_separator); 535 | strcpy(next_path + length_root + length_separator, relative); 536 | state_osx->next = next_path; 537 | } 538 | 539 | return state_osx->next; 540 | } 541 | 542 | void rjd_path_enumerate_destroy(struct rjd_path_enumerator_state* state) 543 | { 544 | RJD_ASSERT(state); 545 | 546 | struct rjd_path_enumerator_state_osx* state_osx = (struct rjd_path_enumerator_state_osx*)state; 547 | state_osx->enumerator = nil; 548 | 549 | rjd_mem_free(state_osx->root); 550 | rjd_mem_free(state_osx->next); 551 | } 552 | 553 | #endif // RJD_PLATFORM_OSX && RJD_LANG_OBJC 554 | #endif // RJD_IMPL 555 | -------------------------------------------------------------------------------- /rjd_platform.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define RJD_PLATFORM_H 1 4 | 5 | // Platforms 6 | #if _WIN32 || __CYGWIN__ 7 | #define RJD_PLATFORM_WINDOWS 1 8 | #define RJD_PLATFORM_OSX 0 9 | #elif __APPLE__ && __MACH__ 10 | #define RJD_PLATFORM_OSX 1 11 | #define RJD_PLATFORM_WINDOWS 0 12 | #else 13 | #error Unknown platform. 14 | #endif 15 | 16 | // Compilers 17 | #ifdef _MSC_VER 18 | #define RJD_COMPILER_MSVC 1 19 | #elif __clang__ 20 | #define RJD_COMPILER_CLANG 1 21 | #elif __GNUC__ 22 | #define RJD_COMPILER_GCC 1 23 | #else 24 | #error Unknown compiler. 25 | #endif 26 | 27 | #if RJD_COMPILER_MSVC 28 | #define RJD_FORCE_INLINE __forceinline 29 | #define RJD_FORCE_ALIGN(alignment, type) __declspec(align(alignment)) type 30 | #define restrict __restrict 31 | #elif RJD_COMPILER_GCC || RJD_COMPILER_CLANG 32 | #define RJD_FORCE_INLINE static inline __attribute__((always_inline)) 33 | #define RJD_FORCE_ALIGN(alignment, type) type __attribute__((aligned(alignment))) 34 | #else 35 | #error Unhandled compiler 36 | #endif 37 | 38 | #if RJD_COMPILER_MSVC 39 | #if defined(_M_X86) || defined(_M_X64) 40 | #define RJD_ARCH_64 1 41 | #elif defined(_M_IX86) 42 | #define RJD_ARCH_32 1 43 | #else 44 | #error Unknown architecture 45 | #endif 46 | #elif RJD_COMPILER_GCC 47 | #if defined(__x86_64) || defined(__x86_64__) 48 | #define RJD_ARCH_64 1 49 | #elif defined(i386) || defined(__i386) || defined(__i386__) 50 | #define RJD_ARCH_32 1 51 | #else 52 | #error Unknown architecture 53 | #endif 54 | #elif RJD_COMPILER_CLANG 55 | #if defined(__x86_64) || defined(__x86_64__) 56 | #define RJD_ARCH_64 1 57 | #elif defined(i386) || defined(__i386) || defined(__i386__) 58 | #define RJD_ARCH_32 1 59 | #else 60 | #error Unknown architecture 61 | #endif 62 | #else 63 | #error Unhandled compiler 64 | #endif 65 | 66 | #if RJD_COMPILER_MSVC 67 | #define RJD_COMPILER_MSVC_ONLY(code) code 68 | #define RJD_COMPILER_GCC_ONLY(code) 69 | #define RJD_COMPILER_CLANG_ONLY(code) 70 | #elif RJD_COMPILER_GCC 71 | #define RJD_COMPILER_MSVC_ONLY(code) 72 | #define RJD_COMPILER_GCC_ONLY(code) code 73 | #define RJD_COMPILER_CLANG_ONLY(code) 74 | #elif RJD_COMPILER_CLANG 75 | #define RJD_COMPILER_MSVC_ONLY(code) 76 | #define RJD_COMPILER_GCC_ONLY(code) 77 | #define RJD_COMPILER_CLANG_ONLY(code) code 78 | #else 79 | #define RJD_COMPILER_MSVC_ONLY(code) 80 | #define RJD_COMPILER_GCC_ONLY(code) 81 | #define RJD_COMPILER_CLANG_ONLY(code) 82 | #endif 83 | 84 | #if RJD_COMPILER_MSVC 85 | #pragma warning(disable:4201) // nonstandard extension used: nameless struct/union (gcc and clang support this) 86 | #pragma warning(disable:4221) // nonstandard extension used: initializing struct with address of local variable (gcc and clang support this) 87 | 88 | #elif RJD_COMPILER_CLANG 89 | #pragma clang diagnostic ignored "-Wmissing-braces" // clang is paranoid about zero-init for nested structs 90 | #endif 91 | 92 | #if defined(__OBJC__) 93 | #define RJD_LANG_OBJC 1 94 | #else 95 | #define RJD_LANG_OBJC 0 96 | #endif 97 | 98 | #if defined(__cplusplus) 99 | #define RJD_LANG_CPP 1 100 | #else 101 | #define RJD_LANG_CPP 0 102 | #endif 103 | 104 | #if RJD_IMPL 105 | #if RJD_PLATFORM_WINDOWS 106 | #if RJD_COMPILER_MSVC 107 | #pragma warning(push) 108 | #pragma warning(disable:5105) // windows.h triggers warning C5105: macro expansion producing 'defined' has undefined behavior 109 | #endif 110 | 111 | #define WIN32_LEAN_AND_MEAN 112 | #define WIN32_EXTRA_LEAN 113 | #define NOMINMAX 114 | #include 115 | #include 116 | 117 | #if RJD_COMPILER_MSVC 118 | #pragma warning(pop) 119 | #endif 120 | #endif // RJD_PLATFORM_WINDOWS 121 | #endif // RJD_IMPL 122 | 123 | -------------------------------------------------------------------------------- /rjd_procgeo.h: -------------------------------------------------------------------------------- 1 | #define RJD_PROCGEO_H 1 2 | 3 | // dependencies: 4 | // * math.h 5 | 6 | // Functions for generating procedural geometry: 7 | // * Generates centered at 0,0 scaled by input params. 8 | // * All functions write 3 floats per vertex, and skip stride number of floats. Pass 0 stride for tightly packed arrays. 9 | // * Returns NULL if there isn't enough space in the float array to generate the geometry. Otherwise, returns the 10 | // pointer to one past the last element. 11 | // * Use *_calc_num_verts() functions to find how many vertices you need. 12 | // * Vertices are generated in clockwise winding order, assuming view is looking -Z 13 | 14 | enum rjd_procgeo_type 15 | { 16 | RJD_PROCGEO_TYPE_RECT, 17 | RJD_PROCGEO_TYPE_CIRCLE, 18 | RJD_PROCGEO_TYPE_BOX, 19 | RJD_PROCGEO_TYPE_CONE, 20 | RJD_PROCGEO_TYPE_CYLINDER, 21 | RJD_PROCGEO_TYPE_SPHERE, 22 | RJD_PROCGEO_TYPE_COUNT, 23 | }; 24 | 25 | uint32_t rjd_procgeo_calc_num_verts(enum rjd_procgeo_type type, uint32_t tesselation); 26 | float* rjd_procgeo(enum rjd_procgeo_type type, uint32_t tesselation, float size_x, float size_y, float size_z, float* out, uint32_t length, uint32_t stride); 27 | 28 | uint32_t rjd_procgeo_rect_calc_num_verts(void); 29 | uint32_t rjd_procgeo_circle_calc_num_verts(uint32_t tesselation); 30 | uint32_t rjd_procgeo_box_calc_num_verts(void); 31 | uint32_t rjd_procgeo_cone_calc_num_verts(uint32_t tesselation); 32 | uint32_t rjd_procgeo_cylinder_calc_num_verts(uint32_t tesselation); 33 | uint32_t rjd_procgeo_sphere_calc_num_verts(uint32_t tesselation); 34 | 35 | float* rjd_procgeo_rect(float width, float height, float* out, uint32_t length, uint32_t stride); 36 | float* rjd_procgeo_circle(float radius, uint32_t tesselation, float* out, uint32_t length, uint32_t stride); 37 | float* rjd_procgeo_box(float width, float height, float depth, float* out, uint32_t length, uint32_t stride); 38 | float* rjd_procgeo_cone(float radius, float height, uint32_t tesselation, float* out, uint32_t length, uint32_t stride); 39 | float* rjd_procgeo_cylinder(float radius, float height, uint32_t tesselation, float* out, uint32_t length, uint32_t stride); 40 | float* rjd_procgeo_sphere(float radius, uint32_t tesselation, float* out, uint32_t length, uint32_t stride); 41 | 42 | //////////////////////////////////////////////////////////////////////////////// 43 | // inline implementation 44 | 45 | #if RJD_IMPL 46 | 47 | const float RJD_PROCGEO_PI = 3.141592653589793238462643f; 48 | const uint32_t RJD_PROCGEO_MIN_TESSELATION_CIRCLE = 3; 49 | const uint32_t RJD_PROCGEO_MIN_TESSELATION_SPHERE = 3; 50 | const uint32_t RJD_PROCGEO_FLOATS_PER_TRI = 3; 51 | 52 | uint32_t rjd_procgeo_calc_num_verts(enum rjd_procgeo_type type, uint32_t tesselation) 53 | { 54 | switch (type) 55 | { 56 | case RJD_PROCGEO_TYPE_RECT: return rjd_procgeo_rect_calc_num_verts(); 57 | case RJD_PROCGEO_TYPE_CIRCLE: return rjd_procgeo_circle_calc_num_verts(tesselation); 58 | case RJD_PROCGEO_TYPE_BOX: return rjd_procgeo_box_calc_num_verts(); 59 | case RJD_PROCGEO_TYPE_CONE: return rjd_procgeo_cone_calc_num_verts(tesselation); 60 | case RJD_PROCGEO_TYPE_CYLINDER: return rjd_procgeo_cylinder_calc_num_verts(tesselation); 61 | case RJD_PROCGEO_TYPE_SPHERE: return rjd_procgeo_sphere_calc_num_verts(tesselation); 62 | default: break; 63 | } 64 | RJD_ASSERTFAIL("Unknown type: %d", type); 65 | return 0; 66 | } 67 | 68 | float* rjd_procgeo(enum rjd_procgeo_type type, uint32_t tesselation, float size_x, float size_y, float size_z, float* out, uint32_t length, uint32_t stride) 69 | { 70 | switch (type) 71 | { 72 | case RJD_PROCGEO_TYPE_RECT: return rjd_procgeo_rect(size_x, size_y, out, length, stride); 73 | case RJD_PROCGEO_TYPE_CIRCLE: return rjd_procgeo_circle(size_x, tesselation, out, length, stride); 74 | case RJD_PROCGEO_TYPE_BOX: return rjd_procgeo_box(size_x, size_y, size_z, out, length, stride); 75 | case RJD_PROCGEO_TYPE_CONE: return rjd_procgeo_cone(size_x, size_y, tesselation, out, length, stride); 76 | case RJD_PROCGEO_TYPE_CYLINDER: return rjd_procgeo_cylinder(size_x, size_y, tesselation, out, length, stride); 77 | case RJD_PROCGEO_TYPE_SPHERE: return rjd_procgeo_sphere(size_x, tesselation, out, length, stride); 78 | default: break; 79 | } 80 | 81 | RJD_ASSERTFAIL("Unknown type: %d", type); 82 | return out; 83 | } 84 | 85 | uint32_t rjd_procgeo_rect_calc_num_verts() { 86 | return RJD_PROCGEO_FLOATS_PER_TRI * 2; 87 | } 88 | 89 | uint32_t rjd_procgeo_circle_calc_num_verts(uint32_t tesselation) { 90 | uint32_t final_tesselation = RJD_PROCGEO_MIN_TESSELATION_CIRCLE + tesselation; 91 | return RJD_PROCGEO_FLOATS_PER_TRI * final_tesselation; 92 | } 93 | 94 | uint32_t rjd_procgeo_box_calc_num_verts() { 95 | return RJD_PROCGEO_FLOATS_PER_TRI * 2 * 6; 96 | } 97 | 98 | uint32_t rjd_procgeo_cone_calc_num_verts(uint32_t tesselation) { 99 | return 2 * rjd_procgeo_circle_calc_num_verts(tesselation); 100 | } 101 | 102 | uint32_t rjd_procgeo_cylinder_calc_num_verts(uint32_t tesselation) 103 | { 104 | uint32_t circle_verts = rjd_procgeo_circle_calc_num_verts(tesselation); 105 | uint32_t final_tesselation = RJD_PROCGEO_MIN_TESSELATION_CIRCLE + tesselation; 106 | uint32_t quad_verts = RJD_PROCGEO_FLOATS_PER_TRI * 2 * final_tesselation; 107 | return quad_verts + (circle_verts * 2); 108 | } 109 | 110 | uint32_t rjd_procgeo_sphere_calc_num_verts(uint32_t tesselation) { 111 | uint32_t final_tesselation = RJD_PROCGEO_MIN_TESSELATION_SPHERE + tesselation; 112 | uint32_t tri_verts = RJD_PROCGEO_FLOATS_PER_TRI; 113 | uint32_t quad_verts = tri_verts * 2; 114 | 115 | return quad_verts * final_tesselation * final_tesselation - tri_verts * final_tesselation * 2; 116 | } 117 | 118 | float* rjd_procgeo_rect(float width, float height, float* out, uint32_t length, uint32_t stride) 119 | { 120 | const uint32_t floats_per_vert = RJD_PROCGEO_FLOATS_PER_TRI + (uint32_t)stride; 121 | const uint32_t num_verts = rjd_procgeo_rect_calc_num_verts(); 122 | if (length < num_verts * floats_per_vert) { 123 | return NULL; 124 | } 125 | 126 | const float x = width / 2.0f; 127 | const float y = height / 2.0f; 128 | 129 | int32_t i = 0; 130 | 131 | out[i++] = -x; out[i++] = -y; out[i++] = 0.0f; i += stride; 132 | out[i++] = x; out[i++] = y; out[i++] = 0.0f; i += stride; 133 | out[i++] = -x; out[i++] = y; out[i++] = 0.0f; i += stride; 134 | 135 | out[i++] = -x; out[i++] = -y; out[i++] = 0.0f; i += stride; 136 | out[i++] = x; out[i++] = -y; out[i++] = 0.0f; i += stride; 137 | out[i++] = x; out[i++] = y; out[i++] = 0.0f; i += stride; 138 | 139 | return out + i; 140 | } 141 | 142 | float* rjd_procgeo_circle(float radius, uint32_t tesselation, float* out, uint32_t length, uint32_t stride) 143 | { 144 | const uint32_t floats_per_vert = RJD_PROCGEO_FLOATS_PER_TRI + stride; 145 | const uint32_t num_verts = rjd_procgeo_circle_calc_num_verts(tesselation); 146 | if (length < num_verts * floats_per_vert) { 147 | return NULL; 148 | } 149 | 150 | const uint32_t final_tesselation = tesselation + RJD_PROCGEO_MIN_TESSELATION_CIRCLE; 151 | const float arc_radians = RJD_PROCGEO_PI * 2 / final_tesselation; 152 | 153 | for (uint32_t i = 0, arc_segment = 0; i < num_verts * floats_per_vert; ++arc_segment) { 154 | float p1_radians = arc_radians * arc_segment; 155 | float p2_radians = arc_radians * (arc_segment + 1); 156 | 157 | out[i++] = 0; out[i++] = 0; out[i++] = 0; i += stride; 158 | out[i++] = cosf(p1_radians) * radius; out[i++] = sinf(p1_radians) * radius; out[i++] = 0; i += stride; 159 | out[i++] = cosf(p2_radians) * radius; out[i++] = sinf(p2_radians) * radius; out[i++] = 0; i += stride; 160 | } 161 | 162 | return out + num_verts * floats_per_vert; 163 | } 164 | 165 | float* rjd_procgeo_box(float width, float height, float depth, float* out, uint32_t length, uint32_t stride) 166 | { 167 | const uint32_t floats_per_vert = RJD_PROCGEO_FLOATS_PER_TRI + stride; 168 | const uint32_t num_verts = rjd_procgeo_box_calc_num_verts(); 169 | if (length < num_verts * floats_per_vert) { 170 | return NULL; 171 | } 172 | 173 | const float x = width / 2; 174 | const float y = height / 2; 175 | const float z = depth / 2; 176 | 177 | int i = 0; 178 | 179 | // front 180 | out[i++] = -x; out[i++] = y; out[i++] = z; i += stride; 181 | out[i++] = -x; out[i++] = -y; out[i++] = z; i += stride; 182 | out[i++] = x; out[i++] = -y; out[i++] = z; i += stride; 183 | out[i++] = -x; out[i++] = y; out[i++] = z; i += stride; 184 | out[i++] = x; out[i++] = -y; out[i++] = z; i += stride; 185 | out[i++] = x; out[i++] = y; out[i++] = z; i += stride; 186 | 187 | // back 188 | out[i++] = -x; out[i++] = y; out[i++] = -z; i += stride; 189 | out[i++] = x; out[i++] = -y; out[i++] = -z; i += stride; 190 | out[i++] = -x; out[i++] = -y; out[i++] = -z; i += stride; 191 | out[i++] = -x; out[i++] = y; out[i++] = -z; i += stride; 192 | out[i++] = x; out[i++] = y; out[i++] = -z; i += stride; 193 | out[i++] = x; out[i++] = -y; out[i++] = -z; i += stride; 194 | 195 | // left 196 | out[i++] = -x; out[i++] = -y; out[i++] = -z; i += stride; 197 | out[i++] = -x; out[i++] = -y; out[i++] = z; i += stride; 198 | out[i++] = -x; out[i++] = y; out[i++] = z; i += stride; 199 | out[i++] = -x; out[i++] = -y; out[i++] = -z; i += stride; 200 | out[i++] = -x; out[i++] = y; out[i++] = z; i += stride; 201 | out[i++] = -x; out[i++] = y; out[i++] = -z; i += stride; 202 | 203 | // right 204 | out[i++] = x; out[i++] = -y; out[i++] = -z; i += stride; 205 | out[i++] = x; out[i++] = y; out[i++] = -z; i += stride; 206 | out[i++] = x; out[i++] = y; out[i++] = z; i += stride; 207 | out[i++] = x; out[i++] = -y; out[i++] = -z; i += stride; 208 | out[i++] = x; out[i++] = y; out[i++] = z; i += stride; 209 | out[i++] = x; out[i++] = -y; out[i++] = z; i += stride; 210 | 211 | // top 212 | out[i++] = x; out[i++] = y; out[i++] = -z; i += stride; 213 | out[i++] = -x; out[i++] = y; out[i++] = -z; i += stride; 214 | out[i++] = -x; out[i++] = y; out[i++] = z; i += stride; 215 | out[i++] = x; out[i++] = y; out[i++] = -z; i += stride; 216 | out[i++] = -x; out[i++] = y; out[i++] = z; i += stride; 217 | out[i++] = x; out[i++] = y; out[i++] = z; i += stride; 218 | 219 | // bottom 220 | out[i++] = -x; out[i++] = -y; out[i++] = z; i += stride; 221 | out[i++] = -x; out[i++] = -y; out[i++] = -z; i += stride; 222 | out[i++] = x; out[i++] = -y; out[i++] = -z; i += stride; 223 | out[i++] = -x; out[i++] = -y; out[i++] = z; i += stride; 224 | out[i++] = x; out[i++] = -y; out[i++] = -z; i += stride; 225 | out[i++] = x; out[i++] = -y; out[i++] = z; i += stride; 226 | 227 | return out + i; 228 | } 229 | 230 | float* rjd_procgeo_cone(float height, float radius, uint32_t tesselation, float* out, uint32_t length, uint32_t stride) 231 | { 232 | const uint32_t floats_per_vert = RJD_PROCGEO_FLOATS_PER_TRI + stride; 233 | const uint32_t num_verts = rjd_procgeo_cone_calc_num_verts(tesselation); 234 | if (length < num_verts * floats_per_vert) { 235 | return NULL; 236 | } 237 | 238 | const uint32_t final_tesselation = tesselation + RJD_PROCGEO_MIN_TESSELATION_CIRCLE; 239 | const float arc_radians = RJD_PROCGEO_PI * 2 / final_tesselation; 240 | 241 | const uint32_t top_begin_offset = (num_verts / 2) * floats_per_vert; 242 | const float cone_y = height / 2; 243 | 244 | for (uint32_t i = 0, arc_segment = 0; i < top_begin_offset; i += 3 * (floats_per_vert), ++arc_segment) { 245 | float p1_radians = arc_radians * arc_segment; 246 | float p2_radians = arc_radians * (arc_segment + 1); 247 | 248 | float cos_1 = cosf(p1_radians) * radius; 249 | float sin_1 = sinf(p1_radians) * radius; 250 | float cos_2 = cosf(p2_radians) * radius; 251 | float sin_2 = sinf(p2_radians) * radius; 252 | 253 | uint32_t bot_index = i; 254 | uint32_t top_index = i + top_begin_offset; 255 | 256 | out[bot_index++] = 0; out[bot_index++] = -cone_y; out[bot_index++] = 0; bot_index += stride; 257 | out[bot_index++] = cos_1; out[bot_index++] = -cone_y; out[bot_index++] = sin_1; bot_index += stride; 258 | out[bot_index++] = cos_2; out[bot_index++] = -cone_y; out[bot_index++] = sin_2; bot_index += stride; 259 | 260 | out[top_index++] = 0; out[top_index++] = cone_y; out[top_index++] = 0; top_index += stride; 261 | out[top_index++] = cos_2; out[top_index++] = -cone_y; out[top_index++] = sin_2; top_index += stride; 262 | out[top_index++] = cos_1; out[top_index++] = -cone_y; out[top_index++] = sin_1; top_index += stride; 263 | } 264 | 265 | return out + num_verts * floats_per_vert; 266 | } 267 | 268 | float* rjd_procgeo_cylinder(float radius, float height, uint32_t tesselation, float* out, uint32_t length, uint32_t stride) 269 | { 270 | const uint32_t floats_per_vert = RJD_PROCGEO_FLOATS_PER_TRI + stride; 271 | const uint32_t num_verts = rjd_procgeo_cylinder_calc_num_verts(tesselation); 272 | if (length < num_verts * floats_per_vert) { 273 | return NULL; 274 | } 275 | 276 | const uint32_t final_tesselation = tesselation + RJD_PROCGEO_MIN_TESSELATION_CIRCLE; 277 | const float arc_radians = RJD_PROCGEO_PI * 2 / final_tesselation; 278 | 279 | const uint32_t top_begin_offset = rjd_procgeo_circle_calc_num_verts(tesselation); 280 | const uint32_t side_begin_offset = top_begin_offset * 2; 281 | const float y = height / 2; 282 | 283 | for (uint32_t v = 0, arc_segment = 0; v < top_begin_offset; v += RJD_PROCGEO_FLOATS_PER_TRI, ++arc_segment) { 284 | float p1_radians = arc_radians * arc_segment; 285 | float p2_radians = arc_radians * (arc_segment + 1); 286 | 287 | float cos_1 = cosf(p1_radians) * radius; 288 | float sin_1 = sinf(p1_radians) * radius; 289 | float cos_2 = cosf(p2_radians) * radius; 290 | float sin_2 = sinf(p2_radians) * radius; 291 | 292 | uint32_t bot_index = v * floats_per_vert; 293 | out[bot_index++] = 0; out[bot_index++] = -y; out[bot_index++] = 0; bot_index += stride; 294 | out[bot_index++] = cos_1; out[bot_index++] = -y; out[bot_index++] = sin_1; bot_index += stride; 295 | out[bot_index++] = cos_2; out[bot_index++] = -y; out[bot_index++] = sin_2; bot_index += stride; 296 | 297 | uint32_t top_index = (v + top_begin_offset) * floats_per_vert; 298 | out[top_index++] = 0; out[top_index++] = y; out[top_index++] = 0; top_index += stride; 299 | out[top_index++] = cos_2; out[top_index++] = y; out[top_index++] = sin_2; top_index += stride; 300 | out[top_index++] = cos_1; out[top_index++] = y; out[top_index++] = sin_1; top_index += stride; 301 | 302 | uint32_t side_index = (v + v + side_begin_offset) * floats_per_vert; 303 | out[side_index++] = cos_1; out[side_index++] = -y; out[side_index++] = sin_1; side_index += stride; 304 | out[side_index++] = cos_1; out[side_index++] = y; out[side_index++] = sin_1; side_index += stride; 305 | out[side_index++] = cos_2; out[side_index++] = y; out[side_index++] = sin_2; side_index += stride; 306 | 307 | out[side_index++] = cos_2; out[side_index++] = y; out[side_index++] = sin_2; side_index += stride; 308 | out[side_index++] = cos_2; out[side_index++] = -y; out[side_index++] = sin_2; side_index += stride; 309 | out[side_index++] = cos_1; out[side_index++] = -y; out[side_index++] = sin_1; side_index += stride; 310 | } 311 | 312 | return out + num_verts * floats_per_vert; 313 | } 314 | 315 | float* rjd_procgeo_sphere(float radius, uint32_t tesselation, float* out, uint32_t length, uint32_t stride) 316 | { 317 | const uint32_t floats_per_vert = RJD_PROCGEO_FLOATS_PER_TRI + stride; 318 | const uint32_t num_verts = rjd_procgeo_sphere_calc_num_verts(tesselation); 319 | if (length < num_verts * floats_per_vert) { 320 | return NULL; 321 | } 322 | 323 | const float pi = RJD_PROCGEO_PI; 324 | 325 | const uint32_t final_tesselation = tesselation + RJD_PROCGEO_MIN_TESSELATION_SPHERE; 326 | 327 | uint32_t i = 0; 328 | 329 | for (uint32_t y_arc = 0; y_arc < final_tesselation; ++y_arc) { 330 | 331 | float y_arc1 = (float)y_arc / final_tesselation; 332 | float y_arc2 = (float)(y_arc + 1) / final_tesselation; 333 | 334 | float cos_2pi_y_arc1 = cosf(2 * pi * y_arc1); 335 | float cos_2pi_y_arc2 = cosf(2 * pi * y_arc2); 336 | 337 | float sin_2pi_y_arc1 = sinf(2 * pi * y_arc1); 338 | float sin_2pi_y_arc2 = sinf(2 * pi * y_arc2); 339 | 340 | for (uint32_t xz_arc = 0; xz_arc < final_tesselation; ++xz_arc) { 341 | 342 | float xz_arc1 = (float)xz_arc / final_tesselation; 343 | float xz_arc2 = (float)(xz_arc + 1) / final_tesselation; 344 | 345 | float sin_pi_xz_arc1 = sinf(pi * xz_arc1); 346 | float cos_pi_xz_arc1 = cosf(pi * xz_arc1); 347 | 348 | float sin_pi_xz_arc2 = sinf(pi * xz_arc2); 349 | float cos_pi_xz_arc2 = cosf(pi * xz_arc2); 350 | 351 | float x1 = sin_pi_xz_arc1 * cos_2pi_y_arc1 * radius; 352 | float y1 = cos_pi_xz_arc1 * radius; 353 | float z1 = sin_pi_xz_arc1 * sin_2pi_y_arc1 * radius; 354 | 355 | float x2 = sin_pi_xz_arc2 * cos_2pi_y_arc1 * radius; 356 | float y2 = cos_pi_xz_arc2 * radius; 357 | float z2 = sin_pi_xz_arc2 * sin_2pi_y_arc1 * radius; 358 | 359 | float x3 = sin_pi_xz_arc1 * cos_2pi_y_arc2 * radius; 360 | float y3 = cos_pi_xz_arc1 * radius; 361 | float z3 = sin_pi_xz_arc1 * sin_2pi_y_arc2 * radius; 362 | 363 | float x4 = sin_pi_xz_arc2 * cos_2pi_y_arc2 * radius; 364 | float y4 = cos_pi_xz_arc2 * radius; 365 | float z4 = sin_pi_xz_arc2 * sin_2pi_y_arc2 * radius; 366 | 367 | if (xz_arc > 0) 368 | { 369 | out[i++] = x1; out[i++] = y1; out[i++] = z1; i += stride; 370 | out[i++] = x3; out[i++] = y3; out[i++] = z3; i += stride; 371 | out[i++] = x2; out[i++] = y2; out[i++] = z2; i += stride; 372 | } 373 | 374 | if (xz_arc < final_tesselation - 1) 375 | { 376 | out[i++] = x2; out[i++] = y2; out[i++] = z2; i += stride; 377 | out[i++] = x3; out[i++] = y3; out[i++] = z3; i += stride; 378 | out[i++] = x4; out[i++] = y4; out[i++] = z4; i += stride; 379 | } 380 | } 381 | } 382 | 383 | return out + num_verts * floats_per_vert; 384 | } 385 | 386 | #endif 387 | 388 | -------------------------------------------------------------------------------- /rjd_resource.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define RJD_RESOURCE_H 1 4 | 5 | // TODO update all the RJD_LOGs to a resource channel 6 | struct rjd_result; 7 | struct rjd_resource_type; 8 | struct rjd_resource; 9 | struct rjd_resource_loader; 10 | struct rjd_resource_load_begin_params; 11 | struct rjd_resource_load_end_params; 12 | struct rjd_resource_unload_params; 13 | struct rjd_resource_load_dependency_params; 14 | struct rjd_resource_get_dependency_params; 15 | 16 | enum rjd_resource_status 17 | { 18 | RJD_RESOURCE_STATUS_INVALID, 19 | RJD_RESOURCE_STATUS_LOAD_BEGIN, 20 | RJD_RESOURCE_STATUS_LOAD_RESOLVE, 21 | RJD_RESOURCE_STATUS_LOAD_END, 22 | RJD_RESOURCE_STATUS_FAILED, 23 | RJD_RESOURCE_STATUS_READY, 24 | }; 25 | 26 | struct rjd_resource_dependency 27 | { 28 | enum rjd_resource_status status; 29 | void* typed_resource_data; 30 | }; 31 | 32 | // Loading takes place in 2 passes, where resources get a chance to declare any dependencies they want loaded before 33 | // their load function gets called. 34 | typedef struct rjd_result rjd_resource_load_begin_func(struct rjd_resource_load_begin_params* params); 35 | typedef struct rjd_result rjd_resource_load_end_func(struct rjd_resource_load_end_params* params); 36 | typedef void rjd_resource_unload_func(struct rjd_resource_unload_params* params); 37 | typedef struct rjd_result rjd_resource_add_dependency_func(struct rjd_resource_load_dependency_params* params, struct rjd_resource_id dependency, struct rjd_resource_handle* child); 38 | typedef struct rjd_resource_dependency rjd_resource_get_dependency_func(struct rjd_resource_get_dependency_params* params, struct rjd_resource_handle handle); 39 | 40 | struct rjd_resource_load_begin_params 41 | { 42 | struct rjd_istream stream_data; 43 | uint32_t filesize; 44 | void* typed_resource_data; 45 | void* userdata; 46 | struct rjd_mem_allocator* allocator; 47 | struct rjd_mem_allocator* scratch_allocator; 48 | 49 | // for loading dependent resources 50 | rjd_resource_add_dependency_func* load_dependency_func; 51 | struct rjd_resource_load_dependency_params* dependency_params; 52 | }; 53 | 54 | struct rjd_resource_load_end_params 55 | { 56 | void* typed_resource_data; 57 | void* userdata; 58 | struct rjd_mem_allocator* allocator; 59 | struct rjd_mem_allocator* scratch_allocator; 60 | 61 | struct rjd_resource_get_dependency_params* get_dependency_params; 62 | rjd_resource_get_dependency_func* get_dependency_func; 63 | }; 64 | 65 | struct rjd_resource_unload_params 66 | { 67 | void* typed_resource_data; 68 | void* userdata; 69 | 70 | struct rjd_resource_get_dependency_params* get_dependency_params; 71 | rjd_resource_get_dependency_func* get_dependency_func; 72 | }; 73 | 74 | struct rjd_resource_type 75 | { 76 | struct rjd_resource_type_id id; 77 | struct rjd_mem_allocator* optional_allocator; 78 | void* userdata; 79 | rjd_resource_load_begin_func* load_begin_func; 80 | rjd_resource_load_end_func* optional_load_end_func; 81 | rjd_resource_unload_func* optional_unload_func; 82 | uint32_t in_memory_size; 83 | }; 84 | 85 | struct rjd_resource_lib_desc 86 | { 87 | struct rjd_mem_allocator* allocator; 88 | struct rjd_resource_loader* loader; 89 | 90 | struct rjd_resource_type* types; 91 | uint32_t count_types; 92 | uint32_t initial_capacity_types; 93 | uint32_t initial_capacity_resources; 94 | uint32_t initial_capacity_buffers; 95 | }; 96 | 97 | struct rjd_resource_lib 98 | { 99 | struct rjd_mem_allocator* allocator; 100 | struct rjd_resource_loader* loader; 101 | struct rjd_resource_type* registered_types; // should only be accessed from one thread, TODO enforce 102 | struct rjd_resource* resources; // TODO make threadsafe 103 | 104 | // TODO make these threadsafe queues 105 | struct { 106 | struct rjd_resource_handle* begin; 107 | struct rjd_resource_handle* resolving_dependencies; 108 | struct rjd_resource_handle* end; 109 | } load_stage_queues; 110 | 111 | struct rjd_resource_handle* unload_queue; 112 | }; 113 | 114 | struct rjd_resource_id rjd_resource_id_from_path(const char* path); 115 | 116 | void rjd_resource_lib_create(struct rjd_resource_lib* lib, struct rjd_resource_lib_desc desc); 117 | struct rjd_result rjd_resource_lib_destroy(struct rjd_resource_lib* lib); 118 | struct rjd_result rjd_resource_lib_register_type(struct rjd_resource_lib* lib, struct rjd_resource_type type); 119 | // TODO have the system return fallback handles for resources that failed to load 120 | //struct rjd_result rjd_resource_lib_register_fallback(struct rjd_resource_lib* lib, struct rjd_resource_type type, struct rjd_resource_handle fallback); 121 | void rjd_resource_lib_pump(struct rjd_resource_lib* lib, struct rjd_mem_allocator* scratch_allocator); 122 | void rjd_resource_lib_wait(struct rjd_resource_lib* lib, struct rjd_resource_handle* resources, size_t count); 123 | void rjd_resource_lib_waitall(struct rjd_resource_lib* lib); 124 | 125 | struct rjd_result rjd_resource_load(struct rjd_resource_lib* lib, struct rjd_resource_id id, struct rjd_resource_handle* out); 126 | enum rjd_resource_status rjd_resource_lib_status(struct rjd_resource_lib* lib, struct rjd_resource_handle handle); 127 | bool rjd_resource_is_loading(struct rjd_resource_lib* lib, struct rjd_resource_handle handle); 128 | void* rjd_resource_get(struct rjd_resource_lib* lib, struct rjd_resource_handle id); 129 | void rjd_resource_unload(struct rjd_resource_lib* lib, struct rjd_resource_handle handle); 130 | 131 | #if RJD_IMPL 132 | 133 | // NOTE: We explicitly don't have refcounting in this system. Clients can build their own refcount on top of this if they want. 134 | struct rjd_resource 135 | { 136 | struct rjd_resource_id id; 137 | void* typed_resource_data; 138 | struct rjd_resource_handle* dependencies; 139 | enum rjd_resource_status status; 140 | uint32_t refcount; // TODO atomic 141 | uint32_t registry_index; 142 | }; 143 | 144 | struct rjd_resource_load_dependency_params 145 | { 146 | struct rjd_resource_lib* lib; 147 | struct rjd_resource_handle parent; 148 | }; 149 | 150 | static struct rjd_result rjd_resource_add_dependency(struct rjd_resource_load_dependency_params* params, struct rjd_resource_id id, struct rjd_resource_handle* child); 151 | static struct rjd_resource_dependency rjd_resource_get_dependency(struct rjd_resource_get_dependency_params* params, struct rjd_resource_handle handle); 152 | 153 | void rjd_resource_lib_create(struct rjd_resource_lib* lib, struct rjd_resource_lib_desc desc) 154 | { 155 | if (desc.initial_capacity_types == 0) { 156 | desc.initial_capacity_types = 32; 157 | } 158 | 159 | if (desc.initial_capacity_resources == 0) { 160 | desc.initial_capacity_resources = 256; 161 | } 162 | 163 | if (desc.initial_capacity_buffers == 0) { 164 | desc.initial_capacity_buffers = 64; 165 | } 166 | 167 | RJD_ASSERTMSG(desc.loader, "You must create a loader with rjd_resource_loader_create() and set it in rjd_resource_lib_desc"); 168 | 169 | lib->allocator = desc.allocator; 170 | lib->loader = desc.loader; 171 | lib->registered_types = rjd_array_alloc(struct rjd_resource_type, desc.initial_capacity_types, desc.allocator); 172 | lib->resources = rjd_slotmap_alloc(struct rjd_resource, desc.initial_capacity_resources, desc.allocator); 173 | lib->load_stage_queues.begin = rjd_array_alloc(struct rjd_resource_handle, desc.initial_capacity_buffers, desc.allocator); 174 | lib->load_stage_queues.resolving_dependencies = rjd_array_alloc(struct rjd_resource_handle, desc.initial_capacity_buffers, desc.allocator); 175 | lib->load_stage_queues.end = rjd_array_alloc(struct rjd_resource_handle, desc.initial_capacity_buffers, desc.allocator); 176 | lib->unload_queue = rjd_array_alloc(struct rjd_resource_handle, desc.initial_capacity_buffers, desc.allocator); 177 | } 178 | 179 | struct rjd_result rjd_resource_lib_destroy(struct rjd_resource_lib* lib) 180 | { 181 | RJD_ASSERT(lib); 182 | 183 | if (rjd_slotmap_count(lib->resources) > 0) { 184 | return RJD_RESULT("You must unload all resources before destroying the lib"); 185 | } 186 | 187 | rjd_array_free(lib->unload_queue); 188 | rjd_array_free(lib->load_stage_queues.end); 189 | rjd_array_free(lib->load_stage_queues.resolving_dependencies); 190 | rjd_array_free(lib->load_stage_queues.begin); 191 | rjd_slotmap_free(lib->resources); 192 | rjd_array_free(lib->registered_types); 193 | rjd_resource_loader_destroy(lib->loader); 194 | 195 | return RJD_RESULT_OK(); 196 | } 197 | 198 | struct rjd_result rjd_resource_lib_register_type(struct rjd_resource_lib* lib, struct rjd_resource_type type) 199 | { 200 | RJD_ASSERT(lib); 201 | RJD_ASSERT(type.load_begin_func); 202 | RJD_ASSERT(type.in_memory_size > 0); 203 | 204 | for (uint32_t i = 0; i < rjd_array_count(lib->registered_types); ++i) 205 | { 206 | if (rjd_strhash_isequal(lib->registered_types[i].id.hash, type.id.hash)) 207 | { 208 | return RJD_RESULT("Resource type has already been registered."); 209 | } 210 | } 211 | 212 | rjd_array_push(lib->registered_types, type); 213 | 214 | return RJD_RESULT_OK(); 215 | } 216 | 217 | void rjd_resource_lib_pump(struct rjd_resource_lib* lib, struct rjd_mem_allocator* scratch_allocator) 218 | { 219 | RJD_ASSERT(lib); 220 | 221 | if (!rjd_array_empty(lib->load_stage_queues.end)) { 222 | struct rjd_resource_handle handle = rjd_array_pop(lib->load_stage_queues.end); 223 | struct rjd_resource* resource = rjd_slotmap_get(lib->resources, handle.slot); 224 | RJD_ASSERT(resource); 225 | RJD_ASSERT(resource->status == RJD_RESOURCE_STATUS_LOAD_END); 226 | 227 | struct rjd_result result = RJD_RESULT_OK(); 228 | const struct rjd_resource_type* type = lib->registered_types + resource->registry_index; 229 | if (type->optional_load_end_func) { 230 | struct rjd_resource_load_end_params params = { 231 | .typed_resource_data = resource->typed_resource_data, 232 | .userdata = type->userdata, 233 | .allocator = lib->allocator, 234 | .scratch_allocator = scratch_allocator, 235 | .get_dependency_params = (struct rjd_resource_get_dependency_params*)lib, 236 | .get_dependency_func = rjd_resource_get_dependency, 237 | }; 238 | result = type->optional_load_end_func(¶ms); 239 | } 240 | 241 | if (rjd_result_isok(result)) { 242 | resource->status = RJD_RESOURCE_STATUS_READY; 243 | //RJD_LOG("Loaded resource '%s'", resource->filepath); 244 | } else { 245 | resource->status = RJD_RESOURCE_STATUS_FAILED; 246 | //RJD_LOG("Failed ending load for resource '%s'", resource->filepath); 247 | } 248 | } 249 | 250 | if (!rjd_array_empty(lib->load_stage_queues.resolving_dependencies)) { 251 | struct rjd_resource_handle handle_resource = rjd_array_last(lib->load_stage_queues.resolving_dependencies); 252 | struct rjd_resource* res = rjd_slotmap_get(lib->resources, handle_resource.slot); 253 | RJD_ASSERT(res); 254 | 255 | bool all_loaded = true; 256 | for (uint32_t i = 0; i < rjd_array_count(res->dependencies); ++i) 257 | { 258 | struct rjd_resource_handle handle_dependency = res->dependencies[i]; 259 | struct rjd_resource* dependency = rjd_slotmap_get(lib->resources, handle_dependency.slot); 260 | RJD_ASSERT(dependency); 261 | // failures to load dependencies don't automatically fail resources; we leave it up to 262 | // the resource to decide what to do in the end stage 263 | // TODO use the "backup resource" as a replacement? 264 | if (dependency->status != RJD_RESOURCE_STATUS_READY && dependency->status != RJD_RESOURCE_STATUS_FAILED) { 265 | all_loaded = false; 266 | break; 267 | } 268 | } 269 | 270 | if (all_loaded) { 271 | // struct rjd_resource_handle handle_resource = rjd_array_pop(lib->load_stage_queues.resolving_dependencies); 272 | rjd_array_pop(lib->load_stage_queues.resolving_dependencies); 273 | res->status = RJD_RESOURCE_STATUS_LOAD_END; 274 | rjd_array_push(lib->load_stage_queues.end, handle_resource); 275 | } 276 | } 277 | 278 | if (!rjd_array_empty(lib->load_stage_queues.begin)) { 279 | struct rjd_resource_handle handle = rjd_array_pop(lib->load_stage_queues.begin); 280 | struct rjd_resource* resource = rjd_slotmap_get(lib->resources, handle.slot); 281 | RJD_ASSERT(resource); 282 | RJD_ASSERT(resource->status == RJD_RESOURCE_STATUS_LOAD_BEGIN); 283 | 284 | struct rjd_istream stream = {0}; 285 | struct rjd_result result = rjd_resource_loader_load(lib->loader, resource->id, scratch_allocator, &stream); 286 | if (rjd_result_isok(result)) 287 | { 288 | struct rjd_resource_type* type = rjd_array_get(lib->registered_types, resource->registry_index); 289 | struct rjd_mem_allocator* resource_allocator = type->optional_allocator ? type->optional_allocator : lib->allocator; 290 | 291 | resource->typed_resource_data = rjd_mem_alloc_array(uint8_t, type->in_memory_size, resource_allocator); 292 | memset(resource->typed_resource_data, 0, type->in_memory_size); 293 | 294 | result = RJD_RESULT_OK(); 295 | if (type->load_begin_func) 296 | { 297 | struct rjd_resource_load_dependency_params dependency_params = { 298 | .lib = lib, 299 | .parent = handle, 300 | }; 301 | 302 | struct rjd_resource_load_begin_params load_params = { 303 | .stream_data = stream, 304 | .typed_resource_data = resource->typed_resource_data, 305 | .userdata = type->userdata, 306 | .allocator = resource_allocator, 307 | .scratch_allocator = scratch_allocator, 308 | .load_dependency_func = &rjd_resource_add_dependency, 309 | .dependency_params = &dependency_params, 310 | }; 311 | 312 | result = type->load_begin_func(&load_params); 313 | } 314 | 315 | if (rjd_result_isok(result)) { 316 | resource->status = RJD_RESOURCE_STATUS_LOAD_RESOLVE; 317 | rjd_array_push(lib->load_stage_queues.resolving_dependencies, handle); 318 | } else { 319 | //RJD_LOG("Failed beginning load for resource '%s': %s", resource->filepath, result.error); 320 | resource->status = RJD_RESOURCE_STATUS_FAILED; 321 | } 322 | 323 | rjd_istream_close(&stream); 324 | } 325 | else 326 | { 327 | //RJD_LOG("Failed loading resource '%s': %s", resource_id.hash.debug_string, result.error); 328 | resource->status = RJD_RESOURCE_STATUS_FAILED; 329 | } 330 | } 331 | 332 | if (!rjd_array_empty(lib->unload_queue)) { 333 | struct rjd_resource_handle handle = rjd_array_pop(lib->unload_queue); 334 | struct rjd_resource* resource = rjd_slotmap_get(lib->resources, handle.slot); 335 | 336 | // The resource could have been already freed if it was inserted into the unload queue more than once 337 | if (resource) 338 | { 339 | struct rjd_resource_type* type = rjd_array_get(lib->registered_types, resource->registry_index); 340 | if (type->optional_unload_func) { 341 | struct rjd_resource_unload_params params = { 342 | .typed_resource_data = resource->typed_resource_data, 343 | .userdata = type->userdata, 344 | .get_dependency_params = (struct rjd_resource_get_dependency_params*)lib, 345 | .get_dependency_func = rjd_resource_get_dependency, 346 | }; 347 | type->optional_unload_func(¶ms); 348 | } 349 | rjd_mem_free(resource->typed_resource_data); 350 | rjd_array_free(resource->dependencies); 351 | rjd_slotmap_erase(lib->resources, handle.slot); 352 | } 353 | } 354 | 355 | // TODO get change notifications from loader to insert resources for reload 356 | // NOTE make sure to preserve the original resource until the reload is complete! 357 | } 358 | 359 | void rjd_resource_lib_wait(struct rjd_resource_lib* lib, struct rjd_resource_handle* resources, size_t count) 360 | { 361 | const size_t K_SCRATCH_SIZE = RJD_MB * 4; 362 | void* scratch_buffer = rjd_mem_alloc_array_noclear(char, K_SCRATCH_SIZE, lib->allocator); 363 | struct rjd_mem_allocator scratch_allocator = rjd_mem_allocator_init_linear(scratch_buffer, K_SCRATCH_SIZE); 364 | 365 | for (uint32_t i = 0; i < count; ++i) 366 | { 367 | while (rjd_resource_is_loading(lib, resources[i])) { 368 | rjd_resource_lib_pump(lib, &scratch_allocator); 369 | rjd_mem_allocator_reset(&scratch_allocator); 370 | } 371 | } 372 | 373 | rjd_mem_free(scratch_buffer); 374 | } 375 | 376 | void rjd_resource_lib_waitall(struct rjd_resource_lib* lib) 377 | { 378 | RJD_ASSERT(lib); 379 | 380 | const size_t K_SCRATCH_SIZE = RJD_MB * 4; 381 | void* scratch_buffer = rjd_mem_alloc_array_noclear(char, K_SCRATCH_SIZE, lib->allocator); 382 | struct rjd_mem_allocator scratch_allocator = rjd_mem_allocator_init_linear(scratch_buffer, K_SCRATCH_SIZE); 383 | 384 | while (!rjd_array_empty(lib->load_stage_queues.begin) || 385 | !rjd_array_empty(lib->load_stage_queues.resolving_dependencies) || 386 | !rjd_array_empty(lib->load_stage_queues.end) || 387 | !rjd_array_empty(lib->unload_queue)) 388 | { 389 | rjd_resource_lib_pump(lib, &scratch_allocator); 390 | rjd_mem_allocator_reset(&scratch_allocator); 391 | } 392 | 393 | rjd_mem_free(scratch_buffer); 394 | } 395 | 396 | struct rjd_result rjd_resource_load(struct rjd_resource_lib* lib, struct rjd_resource_id id, struct rjd_resource_handle* out) 397 | { 398 | RJD_ASSERT(lib); 399 | RJD_ASSERT(out); 400 | 401 | // exists already 402 | for (struct rjd_slot s = rjd_slotmap_next(lib->resources, NULL); rjd_slot_isvalid(s); s = rjd_slotmap_next(lib->resources, &s)) 403 | { 404 | struct rjd_resource* resource = rjd_slotmap_get(lib->resources, s); 405 | if (rjd_resource_id_equals(resource->id, id)) { 406 | ++resource->refcount; 407 | out->slot = s; 408 | return RJD_RESULT_OK(); 409 | } 410 | } 411 | 412 | // cancel unload 413 | for (uint32_t i = 0; i < rjd_array_count(lib->unload_queue); ++i) 414 | { 415 | struct rjd_resource_handle handle = lib->unload_queue[i]; 416 | struct rjd_resource* resource = rjd_slotmap_get(lib->resources, handle.slot); 417 | if (rjd_resource_id_equals(resource->id, id)) { 418 | rjd_array_erase_unordered(lib->resources, i); 419 | ++resource->refcount; 420 | *out = handle; 421 | return RJD_RESULT_OK(); 422 | } 423 | } 424 | 425 | // kickoff the load 426 | struct rjd_resource_type_id type = {0}; 427 | struct rjd_result result = rjd_resource_loader_get_type(lib->loader, id, &type); 428 | if (rjd_result_isok(result)) 429 | { 430 | int32_t registry_index = RJD_ARRAY_NOT_FOUND; 431 | for (uint32_t i = 0; i < rjd_array_count(lib->registered_types); ++i) { 432 | if (rjd_resource_type_id_equals(lib->registered_types[i].id, type)) { 433 | registry_index = (int32_t)i; 434 | break; 435 | } 436 | } 437 | 438 | if (registry_index == RJD_ARRAY_NOT_FOUND) { 439 | return RJD_RESULT("No resource type was found for the given type id. Did you forget to register it?"); 440 | } else { 441 | struct rjd_resource resource = { 442 | .id = id, 443 | .status = RJD_RESOURCE_STATUS_LOAD_BEGIN, 444 | .registry_index = registry_index, 445 | .dependencies = NULL, 446 | .typed_resource_data = NULL, 447 | .refcount = 1, 448 | }; 449 | 450 | rjd_slotmap_insert(lib->resources, resource, &out->slot); 451 | 452 | rjd_array_push(lib->load_stage_queues.begin, *out); 453 | 454 | result = RJD_RESULT_OK(); 455 | } 456 | } 457 | 458 | return result; 459 | } 460 | 461 | enum rjd_resource_status rjd_resource_lib_status(struct rjd_resource_lib* lib, struct rjd_resource_handle handle) 462 | { 463 | RJD_ASSERT(lib); 464 | 465 | if (rjd_slot_isvalid(handle.slot)) 466 | { 467 | if (rjd_slotmap_contains(lib->resources, handle.slot)) { 468 | struct rjd_resource* res = rjd_slotmap_get(lib->resources, handle.slot); 469 | if (res) { 470 | return res->status; 471 | } 472 | } 473 | } 474 | return RJD_RESOURCE_STATUS_INVALID; 475 | } 476 | 477 | bool rjd_resource_is_loading(struct rjd_resource_lib* lib, struct rjd_resource_handle handle) 478 | { 479 | enum rjd_resource_status status = rjd_resource_lib_status(lib, handle); 480 | return status == RJD_RESOURCE_STATUS_LOAD_BEGIN || 481 | status == RJD_RESOURCE_STATUS_LOAD_RESOLVE || 482 | status == RJD_RESOURCE_STATUS_LOAD_END; 483 | } 484 | 485 | void* rjd_resource_get(struct rjd_resource_lib* lib, struct rjd_resource_handle handle) 486 | { 487 | RJD_ASSERT(lib); 488 | 489 | struct rjd_resource* resource = rjd_slotmap_get(lib->resources, handle.slot); 490 | if (resource && resource->status == RJD_RESOURCE_STATUS_READY) { 491 | return resource->typed_resource_data; 492 | } 493 | return NULL; 494 | } 495 | 496 | void rjd_resource_unload(struct rjd_resource_lib* lib, struct rjd_resource_handle handle) 497 | { 498 | RJD_ASSERT(lib); 499 | 500 | struct rjd_resource* resource = rjd_slotmap_get(lib->resources, handle.slot); 501 | if (resource) { 502 | --resource->refcount; 503 | if (resource->refcount == 0) { 504 | rjd_array_push(lib->unload_queue, handle); 505 | for (uint32_t i = 0; i < rjd_array_count(resource->dependencies); ++i) { 506 | rjd_resource_unload(lib, resource->dependencies[i]); 507 | } 508 | } 509 | } 510 | } 511 | 512 | //////////////////////////////////////////////////////////////////////////////// 513 | // private implementation 514 | 515 | static struct rjd_result rjd_resource_add_dependency(struct rjd_resource_load_dependency_params* params, struct rjd_resource_id id, struct rjd_resource_handle* child) 516 | { 517 | RJD_ASSERT(params); 518 | 519 | struct rjd_resource* parent = rjd_slotmap_get(params->lib->resources, params->parent.slot); 520 | RJD_ASSERT(parent); 521 | 522 | struct rjd_result result = rjd_resource_load(params->lib, id, child); 523 | if (rjd_result_isok(result)) { 524 | if (parent->dependencies == NULL) { 525 | parent->dependencies = rjd_array_alloc(struct rjd_resource_handle, 8, params->lib->allocator); 526 | } 527 | rjd_array_push(parent->dependencies, *child); 528 | } 529 | 530 | return result; 531 | } 532 | 533 | static struct rjd_resource_dependency rjd_resource_get_dependency(struct rjd_resource_get_dependency_params* params, struct rjd_resource_handle handle) 534 | { 535 | struct rjd_resource_lib* lib = (struct rjd_resource_lib*)params; 536 | enum rjd_resource_status status = rjd_resource_lib_status(lib, handle); 537 | void* typed_resource_data = rjd_resource_get(lib, handle); 538 | 539 | struct rjd_resource_dependency dependency = { 540 | .status = status, 541 | .typed_resource_data = typed_resource_data, 542 | }; 543 | return dependency; 544 | } 545 | 546 | #endif // RJD_IMPL 547 | 548 | -------------------------------------------------------------------------------- /rjd_resource_loader.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define RJD_RESOURCE_LOADER_H 1 4 | 5 | struct rjd_resource_loader; 6 | 7 | typedef void rjd_resource_loader_destroy_func(struct rjd_resource_loader* loader); 8 | typedef struct rjd_result rjd_resource_loader_get_type_func(struct rjd_resource_loader* loader, struct rjd_resource_id id, struct rjd_resource_type_id* out); 9 | typedef struct rjd_result rjd_resource_loader_load_func(struct rjd_resource_loader* loader, struct rjd_resource_id id, struct rjd_mem_allocator* allocator, struct rjd_istream* out); 10 | 11 | struct rjd_resource_loader 12 | { 13 | void* impl; 14 | rjd_resource_loader_destroy_func* destroy_func; 15 | rjd_resource_loader_get_type_func* get_type_func; 16 | rjd_resource_loader_load_func* load_func; 17 | }; 18 | 19 | enum rjd_resource_loader_type 20 | { 21 | RJD_RESOURCE_LOADER_TYPE_FILESYSTEM, // recursively crawls a root directory to find all resource files 22 | //RJD_RESOURCE_LOADER_TYPE_REMOTE, // requests manifest from a given http endpoint 23 | //RJD_RESOURCE_LOADER_TYPE_PACK, // reads manifest embedded inside given pack files 24 | }; 25 | 26 | struct rjd_resource_extension_to_type_id 27 | { 28 | struct rjd_resource_type_id type; 29 | const char* extension; 30 | }; 31 | 32 | struct rjd_resource_loader_desc 33 | { 34 | enum rjd_resource_loader_type type; 35 | struct rjd_mem_allocator* allocator; 36 | union 37 | { 38 | struct { 39 | const char* root; 40 | const struct rjd_resource_extension_to_type_id* type_mappings; 41 | uint32_t type_mappings_count; 42 | uint32_t manifest_capacity; 43 | } filesystem; 44 | }; 45 | }; 46 | 47 | struct rjd_result rjd_resource_loader_create(struct rjd_resource_loader* out, struct rjd_resource_loader_desc desc); 48 | static inline void rjd_resource_loader_destroy(struct rjd_resource_loader* loader); 49 | static inline struct rjd_result rjd_resource_loader_get_type(struct rjd_resource_loader* loader, struct rjd_resource_id id, struct rjd_resource_type_id* out); 50 | static inline struct rjd_result rjd_resource_loader_load(struct rjd_resource_loader* loader, struct rjd_resource_id id, struct rjd_mem_allocator* allocator, struct rjd_istream* out); 51 | 52 | //////////////////////////////////////////////////////////////////////////////// 53 | // Inline implementation 54 | 55 | static inline void rjd_resource_loader_destroy(struct rjd_resource_loader* loader) 56 | { 57 | RJD_ASSERT(loader); 58 | RJD_ASSERT(loader->destroy_func); 59 | 60 | loader->destroy_func(loader); 61 | } 62 | 63 | static inline struct rjd_result rjd_resource_loader_get_type(struct rjd_resource_loader* loader, struct rjd_resource_id id, struct rjd_resource_type_id* out) 64 | { 65 | RJD_ASSERT(loader); 66 | RJD_ASSERT(loader->get_type_func); 67 | 68 | return loader->get_type_func(loader, id, out); 69 | } 70 | 71 | static inline struct rjd_result rjd_resource_loader_load(struct rjd_resource_loader* loader, struct rjd_resource_id id, struct rjd_mem_allocator* allocator, struct rjd_istream* out) 72 | { 73 | RJD_ASSERT(loader); 74 | RJD_ASSERT(loader->load_func); 75 | 76 | return loader->load_func(loader, id, allocator, out); 77 | } 78 | 79 | #if RJD_IMPL 80 | 81 | struct rjd_resource_manifest_entry_filesystem 82 | { 83 | struct rjd_resource_id id; 84 | struct rjd_resource_type_id type; 85 | struct rjd_strref* path; 86 | }; 87 | 88 | // private types 89 | struct rjd_resource_loader_filesystem 90 | { 91 | enum rjd_resource_loader_type debug_sentinel; 92 | struct rjd_strpool strpool; 93 | struct rjd_resource_extension_to_type_id* type_mappings; 94 | struct rjd_strref* root; 95 | struct rjd_resource_manifest_entry_filesystem* manifest_entries; // TODO could be sorted by resource id 96 | }; 97 | 98 | // static helpers 99 | 100 | static int32_t RJD_COMPILER_MSVC_ONLY(__cdecl) rjd_resource_loader_manifest_entry_comparer(const void* a, const void* b); 101 | static struct rjd_resource_loader_filesystem* rjd_resource_loader_to_filesystem_loader(struct rjd_resource_loader* loader); 102 | static void rjd_resource_loader_filesystem_destroy(struct rjd_resource_loader* loader); 103 | static struct rjd_result rjd_resource_loader_filesystem_get_type(struct rjd_resource_loader* loader, struct rjd_resource_id id, struct rjd_resource_type_id* out); 104 | static struct rjd_result rjd_resource_loader_filesystem_load(struct rjd_resource_loader* loader, struct rjd_resource_id id, struct rjd_mem_allocator* allocator, struct rjd_istream* out); 105 | 106 | // public implementation 107 | 108 | struct rjd_result rjd_resource_loader_create(struct rjd_resource_loader* out, struct rjd_resource_loader_desc desc) 109 | { 110 | void* impl_any = NULL; 111 | 112 | if (desc.type == RJD_RESOURCE_LOADER_TYPE_FILESYSTEM) 113 | { 114 | uint32_t manifest_capacity = desc.filesystem.manifest_capacity == 0 ? 1024 : desc.filesystem.manifest_capacity; 115 | 116 | struct rjd_resource_loader_filesystem* impl = rjd_mem_alloc(struct rjd_resource_loader_filesystem, desc.allocator); 117 | impl->debug_sentinel = RJD_RESOURCE_LOADER_TYPE_FILESYSTEM; 118 | impl->strpool = rjd_strpool_init(desc.allocator, 64); 119 | impl->type_mappings = rjd_array_alloc(struct rjd_resource_extension_to_type_id, desc.filesystem.type_mappings_count, desc.allocator); 120 | impl->root = rjd_strpool_add(&impl->strpool, desc.filesystem.root); 121 | impl->manifest_entries = rjd_array_alloc(struct rjd_resource_manifest_entry_filesystem, manifest_capacity, desc.allocator); 122 | 123 | for (uint32_t i = 0; i < desc.filesystem.type_mappings_count; ++i) 124 | { 125 | rjd_array_push(impl->type_mappings, desc.filesystem.type_mappings[i]); 126 | } 127 | 128 | const size_t length_root_path = strlen(desc.filesystem.root) + 1; // +1 to skip path separator 129 | struct rjd_path_enumerator_state path_enumerator = rjd_path_enumerate_create(desc.filesystem.root, desc.allocator, RJD_PATH_ENUMERATE_MODE_RECURSIVE); 130 | for (const char* path = rjd_path_enumerate_next(&path_enumerator); path != NULL; path = rjd_path_enumerate_next(&path_enumerator)) 131 | { 132 | const char* relative_path = path + length_root_path; 133 | const char* extension = rjd_path_str_extension(relative_path); 134 | if (extension) 135 | { 136 | struct rjd_resource_type_id type = {0}; 137 | for (uint32_t i = 0; i < rjd_array_count(impl->type_mappings); ++i) 138 | { 139 | if (strcmp(impl->type_mappings[i].extension, extension) == 0) // TODO case-insensitive compare 140 | { 141 | type = impl->type_mappings[i].type; 142 | } 143 | } 144 | 145 | if (type.hash.hash.value != 0) 146 | { 147 | struct rjd_strref* pathref = rjd_strpool_add(&impl->strpool, relative_path); 148 | struct rjd_resource_manifest_entry_filesystem entry = { 149 | .id = rjd_strhash_init(relative_path), 150 | .type = type, 151 | .path = pathref, 152 | }; 153 | rjd_array_push(impl->manifest_entries, entry); 154 | } 155 | } 156 | } 157 | 158 | rjd_path_enumerate_destroy(&path_enumerator); 159 | 160 | // since the enumerator doesn't return entries in a sorted manner, make sure the manifest order 161 | // is deterministic 162 | rjd_array_sort(impl->manifest_entries, rjd_resource_loader_manifest_entry_comparer); 163 | 164 | impl_any = impl; 165 | } 166 | else 167 | { 168 | return RJD_RESULT("unimplemented support for this type of loader"); 169 | } 170 | 171 | struct rjd_resource_loader loader = { 172 | .impl = impl_any, 173 | .destroy_func = rjd_resource_loader_filesystem_destroy, 174 | .get_type_func = rjd_resource_loader_filesystem_get_type, 175 | .load_func = rjd_resource_loader_filesystem_load, 176 | }; 177 | *out = loader; 178 | 179 | return RJD_RESULT_OK(); 180 | } 181 | 182 | // private implementation 183 | 184 | static int32_t RJD_COMPILER_MSVC_ONLY(__cdecl) rjd_resource_loader_manifest_entry_comparer(const void* a, const void* b) 185 | { 186 | const struct rjd_resource_manifest_entry_filesystem* aa = a; 187 | const struct rjd_resource_manifest_entry_filesystem* bb = b; 188 | 189 | const char* path_a = rjd_strref_str(aa->path); 190 | const char* path_b = rjd_strref_str(bb->path); 191 | 192 | return strcmp(path_a, path_b); 193 | } 194 | 195 | static struct rjd_resource_loader_filesystem* rjd_resource_loader_to_filesystem_loader(struct rjd_resource_loader* loader) 196 | { 197 | RJD_ASSERT(loader); 198 | 199 | struct rjd_resource_loader_filesystem* impl = (struct rjd_resource_loader_filesystem*)loader->impl; 200 | RJD_ASSERT(impl->debug_sentinel == RJD_RESOURCE_LOADER_TYPE_FILESYSTEM); 201 | return impl; 202 | } 203 | 204 | static void rjd_resource_loader_filesystem_destroy(struct rjd_resource_loader* loader) 205 | { 206 | struct rjd_resource_loader_filesystem* impl = rjd_resource_loader_to_filesystem_loader(loader); 207 | rjd_array_free(impl->manifest_entries); 208 | rjd_array_free(impl->type_mappings); 209 | rjd_strpool_free(&impl->strpool); 210 | rjd_mem_free(impl); 211 | memset(loader, 0, sizeof(*loader)); 212 | } 213 | 214 | static struct rjd_result rjd_resource_loader_filesystem_get_type(struct rjd_resource_loader* loader, struct rjd_resource_id id, struct rjd_resource_type_id* out) 215 | { 216 | struct rjd_resource_loader_filesystem* impl = rjd_resource_loader_to_filesystem_loader(loader); 217 | for (uint32_t i = 0; i < rjd_array_count(impl->manifest_entries); ++i) { 218 | if (rjd_resource_id_equals(impl->manifest_entries[i].id, id)) { 219 | *out = impl->manifest_entries[i].type; 220 | return RJD_RESULT_OK(); 221 | } 222 | } 223 | 224 | return RJD_RESULT("Resource id not found in loader manifest."); 225 | } 226 | 227 | static struct rjd_result rjd_resource_loader_filesystem_load(struct rjd_resource_loader* loader, struct rjd_resource_id id, struct rjd_mem_allocator* allocator, struct rjd_istream* out) 228 | { 229 | struct rjd_resource_loader_filesystem* impl = rjd_resource_loader_to_filesystem_loader(loader); 230 | for (uint32_t i = 0; i < rjd_array_count(impl->manifest_entries); ++i) { 231 | if (rjd_resource_id_equals(impl->manifest_entries[i].id, id)) { 232 | const char* root = rjd_strref_str(impl->root); 233 | const char* relative_path = rjd_strref_str(impl->manifest_entries[i].path); 234 | 235 | struct rjd_path fullpath = rjd_path_init(); 236 | rjd_path_join_str(&fullpath, root); 237 | rjd_path_join_str(&fullpath, relative_path); 238 | 239 | *out = rjd_istream_from_file(rjd_path_get(&fullpath), allocator, 1024 * 512); 240 | return out->result; 241 | } 242 | } 243 | 244 | return RJD_RESULT("Resource id not found in loader manifest."); 245 | } 246 | 247 | #endif // RJD_IMPL 248 | 249 | -------------------------------------------------------------------------------- /rjd_resource_types.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define RJD_RESOURCE_TYPES_H 1 4 | 5 | struct rjd_resource_id 6 | { 7 | struct rjd_strhash hash; 8 | }; 9 | 10 | struct rjd_resource_type_id 11 | { 12 | struct rjd_strhash hash; 13 | }; 14 | 15 | struct rjd_resource_handle 16 | { 17 | struct rjd_slot slot; 18 | }; 19 | 20 | static inline struct rjd_resource_id rjd_resource_id_from_str(const char* str); 21 | static inline struct rjd_resource_id rjd_resource_id_none(void); 22 | static inline bool rjd_resource_id_equals(struct rjd_resource_id a, struct rjd_resource_id b); 23 | static inline bool rjd_resource_id_isvalid(struct rjd_resource_id id); 24 | 25 | static inline struct rjd_resource_type_id rjd_resource_type_id_from_str(const char* str); 26 | static inline struct rjd_resource_type_id rjd_resource_type_id_none(void); 27 | static inline bool rjd_resource_type_id_equals(struct rjd_resource_type_id a, struct rjd_resource_type_id b); 28 | static inline bool rjd_resource_type_id_isvalid(struct rjd_resource_type_id id); 29 | 30 | static inline struct rjd_resource_handle rjd_resource_handle_none(void); 31 | static inline bool rjd_resource_handle_equals(struct rjd_resource_handle a, struct rjd_resource_handle b); 32 | static inline bool rjd_resource_handle_isvalid(struct rjd_resource_handle handle); 33 | 34 | //////////////////////////////////////////////////////////////////////////////// 35 | // static inline implementation 36 | 37 | static inline struct rjd_resource_id rjd_resource_id_from_str(const char* str) 38 | { 39 | struct rjd_resource_id id = { 40 | .hash = rjd_strhash_init(str), 41 | }; 42 | return id; 43 | } 44 | 45 | static inline struct rjd_resource_id rjd_resource_id_none(void) 46 | { 47 | return rjd_resource_id_from_str(NULL); 48 | } 49 | 50 | static inline bool rjd_resource_id_equals(struct rjd_resource_id a, struct rjd_resource_id b) 51 | { 52 | return a.hash.hash.value == b.hash.hash.value; 53 | } 54 | 55 | static inline bool rjd_resource_id_isvalid(struct rjd_resource_id id) 56 | { 57 | return id.hash.hash.value != 0; 58 | } 59 | 60 | static inline struct rjd_resource_type_id rjd_resource_type_id_from_str(const char* str) 61 | { 62 | struct rjd_resource_type_id id = { 63 | .hash = rjd_strhash_init(str), 64 | }; 65 | return id; 66 | } 67 | 68 | static inline struct rjd_resource_type_id rjd_resource_type_id_none(void) 69 | { 70 | return rjd_resource_type_id_from_str(NULL); 71 | } 72 | 73 | static inline bool rjd_resource_type_id_equals(struct rjd_resource_type_id a, struct rjd_resource_type_id b) 74 | { 75 | return a.hash.hash.value == b.hash.hash.value; 76 | } 77 | 78 | static inline bool rjd_resource_type_id_isvalid(struct rjd_resource_type_id id) 79 | { 80 | return id.hash.hash.value != 0; 81 | } 82 | 83 | static inline struct rjd_resource_handle rjd_resource_handle_none(void) 84 | { 85 | struct rjd_resource_handle handle; 86 | handle.slot.index = 0; 87 | handle.slot.salt = 0; 88 | return handle; 89 | } 90 | 91 | static inline bool rjd_resource_handle_equals(struct rjd_resource_handle a, struct rjd_resource_handle b) 92 | { 93 | return a.slot.index == b.slot.index && 94 | a.slot.salt == b.slot.salt; 95 | } 96 | 97 | static inline bool rjd_resource_handle_isvalid(struct rjd_resource_handle handle) 98 | { 99 | return rjd_slot_isvalid(handle.slot); 100 | } 101 | 102 | -------------------------------------------------------------------------------- /rjd_result.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define RJD_RESULT_H 1 4 | 5 | struct rjd_result 6 | { 7 | const char* error; 8 | }; 9 | 10 | static inline bool rjd_result_isok(struct rjd_result result) { 11 | return result.error == NULL; 12 | } 13 | 14 | #define RJD_RESULT(message) ((struct rjd_result){"" message}) 15 | #define RJD_RESULT_OK() ((struct rjd_result){NULL}) 16 | #define RJD_RESULT_CHECK(validation_condition, message) if (!(validation_condition)) { return RJD_RESULT(message); } 17 | #define RJD_RESULT_PROMOTE(result) if (!rjd_result_isok(result)) { return result; } 18 | -------------------------------------------------------------------------------- /rjd_rng.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define RJD_RNG_H 1 4 | 5 | struct rjd_rng 6 | { 7 | uint64_t seed; 8 | uint64_t state; 9 | }; 10 | 11 | struct rjd_rng rjd_rng_init(uint64_t seed); 12 | uint64_t rjd_rng_next(struct rjd_rng* rng); 13 | double rjd_rng_float(struct rjd_rng* rng); 14 | int32_t rjd_rng_range32(struct rjd_rng* rng, int32_t min_inclusive, int32_t max_exclusive); 15 | 16 | #if RJD_IMPL 17 | 18 | struct rjd_rng rjd_rng_init(uint64_t seed) 19 | { 20 | struct rjd_rng rng = { seed, seed }; 21 | return rng; 22 | } 23 | 24 | uint64_t rjd_rng_next(struct rjd_rng* rng) 25 | { 26 | rng->state ^= rng->state << 15; 27 | rng->state ^= rng->state >> 3; 28 | rng->state ^= rng->state << 52; 29 | return rng->state; 30 | } 31 | 32 | double rjd_rng_float(struct rjd_rng* rng) 33 | { 34 | uint64_t next = rjd_rng_next(rng) % 1000001ull; 35 | return (double)next / 1000000.0; 36 | } 37 | 38 | int32_t rjd_rng_range32(struct rjd_rng* rng, int32_t min_inclusive, int32_t max_exclusive) 39 | { 40 | if (max_exclusive < min_inclusive) { 41 | return max_exclusive; 42 | } 43 | 44 | int32_t range = max_exclusive - min_inclusive; 45 | if (range == 0) { 46 | return min_inclusive; 47 | } 48 | 49 | uint64_t next = rjd_rng_next(rng); 50 | return min_inclusive + (next % range); 51 | } 52 | 53 | #endif // RJD_IMPL 54 | 55 | -------------------------------------------------------------------------------- /rjd_slotmap.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define RJD_SLOTMAP_H 1 4 | 5 | struct rjd_slot 6 | { 7 | uint16_t index; 8 | uint16_t salt; 9 | }; 10 | 11 | static inline bool rjd_slot_isvalid(struct rjd_slot slot); 12 | static inline void rjd_slot_invalidate(struct rjd_slot* slot); 13 | 14 | #define rjd_slotmap_alloc(type, capacity, allocator) (rjd_slotmap_alloc_impl(sizeof(type), capacity, allocator)) 15 | #define rjd_slotmap_insert(map, data, out_slot) (rjd_slotmap_insert_impl((void**)(&map), (out_slot)), \ 16 | (map)[(out_slot)->index] = data) 17 | #define rjd_slotmap_contains(map, slot) (rjd_slotmap_contains_impl((map), (slot))) 18 | #define rjd_slotmap_get(map, slot) ((map) + rjd_slotmap_get_impl((map), (slot))) 19 | #define rjd_slotmap_count(map) (rjd_slotmap_count_impl(map)) 20 | #define rjd_slotmap_erase(map, slot) (rjd_slotmap_erase_impl((map), slot)) 21 | #define rjd_slotmap_free(map) (rjd_slotmap_free_impl(map)) 22 | #define rjd_slotmap_next(map, slot) (rjd_slotmap_next_impl((map), (slot))) // pass null slot for first 23 | 24 | void* rjd_slotmap_alloc_impl(size_t sizeof_type, uint32_t capacity, struct rjd_mem_allocator* allocator); 25 | void rjd_slotmap_insert_impl(void** map_p, struct rjd_slot* out_slot); 26 | bool rjd_slotmap_contains_impl(const void* map, struct rjd_slot slot); 27 | uint32_t rjd_slotmap_get_impl(const void* map, struct rjd_slot slot); 28 | uint32_t rjd_slotmap_count_impl(const void* map); 29 | void rjd_slotmap_erase_impl(void* map, struct rjd_slot slot); 30 | void rjd_slotmap_free_impl(void* map); 31 | struct rjd_slot rjd_slotmap_next_impl(void* map, const struct rjd_slot* slot); 32 | 33 | static inline bool rjd_slot_isvalid(struct rjd_slot slot) 34 | { 35 | return slot.salt != 0; 36 | } 37 | 38 | static inline void rjd_slot_invalidate(struct rjd_slot* slot) 39 | { 40 | RJD_ASSERT(slot); 41 | slot->salt = 0; 42 | } 43 | 44 | #if RJD_IMPL 45 | 46 | //////////////////////////////////////////////////////////////////////////////// 47 | // private interface 48 | 49 | struct rjd_slotmap_header 50 | { 51 | struct rjd_mem_allocator* allocator; 52 | void* data; 53 | uint16_t* salts; 54 | bool* used; 55 | uint32_t* freelist; 56 | uint32_t sizeof_type; 57 | uint32_t capacity; 58 | uint32_t count; 59 | uint32_t debug_sentinel; 60 | }; 61 | 62 | enum { 63 | RJD_SLOTMAP_DEBUG_SENTINEL = 0x5A5A5A5A, 64 | }; 65 | 66 | static struct rjd_slotmap_header* rjd_slotmap_getheader(const void* map); 67 | static void* rjd_slotmap_grow(void* oldmap, size_t sizeof_type, uint32_t capacity, struct rjd_mem_allocator* allocator); 68 | 69 | //////////////////////////////////////////////////////////////////////////////// 70 | // public implementation 71 | 72 | void* rjd_slotmap_alloc_impl(size_t sizeof_type, uint32_t capacity, struct rjd_mem_allocator* allocator) 73 | { 74 | return rjd_slotmap_grow(NULL, sizeof_type, capacity, allocator); 75 | } 76 | 77 | void rjd_slotmap_insert_impl(void** map_p, struct rjd_slot* out_slot) 78 | { 79 | RJD_ASSERT(map_p); 80 | RJD_ASSERT(*map_p); 81 | RJD_ASSERT(out_slot); 82 | 83 | void* map = *map_p; 84 | 85 | struct rjd_slotmap_header* header = rjd_slotmap_getheader(map); 86 | 87 | if (rjd_array_count(header->freelist) == 0) { 88 | map = rjd_slotmap_grow(map, header->sizeof_type, header->capacity * 2, header->allocator); 89 | header = rjd_slotmap_getheader(map); 90 | RJD_ASSERT(rjd_array_count(header->freelist) > 0); 91 | *map_p = map; 92 | } 93 | 94 | uint32_t index = rjd_array_pop(header->freelist); 95 | 96 | uint16_t* salt = header->salts + index; 97 | *salt += 1; 98 | 99 | header->used[index] = true; 100 | ++header->count; 101 | 102 | out_slot->index = rjd_math_truncate_u32_to_u16(index); 103 | out_slot->salt = *salt; 104 | } 105 | 106 | bool rjd_slotmap_contains_impl(const void* map, struct rjd_slot slot) 107 | { 108 | RJD_ASSERT(map); 109 | 110 | const struct rjd_slotmap_header* header = rjd_slotmap_getheader(map); 111 | if (slot.index >= header->capacity) { 112 | return false; 113 | } 114 | uint32_t index = slot.index; 115 | return !rjd_array_contains(header->freelist, &index); 116 | } 117 | 118 | uint32_t rjd_slotmap_get_impl(const void* map, struct rjd_slot slot) 119 | { 120 | RJD_ASSERT(map); 121 | 122 | const struct rjd_slotmap_header* header = rjd_slotmap_getheader(map); 123 | RJD_ASSERT(slot.index < header->capacity); 124 | uint32_t index = slot.index; 125 | RJD_ASSERTMSG(!rjd_array_contains(header->freelist, &index), "This slot is unallocated. Use rjd_slotmap_contains to check if the slot is valid first."); 126 | int16_t salt = header->salts[slot.index]; 127 | RJD_ASSERT(salt == slot.salt); 128 | return slot.index; 129 | } 130 | 131 | uint32_t rjd_slotmap_count_impl(const void* map) 132 | { 133 | RJD_ASSERT(map); 134 | 135 | const struct rjd_slotmap_header* header = rjd_slotmap_getheader(map); 136 | return header->count; 137 | } 138 | 139 | void rjd_slotmap_erase_impl(void* map, struct rjd_slot slot) 140 | { 141 | RJD_ASSERT(map); 142 | struct rjd_slotmap_header* header = rjd_slotmap_getheader(map); 143 | RJD_ASSERT(slot.index < header->capacity); 144 | 145 | rjd_array_push(header->freelist, slot.index); 146 | header->used[slot.index] = false; 147 | --header->count; 148 | } 149 | 150 | void rjd_slotmap_free_impl(void* map) 151 | { 152 | RJD_ASSERT(map); 153 | struct rjd_slotmap_header* header = rjd_slotmap_getheader(map); 154 | 155 | rjd_array_free(header->used); 156 | rjd_array_free(header->freelist); 157 | rjd_mem_free(header); 158 | } 159 | 160 | struct rjd_slot rjd_slotmap_next_impl(void* map, const struct rjd_slot* slot) 161 | { 162 | RJD_ASSERT(map); 163 | struct rjd_slotmap_header* header = rjd_slotmap_getheader(map); 164 | 165 | uint32_t start = 0; 166 | if (slot) { 167 | start = slot->index + 1; 168 | } 169 | 170 | for (uint32_t i = start; i < header->capacity; ++i) { 171 | if (header->used[i]) { 172 | struct rjd_slot next = { 173 | .index = rjd_math_truncate_u32_to_u16(i), 174 | .salt = header->salts[i], 175 | }; 176 | return next; 177 | } 178 | } 179 | 180 | struct rjd_slot next = {0}; 181 | RJD_ASSERT(!rjd_slot_isvalid(next)); 182 | return next; 183 | } 184 | 185 | //////////////////////////////////////////////////////////////////////////////// 186 | // private implementation 187 | 188 | struct rjd_slotmap_header* rjd_slotmap_getheader(const void* map) 189 | { 190 | char* raw = (char*)map; 191 | char* rawheader = (raw - sizeof(struct rjd_slotmap_header)); 192 | struct rjd_slotmap_header* header = (struct rjd_slotmap_header*)rawheader; 193 | RJD_ASSERTMSG(header->debug_sentinel == RJD_SLOTMAP_DEBUG_SENTINEL, 194 | "Debug sentinel does not match. Address %p does not point to a slotmap or there was a buffer underrun corruption.", map); 195 | return header; 196 | } 197 | 198 | void* rjd_slotmap_grow(void* oldmap, size_t sizeof_type, uint32_t capacity, struct rjd_mem_allocator* allocator) 199 | { 200 | struct rjd_slotmap_header* oldheader = oldmap ? rjd_slotmap_getheader(oldmap) : NULL; 201 | 202 | uint32_t oldcapacity = oldheader ? oldheader->capacity : 0; 203 | if (capacity <= oldcapacity) { 204 | return oldmap; 205 | } 206 | 207 | size_t total_mem_size = sizeof(struct rjd_slotmap_header) + sizeof_type * capacity + sizeof(uint16_t) * capacity; 208 | char* mem = rjd_mem_alloc_array(char, total_mem_size, allocator); 209 | memset(mem, 0, total_mem_size); 210 | 211 | struct rjd_slotmap_header* header = (struct rjd_slotmap_header*)mem; 212 | header->allocator = allocator; 213 | header->sizeof_type = (uint32_t)sizeof_type; 214 | header->capacity = capacity; 215 | header->count = 0; 216 | header->data = (void*)(mem + sizeof(struct rjd_slotmap_header)); 217 | header->salts = (uint16_t*)((char*)header->data + sizeof_type * capacity); 218 | header->used = rjd_array_alloc(bool, capacity, allocator); 219 | header->freelist = rjd_array_alloc(uint32_t, capacity, allocator); 220 | header->debug_sentinel = RJD_SLOTMAP_DEBUG_SENTINEL; 221 | 222 | memset(header->salts + oldcapacity, 0, (header->capacity - oldcapacity) * sizeof(*header->salts)); 223 | 224 | rjd_array_resize(header->used, capacity); 225 | if (oldheader) { 226 | memcpy(header->used, oldheader->used, sizeof(*header->used) * oldheader->capacity); 227 | header->count = oldheader->count; 228 | } 229 | 230 | for (uint32_t i = oldcapacity; i < capacity; ++i) { 231 | rjd_array_push(header->freelist, i); 232 | } 233 | 234 | // copy existing data 235 | if (oldheader) { 236 | memcpy(header->data, oldheader->data, sizeof_type * oldheader->capacity); 237 | memcpy(header->salts, oldheader->salts, sizeof(uint16_t) * oldheader->capacity); 238 | rjd_slotmap_free(oldmap); 239 | } 240 | 241 | return header->data; 242 | } 243 | 244 | #endif 245 | 246 | -------------------------------------------------------------------------------- /rjd_strbuf.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define RJD_STRBUF_H 1 4 | 5 | #ifndef RJD_STRBUF_STATIC_SIZE 6 | #define RJD_STRBUF_STATIC_SIZE 512 7 | #endif 8 | 9 | RJD_STATIC_ASSERT(RJD_STRBUF_STATIC_SIZE > 0); 10 | 11 | struct rjd_mem_allocator; 12 | 13 | struct rjd_strbuf 14 | { 15 | struct rjd_mem_allocator* allocator; 16 | uint32_t length; 17 | char* heap; 18 | char stack[RJD_STRBUF_STATIC_SIZE]; 19 | }; 20 | 21 | struct rjd_strbuf rjd_strbuf_init(struct rjd_mem_allocator* allocator); 22 | uint32_t rjd_strbuf_length(const struct rjd_strbuf* buf); 23 | const char* rjd_strbuf_str(const struct rjd_strbuf* buf); 24 | void rjd_strbuf_clear(struct rjd_strbuf* buf); 25 | void rjd_strbuf_append(struct rjd_strbuf* buf, const char* format, ...); 26 | void rjd_strbuf_appendv(struct rjd_strbuf* buf, const char* format, va_list args); 27 | void rjd_strbuf_appendl(struct rjd_strbuf* buf, const char* str, uint32_t length); 28 | void rjd_strbuf_free(struct rjd_strbuf* buf); 29 | 30 | #define RJD_STRBUF_SCOPED(buffername, allocator, scope) \ 31 | { \ 32 | struct rjd_strbuf buffername = rjd_strbuf_init(allocator); \ 33 | {scope} \ 34 | rjd_strbuf_free(&buffername); \ 35 | } 36 | 37 | #if RJD_IMPL 38 | 39 | static void rjd_strbuf_grow(struct rjd_strbuf* buf, uint32_t format_length); 40 | 41 | struct rjd_strbuf rjd_strbuf_init(struct rjd_mem_allocator* allocator) 42 | { 43 | struct rjd_strbuf buf; 44 | buf.length = 0; 45 | buf.heap = 0; 46 | buf.stack[0] = 0; 47 | buf.allocator = allocator; 48 | 49 | return buf; 50 | } 51 | 52 | const char* rjd_strbuf_str(const struct rjd_strbuf* buf) 53 | { 54 | RJD_ASSERT(buf); 55 | return buf->heap ? buf->heap : buf->stack; 56 | } 57 | 58 | void rjd_strbuf_clear(struct rjd_strbuf* buf) 59 | { 60 | RJD_ASSERT(buf); 61 | buf->length = 0; 62 | } 63 | 64 | void rjd_strbuf_append(struct rjd_strbuf* buf, const char* format, ...) 65 | { 66 | RJD_ASSERT(buf); 67 | 68 | if (!format || *format == '\0') { 69 | return; 70 | } 71 | 72 | va_list args; 73 | va_start(args, format); 74 | rjd_strbuf_appendv(buf, format, args); 75 | va_end(args); 76 | } 77 | 78 | void rjd_strbuf_appendv(struct rjd_strbuf* buf, const char* format, va_list args) 79 | { 80 | RJD_ASSERT(buf); 81 | 82 | if (!format || *format == '\0') { 83 | return; 84 | } 85 | 86 | uint32_t capacity = buf->heap ? rjd_array_capacity(buf->heap) : RJD_STRBUF_STATIC_SIZE; 87 | uint32_t remaining = capacity - buf->length; 88 | uint32_t format_length = (uint32_t)strlen(format); 89 | 90 | if (remaining < format_length + 1) { 91 | rjd_strbuf_grow(buf, format_length); 92 | remaining = rjd_array_capacity(buf->heap) - buf->length; 93 | } 94 | 95 | char* str = buf->heap ? buf->heap : buf->stack; 96 | int written = vsnprintf(str + buf->length, remaining, format, args); 97 | while (written < 0) { 98 | rjd_strbuf_grow(buf, 1); 99 | str = buf->heap; 100 | written = vsnprintf(str + buf->length, remaining, format, args); 101 | } 102 | 103 | buf->length += written; 104 | } 105 | 106 | void rjd_strbuf_appendl(struct rjd_strbuf* buf, const char* format, uint32_t length) 107 | { 108 | RJD_ASSERT(buf); 109 | RJD_ASSERT(format + length <= format + strlen(format)); 110 | 111 | if (format == NULL || *format == '\0') { 112 | return; 113 | } 114 | 115 | uint32_t capacity = buf->heap ? rjd_array_capacity(buf->heap) : RJD_STRBUF_STATIC_SIZE; 116 | uint32_t remaining = capacity - buf->length; 117 | 118 | if (remaining < length + 1) { 119 | rjd_strbuf_grow(buf, length); 120 | } 121 | 122 | char* str = buf->heap ? buf->heap : buf->stack; 123 | memcpy(str + buf->length, format, length); 124 | buf->length += length; 125 | str[buf->length] = '\0'; 126 | } 127 | 128 | void rjd_strbuf_free(struct rjd_strbuf* buf) 129 | { 130 | RJD_ASSERT(buf); 131 | 132 | rjd_array_free(buf->heap); 133 | buf->length = 0; 134 | buf->heap = 0; 135 | buf->stack[0] = '\0'; 136 | } 137 | 138 | static void rjd_strbuf_grow(struct rjd_strbuf* buf, uint32_t format_length) 139 | { 140 | RJD_ASSERT(buf); 141 | RJD_ASSERT(buf->allocator); 142 | 143 | uint32_t current = buf->heap ? rjd_array_capacity(buf->heap) : RJD_STRBUF_STATIC_SIZE; 144 | uint32_t min = current + format_length + 1; 145 | uint32_t next = rjd_math_next_pow2_u32(min); 146 | 147 | if (!buf->heap) { 148 | buf->heap = rjd_array_alloc(char, next, buf->allocator); 149 | rjd_array_resize(buf->heap, next); 150 | 151 | strcpy(buf->heap, buf->stack); 152 | } else { 153 | rjd_array_resize(buf->heap, next); 154 | } 155 | } 156 | 157 | #endif // RJD_IMPL 158 | 159 | -------------------------------------------------------------------------------- /rjd_stream.h: -------------------------------------------------------------------------------- 1 | #define RJD_STREAM_H 1 2 | 3 | // rjd_istream API design based on Fabien Giesen's Buffer-Centric IO: 4 | // https://fgiesen.wordpress.com/2011/11/21/buffer-centric-io/ 5 | 6 | struct rjd_istream; 7 | 8 | typedef struct rjd_result rjd_istream_refill_func(struct rjd_istream* stream); 9 | typedef void rjd_istream_close_func(struct rjd_istream* stream); 10 | 11 | struct rjd_istream 12 | { 13 | const uint8_t* start; 14 | const uint8_t* end; 15 | const uint8_t* cursor; 16 | void* userdata; 17 | struct rjd_result result; 18 | rjd_istream_refill_func* refill; 19 | rjd_istream_close_func* close; 20 | }; 21 | 22 | // Pass this as buffer_size in rjd_istream_from_file to read the entire file into memory 23 | enum 24 | { 25 | RJD_ISTREAM_FILE_BUFFER_SIZE_ALL = 0, 26 | }; 27 | 28 | enum rjd_ostream_type 29 | { 30 | RJD_OSTREAM_TYPE_MEMORY, 31 | RJD_OSTREAM_TYPE_FILE, 32 | }; 33 | 34 | struct rjd_ostream_memory 35 | { 36 | uint8_t* buffer; 37 | uint32_t size; 38 | uint64_t cursor; 39 | }; 40 | 41 | struct rjd_ostream_file 42 | { 43 | FILE* file; 44 | }; 45 | 46 | struct rjd_ostream 47 | { 48 | enum rjd_ostream_type type; 49 | union { 50 | struct rjd_ostream_memory memory; 51 | struct rjd_ostream_file file; 52 | } state; 53 | }; 54 | 55 | enum rjd_ostream_mode 56 | { 57 | RJD_OSTREAM_MODE_REPLACE, 58 | RJD_OSTREAM_MODE_APPEND, 59 | }; 60 | 61 | struct rjd_mem_allocator; 62 | 63 | struct rjd_istream rjd_istream_from_zeroes(void); 64 | struct rjd_istream rjd_istream_from_memory(const void* buffer, size_t size); 65 | struct rjd_istream rjd_istream_from_file(const char* filepath, struct rjd_mem_allocator* allocator, size_t buffer_size); 66 | struct rjd_result rjd_istream_read(struct rjd_istream* stream, void* buffer, size_t size); 67 | void rjd_istream_close(struct rjd_istream* stream); 68 | 69 | struct rjd_ostream rjd_ostream_from_memory(void* buffer, size_t size); 70 | struct rjd_ostream rjd_ostream_from_file(const char* filepath, enum rjd_ostream_mode); 71 | struct rjd_result rjd_ostream_write(struct rjd_ostream* stream, const void* buffer, size_t size); 72 | void rjd_ostream_close(struct rjd_ostream* stream); 73 | 74 | #if RJD_IMPL 75 | 76 | static struct rjd_result rjd_istream_fail(struct rjd_istream* stream, const char* reason); 77 | static struct rjd_result rjd_istream_refill_zeroes(struct rjd_istream* stream); 78 | static struct rjd_result rjd_istream_refill_memory(struct rjd_istream* stream); 79 | static struct rjd_result rjd_istream_refill_file(struct rjd_istream* stream); 80 | static void rjd_istream_close_file(struct rjd_istream* stream); 81 | 82 | struct rjd_istream rjd_istream_from_zeroes() 83 | { 84 | struct rjd_istream stream = 85 | { 86 | .start = NULL, 87 | .end = NULL, 88 | .cursor = NULL, 89 | .userdata = NULL, 90 | .result = RJD_RESULT_OK(), 91 | .refill = rjd_istream_refill_zeroes, 92 | .close = NULL, 93 | }; 94 | stream.refill(&stream); 95 | return stream; 96 | } 97 | 98 | struct rjd_istream rjd_istream_from_memory(const void* buffer, size_t size) 99 | { 100 | const uint8_t* cbuffer = buffer; 101 | 102 | struct rjd_istream stream = 103 | { 104 | .start = cbuffer, 105 | .end = cbuffer + size, 106 | .cursor = cbuffer, 107 | .userdata = NULL, 108 | .result = RJD_RESULT_OK(), 109 | .refill = rjd_istream_refill_memory, 110 | .close = NULL, 111 | }; 112 | return stream; 113 | } 114 | 115 | struct rjd_istream rjd_istream_from_file(const char* filepath, struct rjd_mem_allocator* allocator, size_t buffer_size) 116 | { 117 | RJD_ASSERT(filepath); 118 | RJD_ASSERT(allocator); 119 | 120 | uint8_t* buffer = NULL; 121 | struct rjd_result result = RJD_RESULT_OK(); 122 | FILE* file = fopen(filepath, "rb"); 123 | 124 | if (file) { 125 | if (buffer_size == RJD_ISTREAM_FILE_BUFFER_SIZE_ALL) { 126 | result = RJD_RESULT("Failed to get file length"); 127 | if (fseek(file, 0, SEEK_END) == 0) { 128 | long int length = ftell(file); 129 | if (length >= 0) { 130 | if (fseek(file, 0, SEEK_SET) == 0) { 131 | buffer_size = (size_t)length; 132 | result = RJD_RESULT_OK(); 133 | } 134 | } 135 | } 136 | } 137 | } else { 138 | result = RJD_RESULT("Failed to open file"); 139 | } 140 | 141 | if (rjd_result_isok(result)) { 142 | buffer = rjd_mem_alloc_array(uint8_t, buffer_size, allocator); 143 | } 144 | 145 | struct rjd_istream stream = 146 | { 147 | .start = buffer, 148 | .end = buffer + buffer_size, 149 | .cursor = buffer, 150 | .userdata = file, 151 | .refill = rjd_istream_refill_file, 152 | .close = rjd_istream_close_file, 153 | }; 154 | stream.result = stream.refill(&stream); 155 | return stream; 156 | } 157 | 158 | struct rjd_result rjd_istream_read(struct rjd_istream* stream, void* buffer, size_t size) 159 | { 160 | RJD_ASSERT(stream); 161 | RJD_ASSERT(buffer); 162 | RJD_ASSERT(size > 0); 163 | 164 | uint8_t* offset_buffer = buffer; 165 | size_t bytes_remaining = size; 166 | while (bytes_remaining > 0) { 167 | if (stream->cursor == stream->end) { 168 | stream->result = stream->refill(stream); 169 | } 170 | RJD_ASSERT(stream->end >= stream->cursor) 171 | size_t buffersize = rjd_math_truncate_u64_to_sizet(stream->end - stream->cursor); 172 | size_t readsize = rjd_math_min(buffersize, bytes_remaining); 173 | 174 | memcpy(offset_buffer, stream->cursor, readsize); 175 | 176 | offset_buffer += readsize; 177 | bytes_remaining -= readsize; 178 | stream->cursor += readsize; 179 | } 180 | 181 | return stream->result; 182 | } 183 | 184 | void rjd_istream_close(struct rjd_istream* stream) 185 | { 186 | RJD_ASSERT(stream); 187 | 188 | if (stream->close) { 189 | stream->close(stream); 190 | } 191 | } 192 | 193 | struct rjd_ostream rjd_ostream_from_memory(void* buffer, size_t size) 194 | { 195 | struct rjd_ostream stream = 196 | { 197 | .type = RJD_OSTREAM_TYPE_MEMORY, 198 | .state = { 199 | .memory = { buffer, (uint32_t)size, 0 }, 200 | }, 201 | }; 202 | return stream; 203 | } 204 | 205 | struct rjd_ostream rjd_ostream_from_file(const char* filepath, enum rjd_ostream_mode mode) 206 | { 207 | const char* writemode = NULL; 208 | switch (mode) 209 | { 210 | case RJD_OSTREAM_MODE_REPLACE: writemode = "wb"; break; 211 | case RJD_OSTREAM_MODE_APPEND: writemode = "ab"; break; 212 | } 213 | RJD_ASSERT(writemode); 214 | FILE* file = fopen(filepath, writemode); 215 | 216 | struct rjd_ostream stream = 217 | { 218 | .type = RJD_OSTREAM_TYPE_FILE, 219 | .state = { 220 | .file = { file }, 221 | }, 222 | }; 223 | return stream; 224 | } 225 | 226 | struct rjd_result rjd_ostream_write(struct rjd_ostream* stream, const void* buffer, size_t size) 227 | { 228 | if (stream->type == RJD_OSTREAM_TYPE_MEMORY) { 229 | struct rjd_ostream_memory* state = &stream->state.memory; 230 | RJD_ASSERT(state->size >= state->cursor); 231 | 232 | const size_t bytes_remaining = rjd_math_truncate_u64_to_sizet(state->size - state->cursor); 233 | if (size > bytes_remaining) { 234 | return RJD_RESULT("attempted to write more data than the buffer can hold"); 235 | } 236 | 237 | const uint8_t* cbuffer = buffer; 238 | RJD_ASSERTMSG(!(cbuffer >= state->buffer && cbuffer < state->buffer + state->size), 239 | "source and destination buffers must not overlap"); 240 | 241 | memcpy(state->buffer + state->cursor, buffer, size); 242 | state->cursor += size; 243 | } else if (stream->type == RJD_OSTREAM_TYPE_FILE) { 244 | struct rjd_ostream_file* state = &stream->state.file; 245 | if (state->file == NULL) { 246 | return RJD_RESULT("failed to open file for writing"); 247 | } 248 | 249 | size_t bytes_written = fwrite(buffer, 1, size, state->file); 250 | if (bytes_written != size) { 251 | return RJD_RESULT("failed to write all data to file"); 252 | } 253 | } 254 | 255 | return RJD_RESULT_OK(); 256 | } 257 | 258 | void rjd_ostream_close(struct rjd_ostream* stream) 259 | { 260 | if (stream->type == RJD_OSTREAM_TYPE_MEMORY) { 261 | stream->state.memory.buffer = NULL; 262 | } else if (stream->type == RJD_OSTREAM_TYPE_FILE) { 263 | fclose(stream->state.file.file); 264 | } 265 | } 266 | 267 | //////////////////////////////////////////////////////////////////////////////// 268 | // static helpers 269 | 270 | static struct rjd_result rjd_istream_fail(struct rjd_istream* stream, const char* reason) 271 | { 272 | stream->result = (struct rjd_result){reason}; 273 | stream->refill = rjd_istream_refill_zeroes; 274 | return stream->refill(stream); 275 | } 276 | 277 | static struct rjd_result rjd_istream_refill_zeroes(struct rjd_istream* stream) 278 | { 279 | static uint8_t zeroes[128] = {0}; 280 | 281 | stream->start = zeroes; 282 | stream->end = zeroes + sizeof(zeroes); 283 | stream->cursor = stream->start; 284 | return stream->result; 285 | } 286 | 287 | static struct rjd_result rjd_istream_refill_memory(struct rjd_istream* stream) 288 | { 289 | return rjd_istream_fail(stream, "reached end of memory buffer"); 290 | } 291 | 292 | static struct rjd_result rjd_istream_refill_file(struct rjd_istream* stream) 293 | { 294 | FILE* file = (FILE*)stream->userdata; 295 | 296 | // cast to non-const since we know the file buffer is ok to write to 297 | void* writable_buffer = (void*)stream->start; 298 | ptrdiff_t bytes_wanted = stream->end - stream->start; 299 | int32_t bytes_read = (int32_t)fread(writable_buffer, 1, bytes_wanted, file); 300 | 301 | RJD_ASSERT(bytes_read <= bytes_wanted); 302 | 303 | if (bytes_read < bytes_wanted) { 304 | if (feof(file)) { 305 | if (bytes_read == 0) { 306 | rjd_istream_close_file(stream); 307 | rjd_istream_fail(stream, "end of file reached, no more data available"); 308 | } else { 309 | stream->end = stream->start + bytes_read; 310 | stream->cursor = stream->start; 311 | stream->result = RJD_RESULT_OK(); 312 | } 313 | } else { 314 | rjd_istream_close_file(stream); 315 | rjd_istream_fail(stream, "error reading file contents into memory"); 316 | } 317 | } else { 318 | if (feof(file)) { 319 | rjd_istream_fail(stream, "end of file reached, no more data available"); 320 | } else { 321 | stream->cursor = stream->start; 322 | } 323 | } 324 | 325 | return stream->result; 326 | } 327 | 328 | static void rjd_istream_close_file(struct rjd_istream* stream) 329 | { 330 | rjd_mem_free(stream->start); 331 | FILE* file = (FILE*)stream->userdata; 332 | if (file) { 333 | fclose(file); 334 | } 335 | 336 | stream->userdata = NULL; 337 | stream->refill = rjd_istream_refill_zeroes; 338 | stream->close = NULL; 339 | 340 | stream->refill(stream); 341 | } 342 | 343 | #endif 344 | 345 | -------------------------------------------------------------------------------- /rjd_strhash.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define RJD_STRHASH_H 1 4 | 5 | struct rjd_strhash 6 | { 7 | struct rjd_hash64 hash; 8 | struct rjd_strref* debug_string; 9 | }; 10 | 11 | void rjd_strhash_global_init(struct rjd_mem_allocator* debug_allocator, uint32_t initial_capacity); 12 | void rjd_strhash_global_destroy(void); 13 | 14 | struct rjd_strhash rjd_strhash_init(const char* str); 15 | bool rjd_strhash_isequal(struct rjd_strhash a, struct rjd_strhash b); 16 | int rjd_strhash_compare(const struct rjd_strhash* a, const struct rjd_strhash* b); 17 | 18 | #define RJD_STRHASH(string) (rjd_strhash_init(string)) 19 | 20 | #if RJD_IMPL 21 | 22 | struct rjd_strpool* g_strhash_strpool; 23 | 24 | void rjd_strhash_global_init(struct rjd_mem_allocator* debug_allocator, uint32_t initial_capacity) 25 | { 26 | if (initial_capacity == 0) { 27 | initial_capacity = 128; 28 | } 29 | 30 | g_strhash_strpool = rjd_mem_alloc(struct rjd_strpool, debug_allocator); 31 | *g_strhash_strpool = rjd_strpool_init(debug_allocator, initial_capacity); 32 | } 33 | 34 | void rjd_strhash_global_destroy(void) 35 | { 36 | rjd_strpool_free(g_strhash_strpool); 37 | rjd_mem_free(g_strhash_strpool); 38 | g_strhash_strpool = NULL; 39 | } 40 | 41 | struct rjd_strhash rjd_strhash_init(const char* str) 42 | { 43 | struct rjd_hash64 hash = rjd_hash64_str(str); 44 | struct rjd_strref* debug_string = NULL; 45 | if (g_strhash_strpool && hash.value != 0) 46 | { 47 | // TODO make threadsafe 48 | debug_string = rjd_strpool_add(g_strhash_strpool, str); 49 | } 50 | 51 | struct rjd_strhash strhash = { 52 | .debug_string = debug_string, 53 | .hash = hash, 54 | }; 55 | return strhash; 56 | } 57 | 58 | bool rjd_strhash_isequal(struct rjd_strhash a, struct rjd_strhash b) 59 | { 60 | return a.hash.value == b.hash.value; 61 | } 62 | 63 | int rjd_strhash_compare(const struct rjd_strhash* a, const struct rjd_strhash* b) 64 | { 65 | return a->hash.value < b->hash.value; 66 | } 67 | 68 | #endif // RJD_IMPL 69 | 70 | -------------------------------------------------------------------------------- /rjd_strpool.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #define RJD_STRPOOL_H 1 3 | 4 | struct rjd_strpool 5 | { 6 | struct rjd_dict storage; 7 | }; 8 | 9 | struct rjd_strref; 10 | 11 | struct rjd_strpool rjd_strpool_init(struct rjd_mem_allocator* allocator, size_t initial_capacity); 12 | void rjd_strpool_free(struct rjd_strpool* pool); 13 | struct rjd_strref* rjd_strpool_add(struct rjd_strpool* pool, const char* str); 14 | struct rjd_strref* rjd_strpool_addf(struct rjd_strpool* pool, const char* fmt, ...); 15 | struct rjd_strref* rjd_strpool_addv(struct rjd_strpool* pool, const char* fmt, va_list args); 16 | struct rjd_strref* rjd_strpool_addl(struct rjd_strpool* pool, const char* inline_string, size_t length); // for non-null-terminated strings 17 | void rjd_strref_release(struct rjd_strref* ref); 18 | const char* rjd_strref_str(const struct rjd_strref* ref); 19 | uint32_t rjd_strref_length(const struct rjd_strref* ref); 20 | 21 | #if RJD_IMPL 22 | 23 | struct rjd_strref 24 | { 25 | const char* str; 26 | struct rjd_strpool* owner; 27 | int32_t refcount; // TODO atomic 28 | uint32_t length; 29 | }; 30 | 31 | static struct rjd_strref* rjd_strpool_addimpl(struct rjd_strpool* pool, const char* str); 32 | 33 | struct rjd_strpool rjd_strpool_init(struct rjd_mem_allocator* allocator, size_t initial_capacity) 34 | { 35 | RJD_ASSERT(allocator); 36 | 37 | struct rjd_strpool pool = { rjd_dict_init(allocator, initial_capacity * 2) }; 38 | return pool; 39 | } 40 | 41 | void rjd_strpool_free(struct rjd_strpool* pool) 42 | { 43 | RJD_ASSERT(pool); 44 | 45 | void** refs = pool->storage.values; 46 | for (uint32_t i = 0; i < rjd_array_count(refs); ++i) { 47 | if (refs[i]) { 48 | struct rjd_strref* ref = refs[i]; 49 | rjd_mem_free(ref); // struct and string are part of the same allocation block 50 | } 51 | } 52 | rjd_dict_free(&pool->storage); 53 | } 54 | 55 | struct rjd_strref* rjd_strpool_add(struct rjd_strpool* pool, const char* str) 56 | { 57 | RJD_ASSERT(pool); 58 | 59 | return rjd_strpool_addimpl(pool, str); 60 | } 61 | 62 | struct rjd_strref* rjd_strpool_addf(struct rjd_strpool* pool, const char* format, ...) 63 | { 64 | RJD_ASSERT(pool); 65 | RJD_ASSERT(format); 66 | 67 | struct rjd_strref* ref = NULL; 68 | 69 | va_list args; 70 | va_start(args, format); 71 | ref = rjd_strpool_addv(pool, format, args); 72 | va_end(args); 73 | 74 | return ref; 75 | } 76 | 77 | struct rjd_strref* rjd_strpool_addv(struct rjd_strpool* pool, const char* format, va_list args) 78 | { 79 | RJD_ASSERT(pool); 80 | RJD_ASSERT(format); 81 | 82 | struct rjd_strref* ref = NULL; 83 | 84 | RJD_STRBUF_SCOPED(buffer, pool->storage.allocator, { 85 | rjd_strbuf_appendv(&buffer, format, args); 86 | ref = rjd_strpool_addimpl(pool, rjd_strbuf_str(&buffer)); 87 | }); 88 | 89 | return ref; 90 | } 91 | 92 | struct rjd_strref* rjd_strpool_addl(struct rjd_strpool* pool, const char* inline_string, size_t length) 93 | { 94 | RJD_ASSERT(pool); 95 | RJD_ASSERT(inline_string); 96 | 97 | struct rjd_strref* ref = NULL; 98 | 99 | RJD_STRBUF_SCOPED(buffer, pool->storage.allocator, { 100 | rjd_strbuf_appendl(&buffer, inline_string, (uint32_t)length); 101 | ref = rjd_strpool_addimpl(pool, rjd_strbuf_str(&buffer)); 102 | }); 103 | 104 | return ref; 105 | } 106 | 107 | void rjd_strref_release(struct rjd_strref* ref) 108 | { 109 | RJD_ASSERT(ref); 110 | 111 | struct rjd_strpool* pool = ref->owner; 112 | 113 | struct rjd_hash64 hash = rjd_hash64_data((const uint8_t*)ref->str, -1); 114 | RJD_ASSERTMSG(rjd_dict_get(&pool->storage, hash) == ref, "ref was not contained in string pool"); 115 | 116 | --ref->refcount; 117 | if (ref->refcount <= 0) { 118 | rjd_mem_free(ref); // struct and string are part of the same allocation block 119 | rjd_dict_erase(&pool->storage, hash); 120 | } 121 | } 122 | 123 | const char* rjd_strref_str(const struct rjd_strref* ref) 124 | { 125 | RJD_ASSERT(ref); 126 | return ref->str; 127 | } 128 | 129 | uint32_t rjd_strref_length(const struct rjd_strref* ref) 130 | { 131 | RJD_ASSERT(ref); 132 | return ref->length; 133 | } 134 | 135 | static struct rjd_strref* rjd_strpool_addimpl(struct rjd_strpool* pool, const char* str) 136 | { 137 | RJD_ASSERT(pool); 138 | 139 | if (!str) { 140 | return NULL; 141 | } 142 | 143 | struct rjd_hash64 hash = rjd_hash64_data((const uint8_t*)str, -1); 144 | struct rjd_strref* ref = rjd_dict_get(&pool->storage, hash); 145 | if (!ref) { 146 | uint8_t* mem = rjd_mem_alloc_array(uint8_t, sizeof(struct rjd_strref) + strlen(str) + 1, pool->storage.allocator); 147 | ref = (struct rjd_strref*)mem; 148 | 149 | char* copied_str = (char*)(mem + sizeof(struct rjd_strref)); 150 | strcpy(copied_str, str); 151 | 152 | ref->str = copied_str; 153 | ref->owner = pool; 154 | ref->refcount = 0; 155 | ref->length = (uint32_t)strlen(ref->str); 156 | 157 | rjd_dict_insert(&pool->storage, hash, ref); 158 | } 159 | ++ref->refcount; 160 | 161 | return ref; 162 | } 163 | 164 | #endif // RJD_IMPL 165 | 166 | -------------------------------------------------------------------------------- /rjd_timer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define RJD_TIMER_H 1 4 | 5 | struct rjd_timer 6 | { 7 | double timestamp; 8 | }; 9 | 10 | struct rjd_timer rjd_timer_init(void); 11 | void rjd_timer_reset(struct rjd_timer* timer); 12 | double rjd_timer_elapsed(const struct rjd_timer* timer); 13 | double rjd_timer_global(void); 14 | 15 | #define RJD_TIMER_SCOPE_BEGIN(name) struct rjd_timer timer_##name = rjd_timer_init(); 16 | #define RJD_TIMER_SCOPE_END_WITHLOG(name, log_function) log_function("Elapsed %s: %.4fms", #name, rjd_timer_elapsed(&timer_##name)); 17 | #define RJD_TIMER_SCOPE_END(name) RJD_TIMER_SCOPE_END_WITHLOG(name, RJD_LOG) 18 | 19 | #if RJD_IMPL 20 | 21 | struct rjd_timer rjd_timer_init(void) 22 | { 23 | struct rjd_timer timer; 24 | rjd_timer_reset(&timer); 25 | return timer; 26 | } 27 | 28 | void rjd_timer_reset(struct rjd_timer* timer) 29 | { 30 | timer->timestamp = rjd_timer_global(); 31 | } 32 | 33 | double rjd_timer_elapsed(const struct rjd_timer* timer) 34 | { 35 | return rjd_timer_global() - timer->timestamp; 36 | } 37 | #if RJD_PLATFORM_WINDOWS 38 | static double RJD_QPC_FREQUENCY = 0; 39 | 40 | double rjd_timer_global(void) 41 | { 42 | if (RJD_QPC_FREQUENCY == 0) { 43 | LARGE_INTEGER frequency = {.QuadPart = 1}; 44 | if (!QueryPerformanceFrequency(&frequency)) 45 | { 46 | RJD_LOG("Failed to get QueryPerformanceFrequency: %d", GetLastError()); 47 | } 48 | RJD_QPC_FREQUENCY = (double)frequency.QuadPart; 49 | } 50 | 51 | LARGE_INTEGER time = { .QuadPart = 0 }; 52 | if (!QueryPerformanceCounter(&time)) 53 | { 54 | RJD_LOG("Failed to get QueryPerformanceCounter. Time will be incorrect. Error: %d", GetLastError()); 55 | } 56 | 57 | return (time.QuadPart * 1000LL) / RJD_QPC_FREQUENCY; 58 | } 59 | #endif //RJD_PLATFORM_WINDOWS 60 | 61 | #if RJD_PLATFORM_OSX 62 | #include 63 | #include 64 | 65 | static mach_timebase_info_data_t RJD_MACH_TIMEBASE_INFO; 66 | double rjd_timer_global(void) 67 | { 68 | if (RJD_MACH_TIMEBASE_INFO.denom == 0) { 69 | int ok = mach_timebase_info(&RJD_MACH_TIMEBASE_INFO); 70 | if (ok != KERN_SUCCESS) { 71 | RJD_LOG("Failed to get mach timebase info: %d", ok); 72 | } 73 | } 74 | 75 | double time = (double)mach_absolute_time(); 76 | return time * ((double)RJD_MACH_TIMEBASE_INFO.numer / ((double)RJD_MACH_TIMEBASE_INFO.denom)) / 1000000; 77 | } 78 | #endif // 79 | 80 | #endif // RJD_IMPL 81 | 82 | -------------------------------------------------------------------------------- /rjd_utf8.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define RJD_UTF8_H 1 4 | 5 | const char* rjd_utf8_bom_skip(const char* string); // expects a NULL-terminated string 6 | struct rjd_result rjd_utf8_bom_write(char* buffer, size_t size); 7 | const char* rjd_utf8_next(const char* string); // returns NULL if string is not UTF8-encoded 8 | 9 | #if RJD_IMPL 10 | 11 | const char* rjd_utf8_bom_skip(const char* string) 12 | { 13 | const uint8_t* s = (const uint8_t*)string; 14 | if (s != NULL && 15 | *(s + 0) == 0xEF && 16 | *(s + 1) == 0xBB && 17 | *(s + 2) == 0xBF) 18 | { 19 | return string + 3; 20 | } 21 | return string; 22 | } 23 | 24 | struct rjd_result rjd_utf8_bom_write(char* buffer, size_t size) 25 | { 26 | if (size <= 3) { 27 | return RJD_RESULT("Buffer must be at least 3 characters long."); 28 | } 29 | 30 | uint8_t* aliased = (uint8_t*)buffer; 31 | 32 | *(aliased + 0) = 0xEF; 33 | *(aliased + 1) = 0xBB; 34 | *(aliased + 2) = 0xBF; 35 | 36 | return RJD_RESULT_OK(); 37 | } 38 | 39 | const char* rjd_utf8_next(const char* string) 40 | { 41 | if (string == NULL || *string == '\0') { 42 | return string; 43 | } 44 | 45 | uint8_t byte = (uint8_t)*string; 46 | 47 | if ((byte >> 7) == 0x00) { 48 | return string + 1; 49 | } else if ((byte >> 5) == 0x06) { // 0b110 50 | return string + 2; 51 | } else if ((byte >> 4) == 0x0E) { // 0b1110 52 | return string + 3; 53 | } else if ((byte >> 3) == 0x1E) { //0b11110 54 | return string + 4; 55 | } 56 | 57 | // if we're in the middle of a character, skip to the end of the current character 58 | bool is_mid_character = (byte >> 6) == 0x02; // 0b10 59 | if (is_mid_character) { 60 | while (is_mid_character) { 61 | ++string; 62 | byte = *string; 63 | if (byte == '\0') { 64 | break; 65 | } 66 | is_mid_character = (byte >> 6) == 0x02; //0b10 67 | } 68 | return string; 69 | } 70 | 71 | return NULL; // invalid UTF8 string 72 | } 73 | 74 | #endif 75 | 76 | -------------------------------------------------------------------------------- /test_data/fio/readonly.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rdunnington/rjd/b5c4d089f2ce9b1bf3766fec759a07d0955a5ec8/test_data/fio/readonly.txt -------------------------------------------------------------------------------- /test_data/fio/writeable.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rdunnington/rjd/b5c4d089f2ce9b1bf3766fec759a07d0955a5ec8/test_data/fio/writeable.txt -------------------------------------------------------------------------------- /test_data/resource/lib/gfx/test.bmp: -------------------------------------------------------------------------------- 1 | 64 2 | 64 3 | 4 | -------------------------------------------------------------------------------- /test_data/resource/lib/gfx/test.material: -------------------------------------------------------------------------------- 1 | gfx/test.shader 2 | gfx/test.bmp 3 | 4 | -------------------------------------------------------------------------------- /test_data/resource/lib/gfx/test.shader: -------------------------------------------------------------------------------- 1 | shader_code_here 2 | -------------------------------------------------------------------------------- /test_data/resource/loader_filesystem/bootstrap.lvl: -------------------------------------------------------------------------------- 1 | bootstrap level 2 | -------------------------------------------------------------------------------- /test_data/resource/loader_filesystem/gfx/invalid.bmp: -------------------------------------------------------------------------------- 1 | placeholder bitmap 2 | -------------------------------------------------------------------------------- /test_data/resource/loader_filesystem/gfx/quad.shader: -------------------------------------------------------------------------------- 1 | shader code here 2 | -------------------------------------------------------------------------------- /test_data/resource/loader_filesystem/init.cfg: -------------------------------------------------------------------------------- 1 | some init data is in here 2 | -------------------------------------------------------------------------------- /test_data/resource/loader_filesystem/levels/dungeon.lvl: -------------------------------------------------------------------------------- 1 | first dungeon level 2 | -------------------------------------------------------------------------------- /test_data/resource/loader_filesystem/levels/mainmenu.lvl: -------------------------------------------------------------------------------- 1 | the main menu and ui 2 | -------------------------------------------------------------------------------- /tests_rjd.c: -------------------------------------------------------------------------------- 1 | #define RJD_IMPL true 2 | #include "tests_rjd_wrapped.h" 3 | 4 | -------------------------------------------------------------------------------- /tests_rjd.m: -------------------------------------------------------------------------------- 1 | #define RJD_IMPL true 2 | #include "tests_rjd_wrapped.h" 3 | 4 | -------------------------------------------------------------------------------- /tests_rjd_wrapped.h: -------------------------------------------------------------------------------- 1 | #define RJD_ENABLE_ASSERT true 2 | #define RJD_ENABLE_LOGGING true 3 | #define RJD_STRBUF_STATIC_SIZE 32 4 | 5 | #define RJD_GFX_BACKEND_NONE 1 6 | 7 | // #if _WIN32 || __CYGWIN__ 8 | // #define RJD_GFX_BACKEND_D3D11 1 9 | // #elif __APPLE__ && __MACH__ 10 | // #define RJD_GFX_BACKEND_METAL 1 11 | // #endif 12 | 13 | 14 | #include "rjd_all.h" 15 | 16 | -------------------------------------------------------------------------------- /todo.txt: -------------------------------------------------------------------------------- 1 | todo: 2 | * rjd_resource: move to a different repo - this is more of an engine feature than a framework thing 3 | * rjd_resource: register fallback resources for certain types 4 | * rjd_resource: have the loader implement a reload notification interface and have rjd_resource_lib poll to perform reloads 5 | * rjd.h: remove and use normal heirarchical #include structure? 6 | * rjd_cmd: no allocations and instead statically configurable 7 | * rjd_mem: option to passthrough linear allocations to heap (can help debug corruption) 8 | * rjd_binrw: swap on big-endianness 9 | 10 | --------------------------------------------------------------------------------