├── .gitignore ├── LICENSE ├── ftg_bitbuffer.h ├── ftg_core.h ├── readme.md └── test └── aireview-prompt.txt /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Object files 5 | *.o 6 | *.ko 7 | *.obj 8 | *.elf 9 | 10 | # Linker output 11 | *.ilk 12 | *.map 13 | *.exp 14 | 15 | # Precompiled Headers 16 | *.gch 17 | *.pch 18 | 19 | # Libraries 20 | *.lib 21 | *.a 22 | *.la 23 | *.lo 24 | 25 | # Shared objects (inc. Windows DLLs) 26 | *.dll 27 | *.so 28 | *.so.* 29 | *.dylib 30 | 31 | # Executables 32 | *.exe 33 | *.out 34 | *.app 35 | *.i*86 36 | *.x86_64 37 | *.hex 38 | 39 | # Debug files 40 | *.dSYM/ 41 | *.su 42 | *.idb 43 | *.pdb 44 | 45 | # Kernel Module Compile Results 46 | *.mod* 47 | *.cmd 48 | .tmp_versions/ 49 | modules.order 50 | Module.symvers 51 | Mkfile.old 52 | dkms.conf 53 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This software is available under 2 licenses -- choose whichever you prefer. 2 | ------------------------------------------------------------------------------ 3 | ALTERNATIVE A - MIT License 4 | Copyright (c) 2023 Frogtoss Games, Inc. 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 9 | of the Software, and to permit persons to whom the Software is furnished to do 10 | so, subject to the following conditions: 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | ------------------------------------------------------------------------------ 21 | ALTERNATIVE B - Public Domain (www.unlicense.org) 22 | This is free and unencumbered software released into the public domain. 23 | Anyone is free to copy, modify, publish, use, compile, sell, or distribute this 24 | software, either in source code form or as a compiled binary, for any purpose, 25 | commercial or non-commercial, and by any means. 26 | In jurisdictions that recognize copyright laws, the author or authors of this 27 | software dedicate any and all copyright interest in the software to the public 28 | domain. We make this dedication for the benefit of the public at large and to 29 | the detriment of our heirs and successors. We intend this dedication to be an 30 | overt act of relinquishment in perpetuity of all present and future rights to 31 | this software under copyright law. 32 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 33 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 34 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 35 | AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 36 | ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 37 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /ftg_bitbuffer.h: -------------------------------------------------------------------------------- 1 | /* ftg_bitbuffer - public domain library 2 | no warranty implied; use at your own risk 3 | 4 | Tightly pack values by bits into a stream of bytes. 5 | 6 | For example, a 1-bit bool and a 32-bit integer are packed into 33 7 | bits. 8 | 9 | Bitbuffers are intended for small amounts of data, like a few 10 | hundred network packets where size is important enough to remove 11 | padding bits, and the cpu overhead of packing/unpacking intermixed 12 | types is not a huge cost. 13 | 14 | FEATURES 15 | 16 | - Compiles C99 warnings-free on clang and visual c++ 17 | 18 | - Pack integers with arbitrary numbers of bits 19 | 20 | - Supports quantized floating point packing 21 | 22 | - Possible to avoid heap allocations and copies on read 23 | 24 | USAGE 25 | 26 | Do this: 27 | #define FTG_IMPLEMENT_BITBUFFER 28 | 29 | before you include this file in one C or C++ file to create the 30 | implementation. 31 | 32 | It should look like this: 33 | #include ... 34 | #include ... 35 | #include ... 36 | #define FTG_IMPLEMENT_BITBUFFER 37 | #include "ftg_bitbuffer.h" 38 | 39 | OPTIONAL 40 | 41 | - Define BITBUF_ASSERT prior to include to override default assert() handler 42 | 43 | REVISION HISTORY 44 | 45 | 1.0 2023-01-17 Initial version 46 | 1.1 2023-01-20 Significant bugfix on read 47 | 1.11 2023-01-24 Fix missing header file error 48 | 49 | USAGE NOTIFICATION REQUEST 50 | 51 | If permitted, emailing the author and notifying him that the 52 | software was used (and how) helps inform him of where he should 53 | spend his time. This step is totally optional, but appreciated! 54 | 55 | AUTHOR 56 | 57 | Michael Labbe https://www.frogtoss.com/pages/contact.html 58 | 59 | LICENSE 60 | 61 | This software is in the public domain. Where that dedication is not 62 | recognized, you are granted a perpetual, irrevocable license to 63 | copy, distribute, and modify this file as you see fit by sole 64 | copyright holder Frogtoss Games, Inc. 65 | 66 | SPECIAL THANKS 67 | 68 | Nick Waanders - quantization functions 69 | */ 70 | 71 | #ifndef BITBUF__INCLUDE_BITBUFFER_H 72 | #define BITBUF__INCLUDE_BITBUFFER_H 73 | 74 | //// DOCUMENTATION 75 | //// 76 | // Known limitations: 77 | // 78 | // - This code does not take any action to manage endianness. 79 | // 80 | // - The buffer size must be known at start; bitbuffers are not stretchy 81 | // 82 | // - The floating point quantization function is not guaranteed to 83 | // output out_min == in_min, or out_max == in_max, except for the 84 | // ranges [0,1] and [-1,1] 85 | // 86 | //// Basic Usage 87 | // 88 | // write values to the buffer 89 | // bitbuf_buffer_t buf = bitbuf_alloc_buffer(256); 90 | // bitbuf_write_bool(&buf, true); 91 | // bitbuf_write_int32(&buf, -32); 92 | // bitbuf_write_cstr(&buf, "hello, world"); 93 | // bitbuf_write_float(&buf, -325.32f); 94 | // 95 | // check for truncation during writes 96 | // assert(!bitbuf_has_truncated(&buf)); 97 | // 98 | // read values from the buffer 99 | // 100 | // a bitbuf_cursor_t aligns to the next bit to read. After 101 | // writing completes, it is thread-safe to have multiple 102 | // read cursors for a single bitbuffer 103 | // 104 | // bitbuf_cursor_t read = bitbuf_cursor_init(&buf); 105 | // assert(bitbuf_read_bool(&read) == true); 106 | // assert(bitbuf_read_int32(&read) == -32); 107 | // 108 | // read a cstring, up until serialized NULL terminator 109 | // char str[256]; 110 | // bitbuf_read_cstr(&read, 256, str); 111 | // assert(strcmp(str, "hello, world") == 0); 112 | // 113 | // assert(bitbuf_read_float(&read) == -325.32f); 114 | // 115 | // check for truncation during reads 116 | // assert(read.read_past_end == 0); 117 | 118 | // free allocated buffer 119 | // bitbuf_free_buffer(&buf); 120 | 121 | #include 122 | #include 123 | #include 124 | 125 | #if defined(__GNUC__) || defined(__clang__) 126 | # define BITBUF_EXT_unused __attribute__((unused)) 127 | #else 128 | # define BITBUF_EXT_unused 129 | #endif 130 | 131 | #ifdef BITBUF_BITBUFFER_STATIC 132 | # define BITBUFDEF static BITBUF_EXT_unused 133 | #else 134 | # define BITBUFDEF extern 135 | #endif 136 | 137 | #if defined(BITBUF_MALLOC) && defined(BITBUF_FREE) 138 | // okay 139 | #elif !defined(BITBUF_MALLOC) && !defined(BITBUF_FREE) 140 | // also okay 141 | #else 142 | # error "Must define both or none of BITBUF_MALLOC and BITBUF_FREE" 143 | #endif 144 | 145 | #ifndef BITBUF_MALLOC 146 | # define BITBUF_MALLOC(size) malloc(size) 147 | # define BITBUF_FREE(ptr) free(ptr) 148 | #endif 149 | 150 | // include ftg_core.h ahead of this header to debug it 151 | #ifdef FTG_ASSERT 152 | # define BITBUF__ASSERT(exp) FTG_ASSERT(exp) 153 | # define BITBUF__ASSERT_FAIL(exp) FTG_ASSERT_FAIL(exp) 154 | #else 155 | # ifdef BITBUF_ASSERT 156 | # define BITBUF__ASSERT(exp) BITBUF_ASSERT(exp) 157 | # define BITBUF__ASSERT_FAIL(exp) BITBUF_ASSERT(0 && exp) 158 | # else 159 | # define BITBUF__ASSERT(exp) (assert(exp)) 160 | # define BITBUF__ASSERT_FAIL(exp) (assert(exp)) 161 | # endif 162 | #endif 163 | 164 | #if defined(__GNUC__) || defined(__clang__) 165 | # if __STDC_VERSION__ < 199901L 166 | # define BITBUF_INLINE __inline 167 | # else 168 | # define BITBUF_INLINE inline 169 | # endif 170 | #elif defined(_MSC_VER) && (_MSC_VER >= 1700) 171 | # define BITBUF_INLINE __inline 172 | #endif 173 | 174 | #ifdef __cplusplus 175 | extern "C" { 176 | #endif 177 | 178 | 179 | // API declaration starts here 180 | 181 | typedef struct bitbuf_buffer_s bitbuf_buffer_t; 182 | 183 | typedef struct { 184 | // seg == data when at beginning 185 | uint64_t* seg; 186 | 187 | // indicates how many bits into seg, <= 63 188 | int bits_into_seg; 189 | 190 | // the owning bitbuffer, or NULL if it's a writer 191 | const bitbuf_buffer_t* owner; 192 | 193 | // set to 1 if an attempt to read past the end of the buffer was 194 | // made 195 | int read_past_end; 196 | } bitbuf_cursor_t; 197 | 198 | struct bitbuf_buffer_s { 199 | uint64_t* data; 200 | size_t capacity_bytes; 201 | int truncated; 202 | 203 | bitbuf_cursor_t write; 204 | }; 205 | 206 | 207 | 208 | // allocate a new buffer for writing 209 | BITBUFDEF bitbuf_buffer_t bitbuf_alloc_buffer(size_t max_bytes); 210 | 211 | // allocate a new buffer, copying *bytes into it 212 | BITBUFDEF bitbuf_buffer_t bitbuf_alloc_buffer_with_bytes(const uint8_t* bytes, 213 | size_t num_bytes); 214 | 215 | 216 | // free a buffer returned from bitbuf_alloc_* 217 | BITBUFDEF void bitbuf_free_buffer(bitbuf_buffer_t*); 218 | 219 | // return a pointer to memory inside bitbuf_buffer_t 220 | // out_num_bytes is set to the number of bytes in *out_data 221 | // 222 | // the pointer to *out_data is made invalid when bitbuf_free_buffer is called on the buffer 223 | BITBUFDEF const uint8_t* bitbuf_get_bytes_from_buffer(const bitbuf_buffer_t*, 224 | size_t* out_num_bytes); 225 | 226 | 227 | 228 | // init a cursor, used for reading from a bitbuffer. 229 | // more than one read cursor can be initialized for a bitbuffer. 230 | // there is no need to free the cursor. 231 | // 232 | // it is illegal to write to a bitbuffer after initializing the first 233 | // cursor 234 | bitbuf_cursor_t bitbuf_cursor_init(bitbuf_buffer_t* buffer); 235 | 236 | 237 | // checks if ANY bitbuf write so far has truncated this bitbuffer. 238 | BITBUFDEF bool bitbuf_has_truncated(const bitbuf_buffer_t*); 239 | 240 | // bitbuf write routines 241 | BITBUFDEF void bitbuf_write_int64(bitbuf_buffer_t*, int64_t value); 242 | BITBUFDEF void bitbuf_write_int32(bitbuf_buffer_t*, int32_t value); 243 | BITBUFDEF void bitbuf_write_int16(bitbuf_buffer_t*, int16_t value); 244 | BITBUFDEF void bitbuf_write_int8(bitbuf_buffer_t*, int8_t value); 245 | BITBUFDEF void bitbuf_write_uint64(bitbuf_buffer_t*, uint64_t value); 246 | BITBUFDEF void bitbuf_write_uint32(bitbuf_buffer_t*, uint32_t value); 247 | BITBUFDEF void bitbuf_write_uint16(bitbuf_buffer_t*, uint16_t value); 248 | BITBUFDEF void bitbuf_write_uint8(bitbuf_buffer_t*, uint8_t value); 249 | BITBUFDEF void bitbuf_write_float(bitbuf_buffer_t*, float value); 250 | BITBUFDEF void bitbuf_write_double(bitbuf_buffer_t*, double value); 251 | BITBUFDEF void bitbuf_write_bool(bitbuf_buffer_t*, bool); 252 | 253 | // write up to strlen(str) + 1 bytes to the bitbuffer, including the null terminator 254 | BITBUFDEF void bitbuf_write_cstr(bitbuf_buffer_t* buf, const char* str); 255 | 256 | // write n bits (up to 64) 257 | BITBUFDEF void bitbuf_write_n_bits(bitbuf_buffer_t* buf, int num_bits, uint64_t value); 258 | 259 | // write a quantized float, using num_bits precision that must be 260 | // between max and min (inclusive) 261 | BITBUFDEF void bitbuf_write_quantized_float( 262 | bitbuf_buffer_t* buf, int num_bits, float min, float max, float value); 263 | 264 | // fill 0-7 bits with zeroes to align write cursor to byte/octet 265 | // 266 | // next write after this call is guaranteed to be byte aligned 267 | // 268 | // in the event that the write cursor is already on the beginning 269 | // of a byte, no bits are written. 270 | // 271 | // bitbuf_skip_byte_padding is the reciprocal function 272 | BITBUFDEF void bitbuf_pad_to_byte(bitbuf_buffer_t* buf); 273 | 274 | // bitbuf read routines 275 | BITBUFDEF int64_t bitbuf_read_int64(bitbuf_cursor_t* read); 276 | BITBUFDEF int32_t bitbuf_read_int32(bitbuf_cursor_t* read); 277 | BITBUFDEF int16_t bitbuf_read_int16(bitbuf_cursor_t* read); 278 | BITBUFDEF int8_t bitbuf_read_int8(bitbuf_cursor_t* read); 279 | BITBUFDEF uint64_t bitbuf_read_uint64(bitbuf_cursor_t* read); 280 | BITBUFDEF uint32_t bitbuf_read_uint32(bitbuf_cursor_t* read); 281 | BITBUFDEF uint16_t bitbuf_read_uint16(bitbuf_cursor_t* read); 282 | BITBUFDEF uint8_t bitbuf_read_uint8(bitbuf_cursor_t* read); 283 | BITBUFDEF float bitbuf_read_float(bitbuf_cursor_t* read); 284 | BITBUFDEF double bitbuf_read_double(bitbuf_cursor_t* read); 285 | BITBUFDEF bool bitbuf_read_bool(bitbuf_cursor_t* read); 286 | 287 | // read n bits (up to 64) 288 | // If non-null, *out_mask contains a bitmask for the returned bits 289 | BITBUFDEF uint64_t bitbuf_read_n_bits(bitbuf_cursor_t* read, 290 | int num_bits, 291 | uint64_t* out_mask); 292 | 293 | // read up to max_bytes from the bitbuffer including null terminator, 294 | // putting the result in out_str. if max_bytes is reached, 295 | // strlen(out_str) == 0 and the read cursor points at the last 296 | // position read (not reset). 297 | BITBUFDEF void bitbuf_read_cstr(bitbuf_cursor_t* read, size_t max_bytes, char* out_str); 298 | 299 | 300 | // skip byte padding generated by bitbuf_pad_to_byte 301 | BITBUFDEF void bitbuf_skip_byte_padding(bitbuf_cursor_t* read); 302 | 303 | // read a quantized float 304 | BITBUFDEF float 305 | bitbuf_read_quantized_float(bitbuf_cursor_t* read, int num_bits, float min, float max); 306 | 307 | // advanced: initialize a buffer with *bytes, avoiding buffer allocation and 308 | // a copy. num_bytes must be a multiple of 8. 309 | // 310 | // do not call bitbuf_free_buffer() on the returned buffer. 311 | BITBUFDEF bitbuf_buffer_t bitbuf_init_buffer_with_bytes(const uint8_t* bytes, 312 | size_t num_bytes); 313 | 314 | // 315 | // End of header file 316 | // 317 | #endif /* BITBUF__INCLUDE_BITBUFFER_H */ 318 | 319 | #ifdef __cplusplus 320 | } 321 | #endif 322 | 323 | 324 | /* implementation */ 325 | #if defined(FTG_IMPLEMENT_BITBUFFER) 326 | 327 | #include 328 | #include 329 | 330 | #define BITBUF__SEG_BITS 64 331 | 332 | #define BITBUF__ALIGN_DOWN(n, a) ((n) & ~((a)-1)) 333 | #define BITBUF__ALIGN_UP(n, a) BITBUF__ALIGN_DOWN((n) + (a)-1, (a)) 334 | #define BITBUF__ALIGN_UP_DELTA(n, a) (((a) - (n)) & ((a)-1)) 335 | 336 | #define BITBUF__MAX(a, b) ((a) > (b) ? (a) : (b)) 337 | #define BITBUF__MIN(a, b) ((a) < (b) ? (a) : (b)) 338 | 339 | #define BITBUF__ASSERT_NO_WRITE_AFTER_READS(BUF) \ 340 | BITBUF__ASSERT((BUF)->write.owner == NULL) 341 | 342 | #define BITBUF__PUN(in_type) \ 343 | union pun_u { \ 344 | in_type value; \ 345 | uint64_t u64; \ 346 | } pun; 347 | 348 | #define BITBUF__READ_TYPE(in_type, bit_count) \ 349 | BITBUF__PUN(in_type) \ 350 | pun.u64 = bitbuf__read_bits(read, bit_count); 351 | 352 | #define BITBUF__WRITE_TYPE(BUF, in_type) \ 353 | BITBUF__PUN(in_type) \ 354 | pun.value = value; \ 355 | bitbuf__write_bits(BUF, pun.u64, sizeof(in_type) * 8); 356 | 357 | #define BITBUF__DECL_WRITE(in_type, in_name) \ 358 | BITBUFDEF void bitbuf_write_##in_name(bitbuf_buffer_t* buf, in_type value) \ 359 | { \ 360 | BITBUF__WRITE_TYPE(buf, in_type); \ 361 | } 362 | 363 | #define BITBUF__DECL_WRITE_T(in_type) BITBUF__DECL_WRITE(in_type##_t, in_type) 364 | 365 | #define BITBUF__DECL_READ(in_type, in_name) \ 366 | BITBUFDEF in_type bitbuf_read_##in_name(bitbuf_cursor_t* read) \ 367 | { \ 368 | BITBUF__READ_TYPE(in_type, sizeof(in_type) * 8); \ 369 | return pun.value; \ 370 | } 371 | 372 | #define BITBUF__DECL_READ_T(in_type) BITBUF__DECL_READ(in_type##_t, in_type) 373 | 374 | /* clang-format off */ 375 | static const uint64_t bitbuf__spanmasktable[65] = { 376 | 0, 377 | (1ull << 1) - 1, (1ull << 2) - 1, (1ull << 3) - 1, (1ull << 4) - 1, 378 | (1ull << 5) - 1, (1ull << 6) - 1, (1ull << 7) - 1, (1ull << 8) - 1, 379 | (1ull << 9) - 1, (1ull << 10) - 1, (1ull << 11) - 1, (1ull << 12) - 1, 380 | (1ull << 13) - 1, (1ull << 14) - 1, (1ull << 15) - 1, (1ull << 16) - 1, 381 | (1ull << 17) - 1, (1ull << 18) - 1, (1ull << 19) - 1, (1ull << 20) - 1, 382 | (1ull << 21) - 1, (1ull << 22) - 1, (1ull << 23) - 1, (1ull << 24) - 1, 383 | (1ull << 25) - 1, (1ull << 26) - 1, (1ull << 27) - 1, (1ull << 28) - 1, 384 | (1ull << 29) - 1, (1ull << 30) - 1, (1ull << 31) - 1, (1ull << 32) - 1, 385 | (1ull << 33) - 1, (1ull << 34) - 1, (1ull << 35) - 1, (1ull << 36) - 1, 386 | (1ull << 37) - 1, (1ull << 38) - 1, (1ull << 39) - 1, (1ull << 40) - 1, 387 | (1ull << 41) - 1, (1ull << 42) - 1, (1ull << 43) - 1, (1ull << 44) - 1, 388 | (1ull << 45) - 1, (1ull << 46) - 1, (1ull << 47) - 1, (1ull << 48) - 1, 389 | (1ull << 49) - 1, (1ull << 50) - 1, (1ull << 51) - 1, (1ull << 52) - 1, 390 | (1ull << 53) - 1, (1ull << 54) - 1, (1ull << 55) - 1, (1ull << 56) - 1, 391 | (1ull << 57) - 1, (1ull << 58) - 1, (1ull << 59) - 1, (1ull << 60) - 1, 392 | (1ull << 61) - 1, (1ull << 62) - 1, 0x7fffffffffffffff, 0xffffffffffffffff, 393 | }; 394 | /* clang-format on */ 395 | 396 | BITBUFDEF bitbuf_buffer_t 397 | bitbuf_alloc_buffer(size_t max_bytes) 398 | { 399 | bitbuf_buffer_t buffer; 400 | 401 | BITBUF__ASSERT(max_bytes > 0); 402 | 403 | // allocate to next segment 404 | buffer.capacity_bytes = BITBUF__ALIGN_UP(max_bytes, 8); 405 | 406 | buffer.data = (uint64_t*)BITBUF_MALLOC(buffer.capacity_bytes); 407 | memset(buffer.data, 0, buffer.capacity_bytes); 408 | 409 | buffer.write.seg = buffer.data; 410 | buffer.write.bits_into_seg = 0; 411 | buffer.write.owner = NULL; 412 | 413 | buffer.truncated = 0; 414 | 415 | return buffer; 416 | } 417 | 418 | BITBUFDEF bitbuf_buffer_t 419 | bitbuf_alloc_buffer_with_bytes(const uint8_t* bytes, size_t num_bytes) 420 | { 421 | // perf: zeroes memory, then copies over it 422 | bitbuf_buffer_t buffer = bitbuf_alloc_buffer(num_bytes); 423 | memcpy(buffer.data, bytes, sizeof(uint8_t) * num_bytes); 424 | 425 | size_t segment = num_bytes / sizeof(uint64_t); 426 | int bits_into_seg = (num_bytes % sizeof(uint64_t)) * 8; 427 | 428 | buffer.write.seg = buffer.data + segment; 429 | buffer.write.bits_into_seg = bits_into_seg; 430 | 431 | return buffer; 432 | } 433 | 434 | BITBUFDEF bitbuf_buffer_t 435 | bitbuf_init_buffer_with_bytes(const uint8_t* bytes, size_t num_bytes) 436 | { 437 | BITBUF__ASSERT((num_bytes % 8) == 0); 438 | 439 | bitbuf_buffer_t buffer; 440 | 441 | buffer.data = (uint64_t*)bytes; 442 | buffer.capacity_bytes = num_bytes; 443 | 444 | buffer.write.seg = buffer.data; 445 | buffer.write.bits_into_seg = 0; 446 | buffer.write.owner = NULL; 447 | 448 | buffer.truncated = 0; 449 | 450 | return buffer; 451 | } 452 | 453 | BITBUFDEF bool 454 | bitbuf_has_truncated(const bitbuf_buffer_t* buffer) 455 | { 456 | return buffer->truncated == 1; 457 | } 458 | 459 | BITBUFDEF void 460 | bitbuf_free_buffer(bitbuf_buffer_t* buffer) 461 | { 462 | // if this is hit, the buffer overflowed at some point, were 463 | // truncated. Increase bitbuffer storage size. 464 | // 465 | // Set (buffer->truncated = 0) prior to free if this is expected 466 | // behaviour. 467 | #ifndef FTGT_TESTS_ENABLED 468 | BITBUF__ASSERT(!bitbuf_has_truncated(buffer)); 469 | #endif 470 | 471 | BITBUF_FREE(buffer->data); 472 | } 473 | 474 | BITBUFDEF const uint8_t* 475 | bitbuf_get_bytes_from_buffer(const bitbuf_buffer_t* buf, size_t* out_num_bytes) 476 | { 477 | BITBUF__ASSERT(out_num_bytes); 478 | 479 | *out_num_bytes = (buf->write.seg - buf->data) * sizeof(uint64_t); 480 | *out_num_bytes += BITBUF__ALIGN_UP(buf->write.bits_into_seg, 8) / 8; 481 | 482 | return (const uint8_t*)buf->data; 483 | } 484 | 485 | BITBUFDEF bitbuf_cursor_t 486 | bitbuf_cursor_init(bitbuf_buffer_t* buffer) 487 | { 488 | // set the writer owner to a legal non-NULL value, indicating that 489 | // writing is complete. 490 | buffer->write.owner = buffer; 491 | 492 | return (bitbuf_cursor_t){ 493 | .seg = buffer->data, 494 | .bits_into_seg = 0, 495 | .owner = buffer, 496 | }; 497 | } 498 | 499 | static ptrdiff_t 500 | bitbuf__remaining_capacity_in_bits(const bitbuf_buffer_t* buffer) 501 | { 502 | BITBUF__ASSERT(buffer->write.bits_into_seg < BITBUF__SEG_BITS); 503 | 504 | size_t completed_seg_bits = (buffer->write.seg - buffer->data) * BITBUF__SEG_BITS; 505 | size_t remaining_seg_bits = BITBUF__SEG_BITS - buffer->write.bits_into_seg; 506 | BITBUF__ASSERT(remaining_seg_bits <= BITBUF__SEG_BITS); 507 | size_t total_bits_used = completed_seg_bits + buffer->write.bits_into_seg; 508 | 509 | size_t capacity_bits = buffer->capacity_bytes * 8; 510 | BITBUF__ASSERT(capacity_bits >= total_bits_used); 511 | 512 | return (ptrdiff_t)capacity_bits - total_bits_used; 513 | } 514 | 515 | static ptrdiff_t 516 | bitbuf__bits_remaining_for_cursor(const bitbuf_buffer_t* buffer, 517 | const bitbuf_cursor_t* cursor) 518 | { 519 | ptrdiff_t remaining_segs = (buffer->capacity_bytes / sizeof(uint64_t)) - 520 | (cursor->seg - buffer->data); 521 | BITBUF__ASSERT(remaining_segs >= 0); 522 | 523 | int remaining_bits = BITBUF__SEG_BITS - cursor->bits_into_seg; 524 | 525 | return (remaining_segs * BITBUF__SEG_BITS) + remaining_bits; 526 | } 527 | 528 | static bool 529 | bitbuf__is_valid_read_cursor(const bitbuf_cursor_t* cursor) 530 | { 531 | return (cursor && cursor->owner && cursor->seg >= cursor->owner->data && 532 | bitbuf__bits_remaining_for_cursor(cursor->owner, cursor) >= 0); 533 | } 534 | 535 | static BITBUF_INLINE void 536 | bitbuf__advance_cursor(bitbuf_cursor_t* cursor) 537 | { 538 | cursor->bits_into_seg = 0; 539 | cursor->seg++; 540 | } 541 | 542 | BITBUFDEF void 543 | bitbuf__write_bits(bitbuf_buffer_t* buffer, uint64_t datum, int num_bits) 544 | { 545 | BITBUF__ASSERT(num_bits <= 64); 546 | if (num_bits > 64) 547 | return; 548 | 549 | // if this is hit, a call to bitbuf_init_cursor() (to begin reading) has 550 | // occurred and a subsequent write was attempted. 551 | BITBUF__ASSERT_NO_WRITE_AFTER_READS(buffer); 552 | 553 | if (bitbuf__remaining_capacity_in_bits(buffer) < num_bits) { 554 | BITBUF__ASSERT_FAIL("out of space writing bits"); 555 | buffer->truncated |= 1; 556 | return; 557 | } 558 | 559 | 560 | int bits_remaining_in_seg = BITBUF__SEG_BITS - buffer->write.bits_into_seg; 561 | 562 | // do the bits fit in the current seg? 563 | if (num_bits <= bits_remaining_in_seg) { 564 | const uint64_t SRC_MASK = bitbuf__spanmasktable[num_bits]; 565 | *buffer->write.seg |= (datum & SRC_MASK) << buffer->write.bits_into_seg; 566 | 567 | buffer->write.bits_into_seg += num_bits; 568 | 569 | if (buffer->write.bits_into_seg == BITBUF__SEG_BITS) { 570 | bitbuf__advance_cursor(&buffer->write); 571 | 572 | BITBUF__ASSERT(bitbuf__remaining_capacity_in_bits(buffer) >= 0); 573 | } 574 | } else { 575 | // no - write the bits for the current segment and call recursively 576 | // to do the remainder 577 | const uint64_t SRC_MASK = bitbuf__spanmasktable[bits_remaining_in_seg]; 578 | *buffer->write.seg |= (datum & SRC_MASK) 579 | << (BITBUF__SEG_BITS - bits_remaining_in_seg); 580 | 581 | bitbuf__advance_cursor(&buffer->write); 582 | 583 | int num_bits_remaining_for_next_write = num_bits - bits_remaining_in_seg; 584 | BITBUF__ASSERT(num_bits_remaining_for_next_write < BITBUF__SEG_BITS); 585 | const uint64_t OVER_MASK = 586 | bitbuf__spanmasktable[num_bits_remaining_for_next_write] 587 | << (bits_remaining_in_seg); 588 | bitbuf__write_bits(buffer, 589 | (datum & OVER_MASK) >> bits_remaining_in_seg, 590 | num_bits_remaining_for_next_write); 591 | } 592 | } 593 | 594 | // read up to 64 bits into *out_bits 595 | BITBUFDEF uint64_t 596 | bitbuf__read_bits(bitbuf_cursor_t* read, int num_bits) 597 | { 598 | const bitbuf_buffer_t* buffer = read->owner; 599 | 600 | BITBUF__ASSERT(bitbuf__is_valid_read_cursor(read)); 601 | BITBUF__ASSERT(read->seg); 602 | 603 | BITBUF__ASSERT(num_bits <= 64); 604 | if (num_bits > 64) { 605 | return 0; 606 | } 607 | 608 | if (bitbuf__bits_remaining_for_cursor(buffer, read) < num_bits) { 609 | BITBUF__ASSERT_FAIL("read past end of buffer"); 610 | read->read_past_end |= 1; 611 | return 0; 612 | } 613 | 614 | int bits_remaining_in_seg = BITBUF__SEG_BITS - read->bits_into_seg; 615 | 616 | // are there enough bits in the current seg? 617 | if (num_bits <= bits_remaining_in_seg) { 618 | const uint64_t DST_MASK = bitbuf__spanmasktable[num_bits] << read->bits_into_seg; 619 | 620 | uint64_t val = (*read->seg & DST_MASK) >> read->bits_into_seg; 621 | 622 | read->bits_into_seg += num_bits; 623 | 624 | if (read->bits_into_seg == BITBUF__SEG_BITS) { 625 | bitbuf__advance_cursor(read); 626 | } 627 | 628 | return val; 629 | } else { 630 | // no - read the bits for the current segment and then 631 | // subsequently read the rest 632 | const uint64_t DST_MASK = bitbuf__spanmasktable[bits_remaining_in_seg] 633 | << (BITBUF__SEG_BITS - bits_remaining_in_seg); 634 | 635 | uint64_t val = 636 | (*read->seg & DST_MASK) >> (BITBUF__SEG_BITS - bits_remaining_in_seg); 637 | 638 | bitbuf__advance_cursor(read); 639 | int next_read_num_bits = num_bits - bits_remaining_in_seg; 640 | BITBUF__ASSERT(next_read_num_bits < BITBUF__SEG_BITS); 641 | BITBUF__ASSERT(bitbuf__bits_remaining_for_cursor(buffer, read) >= num_bits); 642 | 643 | uint64_t OVER_MASK = bitbuf__spanmasktable[next_read_num_bits]; 644 | 645 | val |= (*read->seg & OVER_MASK) << bits_remaining_in_seg; 646 | read->bits_into_seg += next_read_num_bits; 647 | 648 | return val; 649 | } 650 | } 651 | 652 | BITBUF__DECL_WRITE_T(int64); 653 | BITBUF__DECL_WRITE_T(int32); 654 | BITBUF__DECL_WRITE_T(int16); 655 | BITBUF__DECL_WRITE_T(int8); 656 | BITBUF__DECL_WRITE_T(uint64); 657 | BITBUF__DECL_WRITE_T(uint32); 658 | BITBUF__DECL_WRITE_T(uint16); 659 | BITBUF__DECL_WRITE_T(uint8); 660 | BITBUF__DECL_WRITE(float, float); 661 | BITBUF__DECL_WRITE(double, double); 662 | 663 | BITBUFDEF void 664 | bitbuf_write_bool(bitbuf_buffer_t* buf, bool value) 665 | { 666 | BITBUF__PUN(bool); 667 | pun.value = value; 668 | bitbuf__write_bits(buf, pun.u64, 1); 669 | } 670 | 671 | BITBUFDEF void 672 | bitbuf_write_cstr(bitbuf_buffer_t* buf, const char* str) 673 | { 674 | const char* p = str; 675 | 676 | while (*p) { 677 | // perf: write n bits to align, then power through 64-bits at a time 678 | // until the last 64 bits 679 | bitbuf__write_bits(buf, *p, 8); 680 | p++; 681 | } 682 | 683 | // write null terminator 684 | bitbuf__write_bits(buf, 0, 8); 685 | } 686 | 687 | BITBUFDEF void 688 | bitbuf_write_n_bits(bitbuf_buffer_t* buf, int num_bits, uint64_t value) 689 | { 690 | BITBUF__ASSERT(num_bits <= 64); 691 | if (num_bits > 64) { 692 | return; 693 | } 694 | 695 | // if this is hit, value has set bits that are being chopped off 696 | BITBUF__ASSERT((value & (~bitbuf__spanmasktable[num_bits])) == 0); 697 | 698 | bitbuf__write_bits(buf, value, num_bits); 699 | } 700 | 701 | 702 | 703 | BITBUFDEF void 704 | bitbuf_write_quantized_float(bitbuf_buffer_t* buf, int num_bits, float min, float max, float value) 705 | { 706 | BITBUF__ASSERT((size_t)num_bits <= (sizeof(float) * 8) - 1); 707 | BITBUF__ASSERT(min < max); 708 | BITBUF__ASSERT(value >= min && value <= max); 709 | 710 | const uint32_t bit_max = (uint32_t)bitbuf__spanmasktable[num_bits]; 711 | 712 | float qf = 713 | BITBUF__MIN(BITBUF__MAX(((value - min) * bit_max) / (max - min), 0), bit_max); 714 | uint64_t qi = (uint64_t)qf; 715 | 716 | // expr '(value - min) * mult' performed as floating point may result in one additional 717 | // bit, causing qi to exceed num_bits and failing to represent saturation. 718 | qi = qi && (qi & bit_max) == 0 ? bit_max : qi; 719 | 720 | bitbuf__write_bits(buf, qi, num_bits); 721 | } 722 | 723 | 724 | BITBUFDEF void 725 | bitbuf_pad_to_byte(bitbuf_buffer_t* buf) 726 | { 727 | int align_bits = BITBUF__ALIGN_UP_DELTA(buf->write.bits_into_seg, 8); 728 | if (align_bits != 0) { 729 | uint64_t zero = 0; 730 | 731 | bitbuf__write_bits(buf, zero, align_bits); 732 | } 733 | } 734 | 735 | BITBUF__DECL_READ_T(int64); 736 | BITBUF__DECL_READ_T(int32); 737 | BITBUF__DECL_READ_T(int16); 738 | BITBUF__DECL_READ_T(int8); 739 | BITBUF__DECL_READ_T(uint64); 740 | BITBUF__DECL_READ_T(uint32); 741 | BITBUF__DECL_READ_T(uint16); 742 | BITBUF__DECL_READ_T(uint8); 743 | BITBUF__DECL_READ(float, float); 744 | BITBUF__DECL_READ(double, double); 745 | 746 | BITBUFDEF bool 747 | bitbuf_read_bool(bitbuf_cursor_t* read) 748 | { 749 | BITBUF__READ_TYPE(bool, 1); 750 | return pun.value; 751 | } 752 | 753 | BITBUFDEF void 754 | bitbuf_read_cstr(bitbuf_cursor_t* read, size_t max_bytes, char* out_str) 755 | { 756 | size_t i; 757 | for (i = 0; i < max_bytes; i++) { 758 | out_str[i] = (char)bitbuf__read_bits(read, 8); 759 | 760 | if (out_str[i] == '\0') 761 | return; 762 | } 763 | 764 | // null terminator not found -- terminate string 765 | out_str[0] = '\0'; 766 | } 767 | 768 | BITBUFDEF uint64_t 769 | bitbuf_read_n_bits(bitbuf_cursor_t* read, int num_bits, uint64_t* out_mask) 770 | { 771 | BITBUF__ASSERT(num_bits <= 64); 772 | if (num_bits > 64) { 773 | return 0; 774 | } 775 | 776 | uint64_t datum = bitbuf__read_bits(read, num_bits); 777 | 778 | if (out_mask) { 779 | *out_mask = bitbuf__spanmasktable[num_bits]; 780 | } 781 | 782 | return datum; 783 | } 784 | 785 | BITBUFDEF void 786 | bitbuf_skip_byte_padding(bitbuf_cursor_t* read) 787 | { 788 | BITBUF__ASSERT(bitbuf__is_valid_read_cursor(read)); 789 | 790 | read->bits_into_seg = BITBUF__ALIGN_UP(read->bits_into_seg, 8); 791 | 792 | BITBUF__ASSERT(read->bits_into_seg <= BITBUF__SEG_BITS); 793 | 794 | if (read->bits_into_seg == BITBUF__SEG_BITS) { 795 | bitbuf__advance_cursor(read); 796 | } 797 | 798 | BITBUF__ASSERT(bitbuf__bits_remaining_for_cursor(read->owner, read) >= 0); 799 | } 800 | 801 | 802 | BITBUFDEF float 803 | bitbuf_read_quantized_float(bitbuf_cursor_t* read, int num_bits, float min, float max) 804 | { 805 | BITBUF__ASSERT((size_t)num_bits <= (sizeof(float) * 8) - 1); 806 | BITBUF__ASSERT(min < max); 807 | BITBUF__ASSERT(num_bits <= 31); 808 | 809 | uint64_t value = bitbuf_read_n_bits(read, num_bits, NULL); 810 | const uint32_t bit_max = (uint32_t)bitbuf__spanmasktable[num_bits]; 811 | 812 | float q = min + (((float)value / bit_max) * (max - min)); 813 | BITBUF__ASSERT(q >= min && q <= max); 814 | 815 | return q; 816 | } 817 | 818 | // tests follow -- this is intended to be ran by a core developer 819 | // who includes ftg_test.h, a test harness. 820 | #ifdef FTGT_TESTS_ENABLED 821 | 822 | struct bitbuf_testvars_s { 823 | bitbuf_buffer_t buf; 824 | }; 825 | 826 | static struct bitbuf_testvars_s bitbuf__tv; 827 | 828 | static int 829 | bitbuf__test_setup(void) 830 | { 831 | bitbuf__tv.buf = bitbuf_alloc_buffer(256); 832 | return 0; /* setup success */ 833 | } 834 | 835 | static int 836 | bitbuf__test_teardown(void) 837 | { 838 | bitbuf_free_buffer(&bitbuf__tv.buf); 839 | return 0; 840 | } 841 | 842 | static int 843 | bitbuf__test_basic(void) 844 | { 845 | bitbuf_buffer_t* buf = &bitbuf__tv.buf; 846 | 847 | // writes 848 | bitbuf_write_bool(buf, true); 849 | bitbuf_pad_to_byte(buf); 850 | bitbuf_write_int64(buf, -32); 851 | bitbuf_write_cstr(buf, "hello, world"); 852 | bitbuf_write_float(buf, -325.32f); 853 | bitbuf_write_n_bits(buf, 4, 13); 854 | bitbuf_pad_to_byte(buf); 855 | bitbuf_write_n_bits(buf, 7, 121); 856 | 857 | // reads 858 | bitbuf_cursor_t read = bitbuf_cursor_init(buf); 859 | 860 | TEST(bitbuf_read_bool(&read) == true); 861 | bitbuf_skip_byte_padding(&read); 862 | TEST(bitbuf_read_int64(&read) == -32); 863 | 864 | char str[256]; 865 | bitbuf_cursor_t read2 = read; 866 | bitbuf_read_cstr(&read, 256, str); 867 | TEST(strcmp(str, "hello, world") == 0); 868 | 869 | // no room for null terminator 870 | bitbuf_read_cstr(&read2, strlen("hello, world"), str); 871 | TEST(str[0] == '\0'); 872 | 873 | TEST(bitbuf_read_float(&read) == -325.32f); 874 | 875 | uint64_t mask; 876 | TEST(bitbuf_read_n_bits(&read, 4, &mask) == 13); 877 | TEST(mask == 15); 878 | 879 | bitbuf_skip_byte_padding(&read); 880 | 881 | TEST(bitbuf_read_n_bits(&read, 7, NULL) == 121); 882 | 883 | return ftgt_test_errorlevel(); 884 | } 885 | 886 | static int 887 | bitbuf__test_buffer_overflow(void) 888 | { 889 | // write non-overflow -- cursor aligns to end of only segment, but 890 | // does not overflow 891 | { 892 | bitbuf_buffer_t buf = bitbuf_alloc_buffer(1); 893 | bitbuf_write_uint64(&buf, 0xFF); 894 | TEST(!bitbuf_has_truncated(&buf)); 895 | bitbuf_free_buffer(&buf); 896 | } 897 | 898 | // write overflow on non-aligned write 899 | { 900 | // this allocates 64 bits because bitbuffer rounds up to segment width. 901 | // we then write 65 bits, and expect a truncation. 902 | bitbuf_buffer_t buf = bitbuf_alloc_buffer(1); 903 | bitbuf_write_bool(&buf, true); 904 | bitbuf_write_uint64(&buf, 0xFF); 905 | 906 | // expect an assert to be triggered in previous bitbuf call 907 | TEST(ftgt_test_errorlevel()); 908 | 909 | TEST(bitbuf_has_truncated(&buf)); 910 | buf.truncated = 0; 911 | bitbuf_free_buffer(&buf); 912 | } 913 | 914 | // read non-overflow -- cursor aligns to end of single byte 915 | { 916 | bitbuf_buffer_t buf = bitbuf_alloc_buffer(1); 917 | bitbuf_write_uint64(&buf, 0xFF); 918 | 919 | 920 | bitbuf_cursor_t read = bitbuf_cursor_init(&buf); 921 | uint64_t value = bitbuf_read_uint64(&read); 922 | TEST(value == 0xFF); 923 | 924 | bitbuf_free_buffer(&buf); 925 | } 926 | 927 | // read overflow 928 | { 929 | bitbuf_buffer_t buf = bitbuf_alloc_buffer(1); 930 | bitbuf_write_uint64(&buf, 0xFF); 931 | 932 | bitbuf_cursor_t read = bitbuf_cursor_init(&buf); 933 | uint64_t value; 934 | value = bitbuf_read_uint64(&read); 935 | TEST(value == 0xFF); 936 | 937 | // read past end 938 | bitbuf_read_uint64(&read); 939 | value = bitbuf_read_uint64(&read); 940 | TEST(read.read_past_end == 1); 941 | TEST(ftgt_test_errorlevel()); 942 | TEST(value == 0); 943 | 944 | bitbuf_free_buffer(&buf); 945 | } 946 | 947 | return ftgt_test_errorlevel(); 948 | } 949 | 950 | static int 951 | bitbuf__test_align_to_end_of_segment(void) 952 | { 953 | bitbuf_buffer_t buf = bitbuf_alloc_buffer(16); 954 | 955 | uint64_t BIG = 0x7FFFFFFFFFFFFFFFull; 956 | 957 | bitbuf_write_n_bits(&buf, 63, BIG); 958 | bitbuf_pad_to_byte(&buf); 959 | bitbuf_write_int32(&buf, -500000); 960 | 961 | bitbuf_cursor_t read = bitbuf_cursor_init(&buf); 962 | 963 | uint64_t value = bitbuf_read_n_bits(&read, 63, NULL); 964 | TEST(value == BIG); 965 | 966 | bitbuf_skip_byte_padding(&read); 967 | TEST(bitbuf_read_int32(&read) == -500000); 968 | 969 | bitbuf_free_buffer(&buf); 970 | 971 | return ftgt_test_errorlevel(); 972 | } 973 | 974 | static int 975 | bitbuf__test_write_after_read(void) 976 | { 977 | bitbuf_buffer_t* buf = &bitbuf__tv.buf; 978 | 979 | bitbuf_cursor_init(buf); 980 | bitbuf_write_bool(buf, false); 981 | 982 | TEST(ftgt_test_errorlevel()); 983 | 984 | return ftgt_test_errorlevel(); 985 | } 986 | 987 | static int 988 | bitbuf__test_crop_set_bit_on_n_write(void) 989 | { 990 | bitbuf_write_n_bits(&bitbuf__tv.buf, 1, 3); 991 | TEST(ftgt_test_errorlevel()); 992 | 993 | return ftgt_test_errorlevel(); 994 | } 995 | 996 | static int 997 | bitbuf__test_cstr_overflow(void) 998 | { 999 | bitbuf_buffer_t buf = bitbuf_alloc_buffer(5); 1000 | 1001 | // implementation detail dependency 1002 | TEST(buf.capacity_bytes == 8); 1003 | 1004 | // catch assert for overflow on null terminator 1005 | bitbuf_write_cstr(&buf, "abcdefgh"); 1006 | 1007 | TEST(ftgt_test_errorlevel()); 1008 | TEST(bitbuf_has_truncated(&buf)); 1009 | bitbuf_free_buffer(&buf); 1010 | 1011 | return ftgt_test_errorlevel(); 1012 | } 1013 | 1014 | static int 1015 | bitbuf__test_read_buffers(void) 1016 | { 1017 | const char STR[] = "abcdefgh"; 1018 | 1019 | { 1020 | size_t i; 1021 | bitbuf_buffer_t buf = 1022 | bitbuf_alloc_buffer_with_bytes((uint8_t*)STR, strlen(STR)); 1023 | bitbuf_cursor_t read = bitbuf_cursor_init(&buf); 1024 | 1025 | for (i = 0; i < strlen(STR); i++) { 1026 | TEST(bitbuf_read_uint8(&read) == (uint8_t)STR[i]); 1027 | } 1028 | 1029 | bitbuf_free_buffer(&buf); 1030 | 1031 | return ftgt_test_errorlevel(); 1032 | } 1033 | 1034 | { 1035 | size_t i; 1036 | bitbuf_buffer_t buf = 1037 | bitbuf_init_buffer_with_bytes((uint8_t*)STR, strlen(STR)); 1038 | bitbuf_cursor_t read = bitbuf_cursor_init(&buf); 1039 | 1040 | for (i = 0; i < strlen(STR); i++) { 1041 | TEST(bitbuf_read_uint8(&read) == (uint8_t)STR[i]); 1042 | } 1043 | 1044 | return ftgt_test_errorlevel(); 1045 | } 1046 | } 1047 | 1048 | 1049 | static int 1050 | bitbuf__test_qfloat(void) 1051 | { 1052 | const int STORE_BITS[] = {4, 8, 16, 24, 31}; 1053 | const size_t STORE_BITS_LEN = sizeof(STORE_BITS) / sizeof(STORE_BITS[0]); 1054 | size_t i; 1055 | 1056 | 1057 | const float TEST_RANGES[][3] = { 1058 | /* min, max */ 1059 | {+0.0f, +1.0f}, 1060 | {-1.0, 0.0f}, 1061 | {-1.0f, 1.0f}, 1062 | {-32000.f, 32000.f}, 1063 | }; 1064 | const size_t TEST_RANGES_LEN = sizeof(TEST_RANGES) / sizeof(TEST_RANGES[0]); 1065 | 1066 | 1067 | for (i = 0; i < TEST_RANGES_LEN; i++) { 1068 | size_t n; 1069 | 1070 | // todo: get this working with sizeof(float) * 3 1071 | bitbuf_buffer_t buf = bitbuf_alloc_buffer(256); 1072 | 1073 | float in_min = TEST_RANGES[i][0]; 1074 | float in_max = TEST_RANGES[i][1]; 1075 | 1076 | for (n = 0; n < STORE_BITS_LEN; n++) { 1077 | int num_bits = STORE_BITS[n]; 1078 | 1079 | bitbuf_write_quantized_float(&buf, num_bits, in_min, in_max, in_min); 1080 | bitbuf_write_quantized_float(&buf, num_bits, in_min, in_max, in_max); 1081 | } 1082 | TEST(!bitbuf_has_truncated(&buf)); 1083 | 1084 | bitbuf_cursor_t read = bitbuf_cursor_init(&buf); 1085 | 1086 | for (n = 0; n < STORE_BITS_LEN; n++) { 1087 | int num_bits = STORE_BITS[n]; 1088 | 1089 | float min = bitbuf_read_quantized_float(&read, num_bits, in_min, in_max); 1090 | float max = bitbuf_read_quantized_float(&read, num_bits, in_min, in_max); 1091 | 1092 | TEST(min == in_min); 1093 | TEST(max == in_max); 1094 | } 1095 | 1096 | bitbuf_free_buffer(&buf); 1097 | } 1098 | 1099 | return ftgt_test_errorlevel(); 1100 | } 1101 | 1102 | static int 1103 | bitbuf__test_get_bytes_from_buffer(void) 1104 | { 1105 | bitbuf_buffer_t* buf = &bitbuf__tv.buf; 1106 | 1107 | size_t num_bytes; 1108 | TEST(bitbuf_get_bytes_from_buffer(buf, &num_bytes) == (uint8_t*)buf->data); 1109 | TEST(num_bytes == 0); 1110 | 1111 | bitbuf_write_bool(buf, true); 1112 | bitbuf_get_bytes_from_buffer(buf, &num_bytes); 1113 | TEST(num_bytes == 1); 1114 | 1115 | bitbuf_write_uint64(buf, 0xFF00FF00FF00FF00); 1116 | bitbuf_get_bytes_from_buffer(buf, &num_bytes); 1117 | TEST(num_bytes == 9); 1118 | 1119 | 1120 | return ftgt_test_errorlevel(); 1121 | } 1122 | 1123 | BITBUFDEF 1124 | void 1125 | bitbuf_decl_suite(void) 1126 | { 1127 | ftgt_suite_s* suite = ftgt_create_suite( 1128 | NULL, "bitbuf_core", bitbuf__test_setup, bitbuf__test_teardown); 1129 | FTGT_ADD_TEST(suite, bitbuf__test_basic); 1130 | FTGT_ADD_TEST(suite, bitbuf__test_buffer_overflow); 1131 | FTGT_ADD_TEST(suite, bitbuf__test_align_to_end_of_segment); 1132 | FTGT_ADD_TEST(suite, bitbuf__test_write_after_read); 1133 | FTGT_ADD_TEST(suite, bitbuf__test_crop_set_bit_on_n_write); 1134 | FTGT_ADD_TEST(suite, bitbuf__test_cstr_overflow); 1135 | FTGT_ADD_TEST(suite, bitbuf__test_read_buffers); 1136 | FTGT_ADD_TEST(suite, bitbuf__test_qfloat); 1137 | FTGT_ADD_TEST(suite, bitbuf__test_get_bytes_from_buffer); 1138 | } 1139 | 1140 | #endif /* FTGT_TESTS_ENABLED */ 1141 | 1142 | #endif /* defined(BITBUF_IMPLEMENT_BITBUFFER) */ 1143 | 1144 | /* 1145 | ------------------------------------------------------------------------------ 1146 | This software is available under 2 licenses -- choose whichever you prefer. 1147 | ------------------------------------------------------------------------------ 1148 | ALTERNATIVE A - MIT License 1149 | Copyright (c) 2019 Sean Barrett 1150 | Permission is hereby granted, free of charge, to any person obtaining a copy of 1151 | this software and associated documentation files (the "Software"), to deal in 1152 | the Software without restriction, including without limitation the rights to 1153 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 1154 | of the Software, and to permit persons to whom the Software is furnished to do 1155 | so, subject to the following conditions: 1156 | The above copyright notice and this permission notice shall be included in all 1157 | copies or substantial portions of the Software. 1158 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 1159 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 1160 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 1161 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 1162 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 1163 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 1164 | SOFTWARE. 1165 | ------------------------------------------------------------------------------ 1166 | ALTERNATIVE B - Public Domain (www.unlicense.org) 1167 | This is free and unencumbered software released into the public domain. 1168 | Anyone is free to copy, modify, publish, use, compile, sell, or distribute this 1169 | software, either in source code form or as a compiled binary, for any purpose, 1170 | commercial or non-commercial, and by any means. 1171 | In jurisdictions that recognize copyright laws, the author or authors of this 1172 | software dedicate any and all copyright interest in the software to the public 1173 | domain. We make this dedication for the benefit of the public at large and to 1174 | the detriment of our heirs and successors. We intend this dedication to be an 1175 | overt act of relinquishment in perpetuity of all present and future rights to 1176 | this software under copyright law. 1177 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 1178 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 1179 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 1180 | AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 1181 | ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 1182 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 1183 | ------------------------------------------------------------------------------ 1184 | */ 1185 | -------------------------------------------------------------------------------- /ftg_core.h: -------------------------------------------------------------------------------- 1 | /* ftg_core.h - Frogtoss Toolbox. Public domain-like license below. 2 | 3 | ftg libraries are copyright (C) 2015-2023 Frogtoss Games, Inc. 4 | https://github.com/frogtoss/ftg_toolbox_public 5 | 6 | ftg header files are single file header files intended to be useful 7 | in C/C++. ftg_core contains generally useful functions 8 | 9 | Special thanks to STB for the inspiration. 10 | 11 | 12 | **** 13 | You must 14 | 15 | #define FTG_IMPLEMENT_CORE 16 | 17 | in exactly one C or C++ file. 18 | **** 19 | 20 | If _DEBUG is defined, assert behaviors change. 21 | 22 | OPTIONS 23 | 24 | 1. define FTG_ENABLE_ASSERT to enable FTG_ASSERT and related macros. 25 | 2. define FTG_ENABLE_STOPWATCH to enable code timing stopwatch macros. 26 | 3. See SELF-TESTING below to enable self testing. 27 | 4. define FTG_BIG_ENDIAN if you are compiling for big 28 | endian. Otherwise FTG_LITTLE_ENDIAN will be set. 29 | 5. define FTG_DISABLE_BRIEF_TYPES to avoid u8, i8, etc. 30 | 31 | PURPOSE 32 | 33 | ftg_core.h contains common routines and aims to smooth over the 34 | differences between compilers and their libraries. It is tested on 35 | Visual C++ 98 (c89), Visual Studio 2015 (roughly c99), modern Clang 36 | and modern GCC both in --std=gnu89 and --std=gnu99 dialects. 37 | 38 | It also contains a grab bag of commonly-useful routines and is 39 | somewhat unfocused. 40 | 41 | 42 | SELF-TESTING 43 | 44 | This file contains unit tests. In order to run them, add 45 | ftg_core.h as follows: 46 | 47 | #define FTG_IMPLEMENT_TEST 48 | #define FTG_IMPLEMENT_CORE 49 | #define 50 | #define 51 | 52 | { 53 | ftg_decl_suite(); 54 | int num_failures = ftgt_run_all_tests(NULL); 55 | } 56 | 57 | 58 | Version history 59 | 0.8 ftg_strto* wrappers 60 | 0.7 simple arena allocator 61 | 0.6 windows console alloc/free 62 | 0.5 file management 63 | limited utf-8 support (u8 functions) 64 | FTG_MALLOC/FREE/REALLOC macros 65 | 0.4 stopwatch timers 66 | 0.3 ftg_index_array, 67 | 64-bit file management 68 | 0.2 ftg_strncpy, ftg_tests, 69 | ftg_va 70 | 0.1 ftg_stristr, ftg_stricmp, 71 | compiler macros 72 | 73 | 74 | Future additions 75 | delete file, dir 76 | unit test 64-bit file functions 77 | wrap allocations in ifdefs 78 | standard utf-8 string ops 79 | 80 | 81 | */ 82 | #ifndef FTG__INCLUDE_CORE_H 83 | #define FTG__INCLUDE_CORE_H 84 | 85 | #if defined(_WIN32) && !defined(__MINGW32__) 86 | # ifndef _CRT_SECURE_NO_WARNINGS 87 | # define _CRT_SECURE_NO_WARNINGS 88 | # endif 89 | #endif 90 | 91 | #ifndef FTG_CORE_NO_STDIO 92 | # include 93 | #else 94 | # include // for size_t 95 | #endif 96 | 97 | /* for off64_t */ 98 | #if defined(__linux__) && _FILE_OFFSET_BITS == 64 99 | # include 100 | #endif 101 | 102 | /* set FTG_DEBUG to either 0 or 1, and ensure both debug and !debug aren't 103 | set across popular macros */ 104 | #if defined(NDEBUG) 105 | # define FTG_DEBUG 0 106 | #endif 107 | 108 | #if defined(_DEBUG) || defined(DEBUG) 109 | # if defined(FTG_DEBUG) 110 | # error "Both DEBUG and NDEBUG are set." 111 | # endif 112 | # define FTG_DEBUG 1 113 | #endif 114 | 115 | 116 | /* broad test for OSes that are vaguely POSIX compliant */ 117 | #if defined(__linux__) || defined(__APPLE__) || defined(ANDROID) || \ 118 | defined(TARGET_OS_IPHONE) || defined(TARGET_IPHONE_SIMULATOR) || \ 119 | defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__EMSCRIPTEN__) 120 | # define FTG_POSIX_LIKE 1 121 | #endif 122 | 123 | #if defined(__EMSCRIPTEN__) 124 | # define FTG_WASM 1 125 | #endif 126 | 127 | /* detect target enviroment bits */ 128 | #if _WIN64 129 | # define FTG_BITS 64 130 | #elif _WIN32 131 | # define FTG_BITS 32 132 | #endif 133 | #if __GNUC__ 134 | # if __x86_64__ || __ppc64__ || __aarch64__ 135 | # define FTG_BITS 64 136 | # else 137 | # define FTG_BITS 32 // todo: confirm emscripten sets this 138 | # endif 139 | #endif 140 | 141 | /* unicode strategy: UTF-8 everywhere, except when dealing with Windows functions. 142 | On Windows, define UNICODE and directly call wide versions of functions. 143 | 144 | By defining UNICODE, TCHAR functions don't accept char* which is a safety mechanism. 145 | 146 | I agree with and attempt to comply with utf8everywhere.org. 147 | 148 | Define FTG_IGNORE_UNICODE at your own peril. 149 | */ 150 | #ifndef FTG_IGNORE_UNICODE 151 | # ifdef _WIN32 152 | # if !defined(UNICODE) || !defined(_UNICODE) 153 | # error _UNICODE and UNICODE not defined -- see header comment for justification 154 | # endif 155 | # endif 156 | #endif 157 | 158 | #ifndef FTG_BITS 159 | # error "ftg_core.h could not determine 32-bit or 64-bit" 160 | #endif 161 | 162 | /* min/max */ 163 | #define FTG_MAX(a,b) ((a) > (b) ? (a) : (b)) 164 | #define FTG_MIN(a,b) ((a) < (b) ? (a) : (b)) 165 | #define FTG_CLAMP(x,xmin,xmax) ((x) < (xmin) ? (xmin) : (x) > (xmax) ? (xmax) : (x)) 166 | 167 | /* portable specifiers -- used to portably printf these types, ex: 168 | printf("size_t is %" FTG_SPEC_SIZE_T, (size_t)1); 169 | 170 | These are piecemeal, as encountered, and best-guess. 171 | If using C99, just use inttypes.h. 172 | */ 173 | #if defined(_MSC_VER) && _MSC_VER < 1310 174 | # define FTG_SPEC_SIZE_T "u" 175 | # define FTG_SPEC_SSIZE_T "d" 176 | # define FTG_SPEC_PTRDIFF_T "d" 177 | #elif defined(_MSC_VER) || defined(__MINGW32__) 178 | # define FTG_SPEC_SIZE_T "Iu" 179 | # define FTG_SPEC_SSIZE_T "Id" 180 | # define FTG_SPEC_PTRDIFF_T "Id" 181 | #elif defined(__GNUC__) 182 | # define FTG_SPEC_SIZE_T "zu" 183 | # define FTG_SPEC_SSIZE_T "zd" 184 | # define FTG_SPEC_PTRDIFF_T "zd" 185 | #endif 186 | 187 | #if defined(_MSC_VER) 188 | # define FTG_SPEC_INT64 "I64d" 189 | # define FTG_SPEC_UINT64 "I64d" 190 | #else 191 | # if FTG_BITS == 64 && !defined(__APPLE__) && !defined(__OpenBSD__) 192 | # define FTG_SPEC_INT64 "li" 193 | # define FTG_SPEC_UINT64 "lu" 194 | # elif FTG_BITS == 32 || defined(__APPLE__) || defined(__OpenBSD__) 195 | # define FTG_SPEC_INT64 "lli" 196 | # define FTG_SPEC_UINT64 "lld" 197 | # endif 198 | #endif 199 | 200 | /* ftg_off_t printing */ 201 | #ifndef FTG_CORE_NO_STDIO 202 | # define FTG_SPEC_OFF_T FTG_SPEC_INT64 203 | #endif 204 | 205 | /* octal directory creation mode for posix-like OSes; override if needed */ 206 | #ifndef FTG_DIRECTORY_MODE 207 | # define FTG_DIRECTORY_MODE 0750 208 | #endif 209 | 210 | #if defined(_MSC_VER) && (_MSC_VER >= 1800) 211 | // VS doesn't define STDC_VERSION because it isn't fully compliant, but this 212 | // actually lets us compile code that we couldn't without it. 213 | # if !defined(__STDC_VERSION__) && !defined(__cplusplus) 214 | # define __STDC_VERSION__ 199901L 215 | # endif 216 | 217 | // Because VS doesn't support C99, it thinks non-constant aggregate initializers 218 | // are non-standard per C89 3.5.7, but they are in C99. Disable warning 219 | // in vs2013 and greater. 220 | # ifndef __cplusplus 221 | # pragma warning(disable : 4204) 222 | # endif 223 | #endif 224 | 225 | #if __STDC_VERSION__ < 199901L 226 | #define FTG_FUNC "" 227 | #else 228 | #define FTG_FUNC __func__ 229 | #endif 230 | 231 | /* break */ 232 | 233 | #ifdef FTGT_TESTS_ENABLED 234 | # define FTG_BREAK() do{}while(0) 235 | #endif 236 | 237 | #ifndef FTG_BREAK 238 | #if defined(_WIN32) && _MSC_VER >= 1310 239 | #define FTG_BREAK() __debugbreak(); 240 | #elif defined(_WIN32) && defined(_MSC_VER) 241 | #define FTG_BREAK() __asm{ int 0x3 } 242 | #elif defined(__MINGW32__) 243 | #define FTG_BREAK() __asm__ volatile("int $0x03"); 244 | #elif defined(__linux__) 245 | #ifdef __arm__ 246 | #define FTG_BREAK() __asm__ volatile(".inst 0xde01"); 247 | #elif defined(__aarch64__) 248 | #define FTG_BREAK() __builtin_trap(); 249 | #else 250 | #define FTG_BREAK() __asm__("int $0x3"); 251 | #endif 252 | #elif defined(TARGET_OS_MAC) && defined(__LITTLE_ENDIAN__) 253 | #define FTG_BREAK() __builtin_trap(); 254 | #elif defined(ANDROID) 255 | #define FTG_BREAK() __android_log_assert("breakpoint()", "ftg", "breakpoint"); 256 | #elif defined(TARGET_IPHONE_SIMULATOR) 257 | #define FTG_BREAK() { __builtin_trap(); } 258 | #elif defined(TARGET_OS_IPHONE) 259 | #ifdef __ARM_ARCH_ISA_A64 260 | asm("svc 0"); 261 | #else 262 | asm("trap"); 263 | #endif 264 | #else 265 | #include 266 | #define FTG_BREAK() assert(0) 267 | #endif 268 | #endif 269 | 270 | /* Make true/false/bool universal macros */ 271 | #ifndef __cplusplus 272 | #if __STDC_VERSION__ < 199901L 273 | #if !__bool_true_false_are_defined 274 | #define true 1 275 | #define false 0 276 | #define bool int 277 | #endif 278 | #else 279 | #include 280 | #endif 281 | #endif 282 | 283 | /* assert 284 | 285 | define FTG_ENABLE_ASSERT with FTG_DEBUG=1 to prevent asserts from being noops 286 | 287 | Further, define FTG_CUSTOM_ASSERT_REPORTER to provide alternative diagnostics for FTG_ASSERT calls. 288 | Alternatively, undef it to use system assert. 289 | */ 290 | 291 | #if !defined(FTG_CUSTOM_ASSERT_REPORTER) && defined(FTGT_TESTS_ENABLED) 292 | # define FTG_CUSTOM_ASSERT_REPORTER ftg__test_assert_reporter 293 | #elif !defined(FTG_CUSTOM_ASSERT_REPORTER) 294 | # define FTG_CUSTOM_ASSERT_REPORTER ftg__default_assert_reporter 295 | #endif 296 | 297 | #if defined(FTG_ENABLE_ASSERT) || FTG_DEBUG==1 || defined(FTGT_TESTS_ENABLED) 298 | # ifdef FTG_CUSTOM_ASSERT_REPORTER 299 | # define FTG_ASSERT(exp) \ 300 | { if (!(exp) && FTG_CUSTOM_ASSERT_REPORTER(#exp, __FILE__, FTG_FUNC, __LINE__)) FTG_BREAK() \ 301 | ;} 302 | # define FTG_ASSERT_FAIL(exp) \ 303 | { FTG_CUSTOM_ASSERT_REPORTER(#exp, __FILE__, FTG_FUNC, __LINE__); FTG_BREAK() \ 304 | ;((void)exp);} 305 | # else 306 | # include 307 | # define FTG_ASSERT(exp) (void)((exp)||(assert(exp))) 308 | # define FTG_ASSERT_FAIL(exp) (assert(0);) 309 | # endif 310 | #else 311 | # define FTG_ASSERT(exp) ((void)0) 312 | # define FTG_ASSERT_FAIL(exp) ((void)0) 313 | #endif 314 | 315 | #define FTG_ASSERT_ALWAYS(exp) \ 316 | { if (!(exp) && FTG_CUSTOM_ASSERT_REPORTER(#exp, __FILE__, FTG_FUNC, __LINE__)) FTG_BREAK(); } 317 | 318 | 319 | /* static assert */ 320 | 321 | #define FTG_STATIC_ASSERT_MSG(x, msg) typedef int FTG__StaticAssert_##msg[(x) ? 1 : -1] 322 | #define FTG__STATIC_ASSERT_3(x,L) FTG_STATIC_ASSERT_MSG(x, assertion_at_line_##L) 323 | #define FTG__STATIC_ASSERT_2(x,L) FTG__STATIC_ASSERT_3(x,L) 324 | #define FTG_STATIC_ASSERT(x) FTG__STATIC_ASSERT_2(x,__LINE__) 325 | 326 | /* int types */ 327 | #if (defined(_MSC_VER) && (_MSC_VER >= 1800)) || defined (__GNUC__) || defined(__clang__) 328 | # include 329 | # include 330 | #else 331 | typedef unsigned char uint8_t; 332 | typedef signed char int8_t; 333 | typedef unsigned short uint16_t; 334 | typedef signed short int16_t; 335 | typedef unsigned int uint32_t; 336 | typedef signed int int32_t; 337 | 338 | #if FTG_BITS == 32 339 | typedef unsigned int uintptr_t; 340 | typedef int intptr_t; 341 | #else 342 | typedef unsigned long int uintptr_t; 343 | typedef long int intptr_t; 344 | #endif 345 | 346 | # if defined(_MSC_VER) 347 | typedef unsigned __int64 uint64_t; 348 | typedef signed __int64 int64_t; 349 | 350 | # if FTG_BITS == 64 351 | # define SIZE_MAX (18446744073709551615UL) 352 | # else 353 | # define SIZE_MAX (4294967295U) 354 | # endif 355 | 356 | # else 357 | typedef unsigned long long uint64_t; 358 | typedef signed long long int64_t; 359 | # endif 360 | #endif 361 | 362 | #ifndef FTG_DISABLE_BRIEF_TYPES 363 | # define FTG_HAS_BRIEF_TYPES 364 | typedef int8_t i8; 365 | typedef int16_t i16; 366 | typedef int32_t i32; 367 | typedef int64_t i64; 368 | typedef uint8_t u8; 369 | typedef uint16_t u16; 370 | typedef uint32_t u32; 371 | typedef uint64_t u64; 372 | typedef float f32; 373 | typedef double f64; 374 | typedef size_t usize; 375 | typedef ptrdiff_t isize; 376 | 377 | FTG_STATIC_ASSERT(sizeof(i8) == 1); 378 | FTG_STATIC_ASSERT(sizeof(i16) == 2); 379 | FTG_STATIC_ASSERT(sizeof(i32) == 4); 380 | FTG_STATIC_ASSERT(sizeof(i64) == 8); 381 | FTG_STATIC_ASSERT(sizeof(u8) == 1); 382 | FTG_STATIC_ASSERT(sizeof(u16) == 2); 383 | FTG_STATIC_ASSERT(sizeof(u32) == 4); 384 | FTG_STATIC_ASSERT(sizeof(u64) == 8); 385 | FTG_STATIC_ASSERT(sizeof(f32) == 4); 386 | FTG_STATIC_ASSERT(sizeof(f64) == 8); 387 | FTG_STATIC_ASSERT(sizeof(isize) == sizeof(usize)); 388 | #endif 389 | 390 | 391 | 392 | /* portable 64-bit offset types 393 | ftg_off_t is guaranteed to be 64-bit, even on 32-bit builds 394 | on all OSes. 395 | ftg_fopen, ftg_ftell, etc. all operate on 64-bit values. 396 | */ 397 | #ifndef FTG_CORE_NO_STDIO 398 | 399 | struct ftg_dirhandle_s; 400 | typedef struct ftg_dirhandle_s ftg_dirhandle_t; 401 | 402 | 403 | #if defined(__APPLE__) 404 | # define FTG_IO64_EXPLICIT 0 405 | # if _FILE_OFFSET_BITS == 64 || defined(__LP64__) 406 | typedef off_t ftg_off_t; 407 | # else 408 | typedef off64_t ftg_off_t 409 | # endif 410 | #elif defined(__linux__) 411 | /* Linux is a fun one. It offers three separate ways to get a 412 | 64-bit file descriptor. The first is through explicit 64-bit 413 | calls and typedefs: defining _LARGEFILE64_SOURCE. The 414 | alternative is specifying _FILE_OFFSET_BITS=64 and then using 415 | transparently using the normal routines. Finally, if __LP64__ 416 | is defined, off_t is 64-bit anyway. 417 | 418 | As a library trying to work in all cases, we use explicit calls 419 | if they are available, or fall back on transparent calls if they 420 | are not. To simplify preprocessor tests, we define 421 | FTG__IO64_EXPLICIT. 422 | 423 | And we statically assert if neither of these are defined. */ 424 | # if defined(_LARGEFILE64_SOURCE) 425 | # define FTG__IO64_EXPLICIT 1 426 | typedef off64_t ftg_off_t; 427 | # elif _FILE_OFFSET_BITS == 64 428 | # define FTG__IO64_EXPLICIT 0 429 | typedef off_t ftg_off_t; 430 | # else 431 | # define FTG__IO64_EXPLICIT 0 432 | # if defined(__LP64__) 433 | typedef off_t ftg_off_t; 434 | # else 435 | /* if this is hit, define _FILE_OFFSET_BITS=64 across your compile, or 436 | define FTG_CORE_NO_STDIO to disable all ftg_core I/O */ 437 | # error "no 64-bit fopen support." 438 | # endif 439 | # endif 440 | #elif defined(_WIN32) 441 | typedef int64_t ftg_off_t; 442 | typedef wchar_t ftg_wchar_t; 443 | #elif defined(__FreeBSD__) || defined(__OpenBSD__) 444 | typedef off_t ftg_off_t; 445 | #elif defined(FTG_WASM) 446 | typedef off_t ftg_off_t; 447 | #endif 448 | 449 | FTG_STATIC_ASSERT(sizeof(ftg_off_t)==8); 450 | 451 | #endif /* !FTG_CORE_NO_STDIO */ 452 | 453 | FTG_STATIC_ASSERT(sizeof(int8_t)==1); 454 | FTG_STATIC_ASSERT(sizeof(int16_t)==2); 455 | FTG_STATIC_ASSERT(sizeof(int32_t)==4); 456 | FTG_STATIC_ASSERT(sizeof(int64_t)==8); 457 | 458 | /* these will fail on 16-bit segmented memory platforms. Comment them 459 | out if that happens. */ 460 | FTG_STATIC_ASSERT(sizeof(intptr_t) == sizeof(ptrdiff_t)); 461 | FTG_STATIC_ASSERT(sizeof(uintptr_t) == sizeof(size_t)); 462 | 463 | #define FTG_ARRAY_ELEMENTS(n) (sizeof((n)) / sizeof((*n))) 464 | /* 465 | Compiler extensions made portable 466 | 467 | USAGE 468 | 469 | In MSVC, attributes must be on declarations in addition to prototypes. 470 | Use FTG_ATTRIBUTES to declare them. 471 | 472 | Prototype 473 | FTG_EXT_warn_unused_result FTG_EXT_force_inline int SomeFunc( void ); 474 | 475 | Declaration 476 | FTG_ATTRIBUTES(FTG_EXT_warn_unused_result) int SomeFunc( void ); 477 | 478 | 479 | Keyword extensions made portable 480 | 481 | Goal is to use language and compiler-native versions of keywords added 482 | after the c89 standard. 483 | 484 | ftg_inline 485 | ftg_restrict 486 | */ 487 | 488 | #if defined(__GNUC__) || defined(__clang__) 489 | #define FTG_EXT_warn_unused_result __attribute__((warn_unused_result)) 490 | #define FTG_EXT_force_inline __attribute__((always_inline)) 491 | #define FTG_EXT_pure_function __attribute__((pure)) 492 | #define FTG_EXT_const_function __attribute__((const)) 493 | #define FTG_EXT_unused __attribute__((unused)) 494 | #define FTG_EXT_no_vtable 495 | #ifdef __cplusplus 496 | #define ftg_restrict __restrict 497 | #else 498 | #if __STDC_VERSION__ >= 199901L 499 | #define ftg_restrict restrict 500 | #endif 501 | #endif 502 | #if __STDC_VERSION__ < 199901L 503 | #define ftg_inline __inline 504 | #else 505 | #define ftg_inline inline 506 | #endif 507 | #define FTG_ATTRIBUTES(x) 508 | 509 | // todo: investigate https://twitter.com/deplinenoise/status/971873640609300480 510 | #elif defined(_MSC_VER) && (_MSC_VER >= 1700) /* vs2012 */ 511 | #define ftg_restrict __restrict 512 | #define ftg_inline __inline 513 | #define FTG_EXT_warn_unused_result _Check_return_ 514 | #define FTG_EXT_force_inline __forceinline 515 | #define FTG_EXT_unused 516 | #define FTG_ATTRIBUTES(x) x 517 | #elif defined(_MSC_VER) && (_MSC_VER >= 1400) /* vs2005 */ 518 | #define FTG_EXT_no_vtable __declspec(novtable) 519 | #define ftg_restrict __restrict 520 | #define ftg_inline __inline 521 | #define FTG_EXT_unused 522 | #define FTG_ATTRIBUTES(x) x 523 | #endif 524 | 525 | #ifndef FTG_EXT_warn_unused_result 526 | #define FTG_EXT_warn_unused_result 527 | #endif 528 | #ifndef FTG_EXT_force_inline 529 | #define FTG_EXT_force_inline 530 | #endif 531 | #ifndef FTG_EXT_pure_function 532 | #define FTG_EXT_pure_function 533 | #endif 534 | #ifndef FTG_EXT_const_function 535 | #define FTG_EXT_const_function 536 | #endif 537 | #ifndef ftg_restrict 538 | #define ftg_restrict 539 | #endif 540 | #ifndef ftg_inline 541 | #define ftg_inline 542 | #endif 543 | #ifndef FTG_ATTRIBUTES 544 | #define FTG_ATTRIBUTES(x) 545 | #endif 546 | 547 | #ifdef FTG_CORE_STATIC 548 | # define FTGDEF static FTG_EXT_unused 549 | #else 550 | # define FTGDEF extern 551 | #endif 552 | 553 | /* endianness 554 | 555 | use little endian unless FTG_BIG_ENDIAN is defined. 556 | */ 557 | 558 | #ifndef FTG_BIG_ENDIAN 559 | # define FTG_LITTLE_ENDIAN 560 | #endif 561 | 562 | /* fourcc */ 563 | #ifdef FTG_LITTLE_ENDIAN 564 | # define FTG_FOURCC(a,b,c,d) ((d) | ((c)<<8) | ((b)<<16) | ((a)<<24)) 565 | #elif FTG_BIG_ENDIAN 566 | # define FTG_FOURCC(a,b,c,d) ((a) | ((b)<<8) | ((c)<<16) | ((d)<<24)) 567 | #endif 568 | 569 | /* misc macros */ 570 | 571 | #define FTG_UNUSED(x) ((void)x) 572 | #define FTG_STRLEN 255 573 | #define FTG_STRLEN_SHORT 16 574 | #define FTG_STRLEN_LONG 1024 575 | 576 | #define FTG_UNDEFINED_HUE 360.f*2 577 | 578 | #define FTG_IA_INIT_ZERO {NULL, 0, 0} 579 | #define FTG_IA_INITIAL_RECORD_COUNT 8 580 | 581 | /* usually better to use ftg_correct_dirslash than reference this directly */ 582 | #ifdef _WIN32 583 | # define FTG_DIRSLASH '\\' 584 | # define FTG_DIRSLASH_STR "\\" 585 | #elif defined(FTG_POSIX_LIKE) 586 | # define FTG_DIRSLASH '/' 587 | # define FTG_DIRSLASH_STR "/" 588 | #endif 589 | 590 | #if defined(__linux__) 591 | # define FTG__HAVE_EXPLICIT_BZERO 592 | #elif defined(__APPLE__) 593 | # define FTG__HAVE_MEMSET_S 594 | #endif 595 | 596 | /* 597 | stopwatch -- portable, high precision start/next/stop timer 598 | 599 | #define FTG_ENABLE_STOPWATCH before calling this function, or every 600 | call is a noop. 601 | 602 | 603 | USAGE 604 | 1. Declare a stopwatch variable mytimer: 605 | FTG_STOPWATCH_DECL(mytimer); 606 | 607 | 2. Start the timer, storing time in the bucket named "0": 608 | FTG_STOPWATCH_START(mytimer, 0) 609 | 610 | 3. (optional) Track additional times within the timer. 611 | This buckets the remaining code into bucket "1": 612 | FTG_STOPWATCH_NEXT(mytimer, 1) 613 | 614 | 4. Stop the timer 615 | FTG_STOPWATCH_STOP(mytimer) 616 | 617 | 618 | TIMING AFTER STOP 619 | If FTG_STOPWATCH_NEXT() is encountered after a timer has stopped, 620 | no additional time is counted. This is a key subtlety. 621 | Time spent in subfunction c() of a() is accumulated, but when c() 622 | is a subfunction of b(), it is not accumulated, providing the timer 623 | was stopped before b() was called. 624 | 625 | 626 | DESCRIBING THE STEPS (optional) 627 | Instead of using numerical steps, you can use named steps like this: 628 | #define A_STEP_NAME 1 629 | Then, 630 | FTG_STOPWATCH_NEXT(mytimer, A_STEP_NAME) 631 | This increases the readability of the reporting. 632 | 633 | 634 | CUSTOM REPORTING (optional) 635 | After a stopwatch ends, a function with the following prototype is called 636 | to report the stopwatch: 637 | 638 | void ftg__default_stopwatch_reporter(const struct ftg_stopwatch_s *); 639 | 640 | #define FTG_STOPWATCH_REPORTER to your own function in the file that implements ftg_core 641 | to call that instead. 642 | */ 643 | 644 | #ifdef FTG_ENABLE_STOPWATCH 645 | 646 | # define FTG_STOPWATCH_DECL(sw) struct ftg_stopwatch_s sw = {"", {0}, {""}, 0, FTG__MAX_STOPWATCH_TIMES+100, false}; 647 | # define FTG_STOPWATCH_START(sw, bucket) ftg__stopwatch_start(&sw, #sw, #bucket, bucket) 648 | # define FTG_STOPWATCH_NEXT(sw, bucket) ftg__stopwatch_next_bucket(&sw, #bucket, bucket) 649 | # define FTG_STOPWATCH_STOP(sw) ftg__stopwatch_stop(&sw) 650 | # define FTG_STOPWATCH_EXPR(exp) exp 651 | 652 | #define FTG__MAX_STOPWATCH_TIMES 12 653 | 654 | struct ftg_stopwatch_s { 655 | char name[FTG_STRLEN_SHORT]; 656 | 657 | uint64_t times[FTG__MAX_STOPWATCH_TIMES]; 658 | char time_names[FTG__MAX_STOPWATCH_TIMES][FTG_STRLEN_SHORT]; 659 | 660 | uint64_t start_time; 661 | 662 | size_t current_bucket; 663 | bool currently_logging; 664 | }; 665 | 666 | #else 667 | # define FTG_STOPWATCH_DECL(sw) 668 | # define FTG_STOPWATCH_START(sw, bucket) 669 | # define FTG_STOPWATCH_NEXT(sw, bucket) 670 | # define FTG_STOPWATCH_STOP(sw) 671 | # define FTG_STOPWATCH_EXPR(exp) 672 | struct ftg_stopwatch_s; 673 | #endif 674 | 675 | /* Define a type by its language-native class type. Useful for 676 | creating types as forward declarations which are ignored when 677 | actually compiling in C. 678 | 679 | Usage: 680 | #define TYPE_MACRO FTG_DECL_CLASS(some_type) 681 | TYPE_MACRO; 682 | void some_func(TYPE_MACRO *); 683 | */ 684 | #ifdef __cplusplus 685 | #define FTG_DECL_CLASS(T) class T 686 | #elif __OBJC__ 687 | #define FTG_DECL_CLASS(T) @class T 688 | #else 689 | #define FTG_DECL_CLASS(T) struct T 690 | #endif 691 | 692 | /* exported decls */ 693 | 694 | #ifdef __cplusplus 695 | extern "C" { 696 | #endif 697 | 698 | /* MSVC earlier than 2005 does not include these in stdio.h. */ 699 | #if defined(_MSC_VER) && _MSC_VER < 1400 700 | extern int __cdecl _fseeki64(FILE *, __int64, int); 701 | extern __int64 __cdecl _ftelli64(FILE *); 702 | #endif 703 | 704 | 705 | #if defined(FTG_MALLOC) && defined(FTG_FREE) && defined(FTG_REALLOC) 706 | // okay 707 | #elif !defined(FTG_MALLOC) && !defined(FTG_FREE) && !defined(FTG_REALLOC) 708 | // also okay 709 | #else 710 | #error "Must define all or none of FTG_MALLOC, FTG_FREE and FTG_REALLOC." 711 | #endif 712 | 713 | /* default implementations just call system malloc with assert guards 714 | for OOM and overflow. 715 | 716 | ftg_free zeroes pointer in calling code after free. 717 | 718 | num - number of elements, size - size of each element */ 719 | #ifndef FTG_MALLOC 720 | # define FTG_MALLOC(size, num) ftg_malloc(size, num); 721 | # define FTG_FREE(ptr) ftg_free((void**)&ptr); 722 | # define FTG_REALLOC(ptr, size, num) ftg_realloc(ptr, size, num) 723 | #endif 724 | 725 | /* 726 | Arena Allocator 727 | 728 | Useful for lots of small allocations on the same thread that all need 729 | to be freed at the same time. 730 | 731 | See FTG_ARENA_OVERRIDE to override alignment and page size. 732 | 733 | USAGE 734 | 735 | ftg_arena_t *arena = ftg_arena_new(); 736 | void *ptr = ftg_arena_alloc(&arena, sizeof(sometype_t)); 737 | assert(ptr); 738 | ftg_arena_free(arena); 739 | 740 | */ 741 | 742 | #define FTG_ALIGN_DOWN(n, a) ((n) & ~((a)-1)) 743 | #define FTG_ALIGN_UP(n, a) FTG_ALIGN_DOWN((n) + (a)-1, (a)) 744 | #define FTG_ALIGN_DOWN_PTR(p, a) ((void*)FTG_ALIGN_DOWN((uintptr_t)(p), (a))) 745 | #define FTG_ALIGN_UP_PTR(p, a) ((void*)FTG_ALIGN_UP((uintptr_t)(p), (a))) 746 | 747 | #ifndef FTG_ARENA_OVERRIDE 748 | # define FTG_ARENA_ALIGNMENT 8 749 | # define FTG_ARENA_BLOCK_SIZE (1024 * 4) 750 | #endif 751 | 752 | struct ftg_arena_s { 753 | uint8_t* ptr; 754 | uint8_t* start; 755 | uint8_t* end; 756 | struct ftg_arena_s* prev; 757 | }; 758 | 759 | typedef struct ftg_arena_s ftg_arena_t; 760 | 761 | FTGDEF void * 762 | ftg_malloc(size_t size, size_t num); 763 | 764 | FTGDEF void * 765 | ftg_calloc(size_t size, size_t num); 766 | 767 | FTGDEF void 768 | ftg_free(void **heap); 769 | 770 | FTGDEF int 771 | ftg_stricmp(const char *s1, const char *s2); 772 | 773 | FTGDEF void * 774 | ftg_realloc(void *ptr, size_t size, size_t num); 775 | 776 | FTGDEF char * 777 | ftg_stristr(const char *haystack, const char *needle); 778 | 779 | FTGDEF FTG_EXT_warn_unused_result int 780 | ftg_strncpy(char *ftg_restrict dst, const char *ftg_restrict src, size_t max_copy); 781 | 782 | FTGDEF FTG_EXT_warn_unused_result char * 783 | ftg_strcatall(size_t num, ...); 784 | 785 | FTGDEF const char* 786 | ftg_strsplit(const char *str, char split_ch, size_t index, size_t *out_len); 787 | 788 | FTGDEF void 789 | ftg_bzero(void *ptr, size_t num); 790 | 791 | FTGDEF char * 792 | ftg_va(const char *fmt, ...); 793 | 794 | FTGDEF uint32_t 795 | ftg_hash_fast(const void *p, uint32_t len); 796 | 797 | FTGDEF uint32_t 798 | ftg_hash_number(uint32_t number); 799 | 800 | FTGDEF int 801 | ftg__default_assert_reporter(const char *expr, const char *filename, const char *func, unsigned int lineno); 802 | 803 | #ifdef FTGT_TESTS_ENABLED 804 | FTGDEF int 805 | ftg__test_assert_reporter(const char *expr, const char *filename, const char *func, unsigned int lineno); 806 | #endif 807 | 808 | #ifndef FTG_CORE_NO_STDIO 809 | FTGDEF unsigned char * 810 | ftg_file_read(const char *path, bool make_string, ftg_off_t *len); 811 | 812 | FTGDEF bool 813 | ftg_file_write(const char *path, const uint8_t *buf, size_t buf_bytes); 814 | 815 | FTGDEF bool 816 | ftg_file_write_string(const char *path, const char *str); 817 | #endif 818 | 819 | FTGDEF void 820 | ftg_gethsv(float r, float g, float b, float *ftg_restrict h, float *ftg_restrict s, float *ftg_restrict v); 821 | 822 | FTGDEF void 823 | ftg_getrgb(float h, float s, float v, float *ftg_restrict r, float *ftg_restrict g, float *ftg_restrict b); 824 | 825 | FTGDEF float 826 | ftg_aspect_correct_scale_for_rect(const float screen[2], const float rect[2]); 827 | 828 | FTGDEF size_t 829 | ftg_u8_strlen(const char *s); 830 | 831 | struct ftg_index_array_s { 832 | size_t *indices; 833 | size_t count; /* num used */ 834 | size_t records; /* alloced, always > count */ 835 | }; 836 | 837 | /* warning: ftg_ia api will be deprecated and moved to a container-specific header file */ 838 | FTGDEF bool 839 | ftg_ia_is_init(struct ftg_index_array_s *i); 840 | 841 | FTGDEF bool 842 | ftg_ia_append(struct ftg_index_array_s *i, size_t index); 843 | 844 | FTGDEF void 845 | ftg_ia_prealloc(struct ftg_index_array_s *i, size_t initial_record_count); 846 | 847 | FTGDEF void 848 | ftg_ia_free(struct ftg_index_array_s *i); 849 | 850 | FTGDEF const char * 851 | ftg_correct_dirslash(char *path); 852 | 853 | FTGDEF char * 854 | ftg_get_filename_ext(const char *path); 855 | 856 | FTGDEF char * 857 | ftg_get_filename_from_path(const char *path); 858 | 859 | FTGDEF bool 860 | ftg_is_dirslash(char c); 861 | 862 | FTGDEF bool 863 | ftg_push_path(char *dst_path, const char *src_path, size_t max_path); 864 | 865 | FTGDEF void 866 | ftg_pop_path(char *dst_path); 867 | 868 | #ifndef FTG_CORE_NO_STDIO 869 | FTGDEF FILE * 870 | ftg_fopen64(const char *filename, const char *type); 871 | 872 | FTGDEF ftg_off_t 873 | ftg_ftell64(FILE *stream); 874 | 875 | FTGDEF int64_t 876 | ftg_fseek64(FILE *stream, ftg_off_t offset, int origin); 877 | 878 | FTGDEF const char * 879 | ftg_opendir(ftg_dirhandle_t *handle, const char *path, char *out, size_t out_len); 880 | 881 | FTGDEF const char * 882 | ftg_readdir(ftg_dirhandle_t *handle, char *out, size_t out_len); 883 | 884 | FTGDEF void 885 | ftg_closedir(ftg_dirhandle_t *handle); 886 | 887 | FTGDEF bool 888 | ftg_is_dir(const char *path); 889 | 890 | FTGDEF bool 891 | ftg_path_exists(const char *path); 892 | 893 | FTGDEF bool 894 | ftg_mkdir(const char *path); 895 | 896 | FTGDEF void 897 | ftg_mkalldirs(const char *path); 898 | 899 | FTGDEF void 900 | ftg_rmalldirs(const char *path); 901 | 902 | FTGDEF bool 903 | ftg_rmdir(const char *path); 904 | 905 | FTGDEF void 906 | ftg_alloc_console(void); 907 | 908 | FTGDEF void 909 | ftg_free_console(void); 910 | #endif 911 | 912 | #ifdef FTG_ENABLE_STOPWATCH 913 | FTGDEF void 914 | ftg__stopwatch_start(struct ftg_stopwatch_s *stopwatch, 915 | const char *name, const char *bucket_name, size_t bucket); 916 | 917 | FTGDEF void 918 | ftg__stopwatch_stop(struct ftg_stopwatch_s *stopwatch); 919 | 920 | FTGDEF void 921 | ftg__stopwatch_next_bucket(struct ftg_stopwatch_s *stopwatch, 922 | const char *bucket_name, size_t bucket); 923 | 924 | FTGDEF void 925 | ftg__default_stopwatch_reporter(struct ftg_stopwatch_s *sw); 926 | #endif 927 | 928 | FTGDEF ftg_arena_t * 929 | ftg_arena_new(void); 930 | 931 | FTGDEF void* 932 | ftg_arena_alloc(ftg_arena_t **arena, size_t size); 933 | 934 | FTGDEF void 935 | ftg_arena_free(ftg_arena_t* arena); 936 | 937 | FTGDEF int 938 | ftg_strtof(const char *str, char **endptr, float *out_float); 939 | 940 | FTGDEF int 941 | ftg_strtol(const char *str, char **endptr, int base, long int *out_long); 942 | 943 | #ifdef __cplusplus 944 | } 945 | #endif 946 | 947 | 948 | /* implementation */ 949 | 950 | #ifdef FTG_IMPLEMENT_CORE 951 | 952 | #ifndef FTG_CORE_NO_STDIO 953 | # include 954 | # ifdef FTG_POSIX_LIKE 955 | # include 956 | # include 957 | # include 958 | # include 959 | # include 960 | # elif defined(_WIN32) 961 | # define WIN32_LEAN_AND_MEAN 962 | # include /* for HANDLE in ftg_opendir */ 963 | # include 964 | # ifndef INVALID_FILE_ATTRIBUTES 965 | # define INVALID_FILE_ATTRIBUTES ((DWORD)-1) /* missing in older sdks */ 966 | # endif 967 | # undef WIN32_LEAN_AND_MEAN 968 | # endif 969 | #endif 970 | 971 | #include 972 | #include 973 | #include 974 | #include 975 | #include 976 | #include 977 | 978 | /* SIZE_MAX may not be defined in C++ because C++ does not know about this C99 addition. */ 979 | #ifndef SIZE_MAX 980 | # if FTG_BITS == 64 981 | # define FTG__SIZE_MAX (18446744073709551615UL) 982 | # else 983 | # define FTG__SIZE_MAX (4294967295U) 984 | # endif 985 | #else 986 | # define FTG__SIZE_MAX SIZE_MAX 987 | #endif 988 | 989 | int 990 | ftg__default_assert_reporter(const char *expr, const char *filename, const char *func, unsigned int lineno) 991 | { 992 | ((void)filename); 993 | fprintf(stderr, "assert %s(%d)%s: %s\n", filename, lineno, func, expr); 994 | return 1; 995 | } 996 | 997 | #ifdef FTGT_TESTS_ENABLED 998 | int 999 | ftg__test_assert_reporter(const char *expr, const char *filename, const char *func, unsigned int lineno) 1000 | { 1001 | printf("\n[ftg_test intercepted assert] "); 1002 | ftg__default_assert_reporter(expr, filename, func, lineno); 1003 | 1004 | ftgt_notify_assert_hit(); 1005 | 1006 | return 1; 1007 | } 1008 | #endif 1009 | 1010 | /* memory */ 1011 | 1012 | void * 1013 | ftg_malloc(size_t size, size_t num) 1014 | { 1015 | void *heap = NULL; 1016 | FTG_ASSERT(size != 0 && num != 0); 1017 | 1018 | if (size > 0 && num > 0 && FTG__SIZE_MAX/size >= num) 1019 | { 1020 | size_t alloc_size = size * num; 1021 | heap = malloc(alloc_size); 1022 | if (!heap) 1023 | { 1024 | FTG_ASSERT_FAIL("oom"); 1025 | } 1026 | } 1027 | else 1028 | { 1029 | FTG_ASSERT_FAIL("ftg_malloc overflow - no memory allocated"); 1030 | } 1031 | 1032 | return heap; 1033 | } 1034 | 1035 | void 1036 | ftg_free(void **heap) 1037 | { 1038 | FTG_ASSERT(heap && *heap); 1039 | free(*heap); 1040 | *heap = NULL; 1041 | } 1042 | 1043 | void * 1044 | ftg_calloc(size_t size, size_t num) { 1045 | void *p; 1046 | 1047 | // unfortuanately, the size and num parameters are reversed from 1048 | // calloc(), but match ftg_malloc() 1049 | 1050 | FTG_ASSERT(size != 0 && num != 0); 1051 | if (size == 0 || num == 0) 1052 | return NULL; 1053 | 1054 | p = ftg_malloc(size, num); 1055 | if (p) { 1056 | ftg_bzero(p, size * num); 1057 | } 1058 | 1059 | return p; 1060 | } 1061 | 1062 | /* strings */ 1063 | 1064 | /* ascii case folded string compare */ 1065 | int 1066 | ftg_stricmp(const char *s1, const char *s2) 1067 | { 1068 | int result; 1069 | const char *p1; 1070 | const char *p2; 1071 | if ( s1==s2) 1072 | return 0; 1073 | 1074 | p1 = s1; 1075 | p2 = s2; 1076 | result = 0; 1077 | if ( p1 == p2 ) 1078 | return result; 1079 | 1080 | while (!result) 1081 | { 1082 | result = tolower(*p1) - tolower(*p2); 1083 | if ( *p1 == '\0' ) 1084 | break; 1085 | ++p1; 1086 | ++p2; 1087 | } 1088 | 1089 | return result; 1090 | } 1091 | 1092 | 1093 | /* Fill up to max_copy characters in dst, including null. Unlike strncpy(), a null 1094 | terminating character is guaranteed to be appended, EVEN if it overwrites 1095 | the last character in the string. 1096 | 1097 | Only appends a single NULL character instead of NULL filling the string. The 1098 | trailing bytes are left uninitialized. 1099 | 1100 | No bytes are written if max_copy is 0, and FTG_ASSERT is thrown. 1101 | 1102 | \return 1 on truncation or max_copy==0, zero otherwise. 1103 | */ 1104 | FTG_ATTRIBUTES(FTG_EXT_warn_unused_result) int 1105 | ftg_strncpy(char *ftg_restrict dst, const char *ftg_restrict src, size_t max_copy) 1106 | { 1107 | size_t n; 1108 | char *d; 1109 | 1110 | FTG_ASSERT(dst); 1111 | FTG_ASSERT(src); 1112 | FTG_ASSERT(max_copy > 0); 1113 | 1114 | if (max_copy == 0) 1115 | return 1; 1116 | 1117 | n = max_copy; 1118 | d = dst; 1119 | while ( n > 0 && *src != '\0' ) 1120 | { 1121 | *d++ = *src++; 1122 | --n; 1123 | } 1124 | 1125 | /* Truncation case - 1126 | terminate string and return true */ 1127 | if ( n == 0 ) 1128 | { 1129 | dst[max_copy-1] = '\0'; 1130 | return 1; 1131 | } 1132 | 1133 | /* No truncation. Append a single NULL and return. */ 1134 | *d = '\0'; 1135 | return 0; 1136 | } 1137 | 1138 | /* case insensitive strstr */ 1139 | char * 1140 | ftg_stristr(const char *haystack, const char *needle) 1141 | { 1142 | if ( !*needle ) 1143 | return (char*)(haystack+strlen(haystack)); 1144 | 1145 | for ( ; *haystack; ++haystack ) 1146 | { 1147 | if ( tolower( *haystack ) == tolower( *needle ) ) 1148 | { 1149 | const char *h,*n; 1150 | for ( h = haystack, n = needle; *h && *n; ++h, ++n ) 1151 | { 1152 | if ( tolower( *h ) != tolower( *n ) ) 1153 | break; 1154 | } 1155 | if ( !*n ) 1156 | return (char*)haystack; 1157 | } 1158 | } 1159 | 1160 | return NULL; 1161 | } 1162 | 1163 | /* append num strings, returning heap-allocated string. 1164 | caller must free() returned ptr. */ 1165 | FTG_ATTRIBUTES(FTG_EXT_warn_unused_result) char * 1166 | ftg_strcatall(size_t num, ...) 1167 | { 1168 | size_t i, alloc_size = 0; 1169 | va_list va1, va2; 1170 | char *buf, *p_buf; 1171 | 1172 | va_start(va1, num); 1173 | for (i = 0; i < num; i++) 1174 | { 1175 | alloc_size += strlen(va_arg(va1, char*)); 1176 | } 1177 | va_end(va1); 1178 | alloc_size += 1; 1179 | 1180 | buf = p_buf = (char*)FTG_MALLOC(sizeof(char), alloc_size); 1181 | if (!buf) return NULL; 1182 | va_start(va2, num); 1183 | for (i = 0; i < num; i++) 1184 | { 1185 | char *s = va_arg(va2, char*); 1186 | while (*s) 1187 | { 1188 | *p_buf = *s; 1189 | p_buf++; s++; 1190 | } 1191 | } 1192 | va_end(va2); 1193 | 1194 | buf[alloc_size-1] = '\0'; 1195 | return buf; 1196 | } 1197 | 1198 | 1199 | /* split *str on split_chr, returing the split number 'index'. 1200 | index 0 = beginning of string, NULL when index exceeds number of splits 1201 | len is set to the length of the string or 0 if NULL is returned */ 1202 | FTGDEF const char* 1203 | ftg_strsplit(const char *str, char split_ch, size_t index, size_t *len) { 1204 | size_t encounters = 0; 1205 | const char *p = str; 1206 | const char *end; 1207 | 1208 | if (index == 0) { 1209 | goto find_next_split; 1210 | } 1211 | 1212 | for (p = str; *p; p++) { 1213 | if (*p == split_ch) 1214 | encounters++; 1215 | if (encounters == index) { 1216 | break; 1217 | } 1218 | 1219 | } 1220 | 1221 | // hit end of string; requested index does not exist 1222 | if (!*p) { 1223 | if (len) *len = 0; 1224 | return NULL; 1225 | } 1226 | 1227 | p++; 1228 | 1229 | find_next_split: 1230 | // skip search for next split char if not needed 1231 | if (!len) 1232 | return p; 1233 | 1234 | for (end = p; *end && *end != split_ch; end++) 1235 | ; 1236 | *len = end-p; 1237 | 1238 | return p; 1239 | } 1240 | 1241 | 1242 | /* avoids memset argument order confusion for most common use case, 1243 | guards against 0 num, works on platforms that don't have bzero, 1244 | can't be optimized away. */ 1245 | void 1246 | ftg_bzero(void *ptr, size_t num) 1247 | { 1248 | FTG_ASSERT(num > 0); 1249 | FTG_ASSERT(ptr); 1250 | 1251 | #ifdef FTG__HAVE_MEMSET_S 1252 | errno_t err = memset_s(ptr, num, 0, num); 1253 | FTG_ASSERT(err == 0); 1254 | FTG_UNUSED(err); 1255 | #elif defined(FTG__HAVE_EXPLICIT_BZERO) 1256 | explicit_bzero(ptr, num); 1257 | #else 1258 | memset(ptr, 0, num); 1259 | #endif 1260 | } 1261 | 1262 | /* calls realloc with guards -- behaves the same way */ 1263 | void * 1264 | ftg_realloc(void *ptr, size_t size, size_t num) 1265 | { 1266 | void *alloc_ptr = NULL; 1267 | FTG_ASSERT(size != 0 && num != 0); 1268 | if (size > 0 && num > 0 && FTG__SIZE_MAX/size >= num) 1269 | { 1270 | size_t alloc_size = size * num; 1271 | 1272 | alloc_ptr = realloc(ptr, alloc_size); 1273 | FTG_ASSERT(alloc_ptr); 1274 | } 1275 | else 1276 | { 1277 | FTG_ASSERT_FAIL("ftg_realloc overflow - no memory allocated"); 1278 | } 1279 | 1280 | /* return result from realloc even if null */ 1281 | return alloc_ptr; 1282 | } 1283 | 1284 | char * 1285 | ftg_va(const char *fmt, ...) 1286 | { 1287 | static char buf[FTG_STRLEN_LONG]; 1288 | int len; 1289 | 1290 | va_list ap; 1291 | va_start(ap, fmt); 1292 | #ifdef _WIN32 1293 | len = _vsnprintf(buf, FTG_STRLEN_LONG, fmt, ap); 1294 | if (len == -1) 1295 | return NULL; 1296 | #else 1297 | len = vsnprintf(buf, FTG_STRLEN_LONG, fmt, ap); 1298 | FTG_ASSERT(len < FTG_STRLEN_LONG); 1299 | if (len >= FTG_STRLEN_LONG) 1300 | return NULL; 1301 | #endif 1302 | va_end(ap); 1303 | buf[FTG_STRLEN_LONG-1] = 0; 1304 | 1305 | return buf; 1306 | } 1307 | 1308 | /* in-place swapping of slashes to match executing platform's 1309 | directory slash order. returns a pointer to *path 1310 | */ 1311 | const char *ftg_correct_dirslash(char *path) 1312 | { 1313 | #ifdef _WIN32 1314 | char bad_slash = '/'; 1315 | char good_slash = '\\'; 1316 | #elif defined(FTG_POSIX_LIKE) 1317 | char bad_slash = '\\'; 1318 | char good_slash = '/'; 1319 | #endif 1320 | 1321 | char *p = &path[0]; 1322 | while(*p) 1323 | { 1324 | if (*p == bad_slash) 1325 | *p = good_slash; 1326 | p++; 1327 | } 1328 | 1329 | return path; 1330 | } 1331 | 1332 | /* get extension of the filename, not including the '.' */ 1333 | FTGDEF char * 1334 | ftg_get_filename_ext(const char *path) 1335 | { 1336 | size_t len = strlen(path); 1337 | const char *end = &path[len]; 1338 | 1339 | while(len > 0) 1340 | { 1341 | char c; 1342 | 1343 | len--; 1344 | c = path[len]; 1345 | 1346 | if (c == FTG_DIRSLASH) 1347 | break; 1348 | 1349 | if (c == '.') 1350 | return (char*)&path[len+1]; 1351 | } 1352 | 1353 | return (char*)end; /* return null terminator */ 1354 | } 1355 | 1356 | /* return the filename part of a path using the current OS's slash char */ 1357 | FTGDEF char * 1358 | ftg_get_filename_from_path(const char *path) 1359 | { 1360 | const char *p = path + strlen(path)-1; 1361 | while (p != path) 1362 | { 1363 | if (ftg_is_dirslash(*p)) 1364 | return (char*)p+1; 1365 | p--; 1366 | } 1367 | 1368 | return (char*)path; 1369 | } 1370 | 1371 | /* cross-environment dir slash check */ 1372 | FTGDEF ftg_inline bool 1373 | ftg_is_dirslash(char c) 1374 | { 1375 | return (c == '/' || c == '\\'); 1376 | } 1377 | 1378 | /* append a directory to a path, separated by a directory slash ('/' or '\') 1379 | return true if truncation occurs */ 1380 | FTGDEF bool 1381 | ftg_push_path(char *dst_path, const char *src_path, size_t max_path) 1382 | { 1383 | char *dst = dst_path; 1384 | const char *src = dst_path; 1385 | bool slashing = false; 1386 | char c; 1387 | 1388 | #define FTG__CHECK_MAX_PATH \ 1389 | if ((ptrdiff_t)(dst - dst_path + 1) >= (ptrdiff_t)max_path) return true; 1390 | 1391 | FTG_ASSERT(src_path && dst_path); 1392 | 1393 | // start with a slash if src_path is absolute 1394 | if (ftg_is_dirslash(dst_path[0])) 1395 | { 1396 | FTG__CHECK_MAX_PATH; 1397 | dst++; 1398 | src++; 1399 | slashing = true; 1400 | } 1401 | 1402 | while ((c = *src) != '\0') 1403 | { 1404 | bool is_slash; 1405 | FTG__CHECK_MAX_PATH; 1406 | is_slash = ftg_is_dirslash(c); 1407 | if (!slashing || !is_slash) 1408 | *dst++ = c; 1409 | 1410 | slashing = is_slash; 1411 | src++; 1412 | } 1413 | 1414 | // begin reading from src_path 1415 | src = src_path; 1416 | 1417 | // insert a separating slash (or leading slash for an abs path) 1418 | if (((dst != dst_path) && !slashing) || 1419 | ((dst == dst_path) && (ftg_is_dirslash(src_path[0])))) 1420 | { 1421 | FTG__CHECK_MAX_PATH; 1422 | *dst++ = FTG_DIRSLASH; 1423 | slashing = true; 1424 | } 1425 | 1426 | while ((c = *src) != '\0') 1427 | { 1428 | bool is_slash; 1429 | FTG__CHECK_MAX_PATH; 1430 | is_slash = ftg_is_dirslash(c); 1431 | if (!slashing || !is_slash) 1432 | *dst++ = c; 1433 | 1434 | slashing = is_slash; 1435 | src++; 1436 | } 1437 | 1438 | *dst = '\0'; 1439 | 1440 | // strip any trailing slash 1441 | if (slashing && ((dst - dst_path) > 1)) 1442 | { 1443 | dst--; 1444 | *dst = '\0'; 1445 | } 1446 | 1447 | return false; 1448 | #undef FTG__CHECK_MAX_PATH 1449 | } 1450 | 1451 | /* in-place truncate dst_path to remove last path component: 1452 | filename or directory. */ 1453 | FTGDEF void 1454 | ftg_pop_path(char *dst_path) 1455 | { 1456 | size_t length = strlen(dst_path); 1457 | 1458 | if (length == 0) 1459 | return; 1460 | 1461 | if ((length == 1) && (ftg_is_dirslash(dst_path[0]))) 1462 | return; 1463 | 1464 | if (ftg_is_dirslash(dst_path[length-1])) 1465 | length--; 1466 | 1467 | // don't strip the root / of an abs path 1468 | if (ftg_is_dirslash(dst_path[0])) 1469 | { 1470 | dst_path++; 1471 | length--; 1472 | } 1473 | 1474 | while(length > 0) 1475 | { 1476 | char c; 1477 | length--; 1478 | c = dst_path[length]; 1479 | if (ftg_is_dirslash(c)) 1480 | break; 1481 | } 1482 | 1483 | dst_path[length] = '\0'; 1484 | } 1485 | 1486 | /* read a file into a memory buffer. clear the memory with ftg_free. 1487 | if make_string is true, a null terminator is attached. 1488 | 1489 | len is the number of bytes returned, including the null terminator if 1490 | return is non-NULL, otherwise it is unchanged. 1491 | 1492 | Uses 64-bit file routines internally. 1493 | */ 1494 | #ifndef FTG_CORE_NO_STDIO 1495 | FTGDEF uint8_t * 1496 | ftg_file_read(const char *path, bool make_string, ftg_off_t *len) 1497 | { 1498 | int64_t file_len; 1499 | ftg_off_t out_size; 1500 | size_t size; 1501 | unsigned char *buf; 1502 | size_t num_elements; 1503 | FILE *fp = ftg_fopen64(path, "rb"); 1504 | if (!fp) 1505 | return NULL; 1506 | 1507 | ftg_fseek64(fp, 0L, SEEK_END); 1508 | file_len = ftg_ftell64(fp); 1509 | ftg_fseek64(fp, 0L, SEEK_SET); 1510 | 1511 | out_size = make_string ? file_len+1 : file_len; 1512 | 1513 | #ifdef _MSC_VER 1514 | # pragma warning(push) 1515 | # pragma warning(disable: 4127) 1516 | #endif 1517 | if (sizeof(int64_t) > sizeof(size_t) && (size_t)file_len > FTG__SIZE_MAX) 1518 | { 1519 | // fread will truncate if trying to read more than 2GB on a 32-bit OS in one call. 1520 | FTG_ASSERT_FAIL("File is larger than a single fread() call and will be truncated."); 1521 | file_len = FTG__SIZE_MAX; 1522 | } 1523 | #ifdef _MSC_VER 1524 | # pragma warning(pop) 1525 | #endif 1526 | 1527 | /* ftg_off_t is a signed value, size_t is an unsigned value. 1528 | rather than work with memory with a signed value, cast 1529 | to size_t. */ 1530 | FTG_ASSERT(out_size >= 0); 1531 | size = (size_t)out_size; 1532 | 1533 | buf = (unsigned char*)FTG_MALLOC(sizeof(unsigned char), size); 1534 | if (!buf) 1535 | return NULL; 1536 | 1537 | num_elements = fread(buf, (size_t)file_len, 1, fp); 1538 | if (num_elements != 1) 1539 | goto fail; 1540 | 1541 | fclose(fp); 1542 | 1543 | if (make_string) 1544 | buf[size-1] = '\0'; 1545 | 1546 | if (len) 1547 | *len = out_size; 1548 | 1549 | return buf; 1550 | 1551 | fail: 1552 | FTG_FREE(buf); 1553 | return NULL; 1554 | } 1555 | 1556 | /* write *buf bytes into a file. Returns whether all the bytes were written. 1557 | buf does not necessarily point to a string. */ 1558 | FTGDEF bool 1559 | ftg_file_write(const char *path, const uint8_t *buf, size_t buf_bytes) 1560 | { 1561 | size_t write_bytes; 1562 | FILE *fp = ftg_fopen64(path, "wb"); 1563 | 1564 | if (!fp) 1565 | return false; 1566 | 1567 | write_bytes = fwrite(buf, sizeof(uint8_t), buf_bytes, fp); 1568 | fclose(fp); 1569 | 1570 | FTG_ASSERT(write_bytes == buf_bytes); 1571 | 1572 | return write_bytes == buf_bytes; 1573 | } 1574 | 1575 | /* writes a string to a file, not including the terminator */ 1576 | FTGDEF bool 1577 | ftg_file_write_string(const char *path, const char *str) 1578 | { 1579 | size_t byte_count = strlen(str); 1580 | return ftg_file_write(path, (const uint8_t*)str, byte_count); 1581 | } 1582 | #endif /* !FTG_CORE_NO_STDIO */ 1583 | 1584 | 1585 | static ftg_inline 1586 | float ftg__get_min(float a, float b, float c) 1587 | { 1588 | float r = a; 1589 | if (r > b) r = b; 1590 | if (r > c) r = c; 1591 | return r; 1592 | } 1593 | 1594 | static ftg_inline 1595 | float ftg__get_max(float a, float b, float c) 1596 | { 1597 | float r = a; 1598 | if (r < b) r = b; 1599 | if (r < c) r = c; 1600 | return r; 1601 | } 1602 | 1603 | 1604 | void 1605 | ftg_gethsv(float r, float g, float b, float *ftg_restrict h, float *ftg_restrict s, float *ftg_restrict v) 1606 | { 1607 | float max_chan = ftg__get_max(r,g,b); 1608 | float min_chan = ftg__get_min(r,g,b); 1609 | float delta; 1610 | 1611 | *v = max_chan; 1612 | 1613 | *s = (max_chan != 0.0f) ? (( max_chan - min_chan) / max_chan ): 0.0f; 1614 | 1615 | if ( *s == 0.0f ) 1616 | { 1617 | *h = FTG_UNDEFINED_HUE; /* undefined */ 1618 | } 1619 | else 1620 | { 1621 | delta = max_chan - min_chan; 1622 | 1623 | if (r == max_chan) 1624 | *h = (g - b) / delta; /* Color between yellow and magenta */ 1625 | else if ( g == max_chan ) 1626 | *h = 2.0f + (b-r) / delta; /* Color between cyan and yellow */ 1627 | else if ( b == max_chan ) 1628 | *h = 4.0f + (r-g) / delta; /* Color between magenta and cyan */ 1629 | 1630 | *h *= 60.0f; /* Convert hue to degrees */ 1631 | if ( *h < 0.0f ) 1632 | *h += 360.0f; 1633 | } 1634 | } 1635 | 1636 | void 1637 | ftg_getrgb(float h, float s, float v, float *ftg_restrict r, float *ftg_restrict g, float *ftg_restrict b) 1638 | { 1639 | FTG_ASSERT( h==FTG_UNDEFINED_HUE || (h >= 0.0f && h <= 360.0f) ); 1640 | FTG_ASSERT( s >= 0.0f && s <= 1.0f ); 1641 | FTG_ASSERT( v >= 0.0f && v <= 1.0f ); 1642 | 1643 | if ( s == 0.0f ) 1644 | { 1645 | /* Achromatic color */ 1646 | if ( h == FTG_UNDEFINED_HUE ) 1647 | { 1648 | *r = *g = *b = v; 1649 | } 1650 | else 1651 | { 1652 | FTG_ASSERT_FAIL("If s == 0 and hue has value, this is an invalid HSV set"); 1653 | return; 1654 | } 1655 | } 1656 | else 1657 | { 1658 | // Chromatic case: s != 0 so there is a hue. 1659 | float f,p,q,t; 1660 | int i; 1661 | 1662 | if ( h == 360.0f ) 1663 | h = 0.0f; 1664 | h /= 60.0f; 1665 | 1666 | i = (int)h; 1667 | f = h - i; 1668 | 1669 | p = v * (1.0f - s); 1670 | q = v * (1.0f - (s * f )); 1671 | t = v * (1.0f - (s * (1.0f - f ))); 1672 | 1673 | switch(i) 1674 | { 1675 | case 0: *r = v; *g = t; *b = p; break; 1676 | case 1: *r = q; *g = v; *b = p; break; 1677 | case 2: *r = p; *g = v; *b = t; break; 1678 | case 3: *r = p; *g = q; *b = v; break; 1679 | case 4: *r = t; *g = p; *b = v; break; 1680 | case 5: *r = v; *g = p; *b = q; break; 1681 | } 1682 | } 1683 | } 1684 | 1685 | 1686 | /* For a rectangle inside a screen, get the scale factor that permits the rectangle 1687 | to be scaled without stretching or squashing. */ 1688 | float 1689 | ftg_aspect_correct_scale_for_rect(const float screen[2], const float rect[2]) 1690 | { 1691 | float screenAspect = screen[0] / screen[1]; 1692 | float rectAspect = rect[0] / rect[1]; 1693 | 1694 | float scaleFactor; 1695 | if (screenAspect > rectAspect) 1696 | scaleFactor = screen[1] / rect[1]; 1697 | else 1698 | scaleFactor = screen[0] / rect[0]; 1699 | 1700 | return scaleFactor; 1701 | } 1702 | 1703 | FTGDEF size_t 1704 | ftg_u8_strlen(const char *s) 1705 | { 1706 | const char *p = s; 1707 | size_t j = 0; 1708 | 1709 | while (*p) { 1710 | if ((*p & 0xc0) != 0x80) 1711 | j++; 1712 | p++; 1713 | } 1714 | 1715 | return j; 1716 | } 1717 | 1718 | bool 1719 | ftg_ia_is_init(struct ftg_index_array_s *i) 1720 | { 1721 | return (i && i->indices && i->records > 0); 1722 | } 1723 | 1724 | void 1725 | ftg_ia_prealloc(struct ftg_index_array_s *i, size_t initial_record_count) 1726 | { 1727 | FTG_ASSERT(initial_record_count > 0); 1728 | 1729 | if (i->records > initial_record_count || initial_record_count == 0) 1730 | return; 1731 | 1732 | i->indices = (size_t*)FTG_MALLOC(initial_record_count, sizeof(size_t)); 1733 | if (!i->indices) 1734 | return; 1735 | i->records = initial_record_count; 1736 | i->count = 0; 1737 | } 1738 | 1739 | bool 1740 | ftg_ia_append(struct ftg_index_array_s *i, size_t index_value) 1741 | { 1742 | if (i->count+1 > i->records) 1743 | { 1744 | size_t new_record_count; 1745 | size_t *new_frame_indices; 1746 | 1747 | if (i->count == 0) 1748 | new_record_count = FTG_IA_INITIAL_RECORD_COUNT; 1749 | else 1750 | new_record_count = i->count*2; 1751 | 1752 | new_frame_indices = (size_t*)FTG_REALLOC(i->indices, 1753 | sizeof(size_t), new_record_count); 1754 | 1755 | if (!new_frame_indices) { 1756 | FTG_ASSERT_FAIL("Could not reallocate index array."); 1757 | return false; 1758 | } 1759 | 1760 | i->records = new_record_count; 1761 | i->indices = new_frame_indices; 1762 | } 1763 | 1764 | i->indices[i->count] = index_value; 1765 | i->count++; 1766 | 1767 | return true; 1768 | } 1769 | 1770 | void 1771 | ftg_ia_free(struct ftg_index_array_s *i) 1772 | { 1773 | if (i->indices) 1774 | FTG_FREE(i->indices); 1775 | i->records = i->count = 0; 1776 | } 1777 | 1778 | /* portable 64-bit UTF-8 file routines */ 1779 | #ifndef FTG_CORE_NO_STDIO 1780 | 1781 | #ifdef _WIN32 1782 | /* these utf8 conversion routines are taken from public domain stb.h 1783 | with minor tweaks */ 1784 | FTGDEF ftg_wchar_t * 1785 | ftg_wchar_from_u8(const char *in_str, ftg_wchar_t *out_str, size_t n) 1786 | { 1787 | unsigned char *str = (unsigned char *)in_str; 1788 | uint32_t c; 1789 | unsigned int i = 0; 1790 | --n; 1791 | while (*str) { 1792 | if (i >= n) 1793 | return NULL; 1794 | if (!(*str & 0x80)) 1795 | out_str[i++] = *str++; 1796 | else if ((*str & 0xe0) == 0xc0) { 1797 | if (*str < 0xc2) return NULL; 1798 | c = (*str++ & 0x1f) << 6; 1799 | if ((*str & 0xc0) != 0x80) return NULL; 1800 | out_str[i++] = (ftg_wchar_t)(c + (*str++ & 0x3f)); 1801 | } else if ((*str & 0xf0) == 0xe0) { 1802 | if (*str == 0xe0 && (str[1] < 0xa0 || str[1] > 0xbf)) return NULL; 1803 | if (*str == 0xed && str[1] > 0x9f) return NULL; // str[1] < 0x80 is checked below 1804 | c = (*str++ & 0x0f) << 12; 1805 | if ((*str & 0xc0) != 0x80) return NULL; 1806 | c += (*str++ & 0x3f) << 6; 1807 | if ((*str & 0xc0) != 0x80) return NULL; 1808 | out_str[i++] = (ftg_wchar_t)(c + (*str++ & 0x3f)); 1809 | } else if ((*str & 0xf8) == 0xf0) { 1810 | if (*str > 0xf4) return NULL; 1811 | if (*str == 0xf0 && (str[1] < 0x90 || str[1] > 0xbf)) return NULL; 1812 | if (*str == 0xf4 && str[1] > 0x8f) return NULL; // str[1] < 0x80 is checked below 1813 | c = (*str++ & 0x07) << 18; 1814 | if ((*str & 0xc0) != 0x80) return NULL; 1815 | c += (*str++ & 0x3f) << 12; 1816 | if ((*str & 0xc0) != 0x80) return NULL; 1817 | c += (*str++ & 0x3f) << 6; 1818 | if ((*str & 0xc0) != 0x80) return NULL; 1819 | c += (*str++ & 0x3f); 1820 | // utf-8 encodings of values used in surrogate pairs are invalid 1821 | if ((c & 0xFFFFF800) == 0xD800) return NULL; 1822 | if (c >= 0x10000) { 1823 | c -= 0x10000; 1824 | if ((size_t)i + 2 > n) return NULL; 1825 | out_str[i++] = 0xD800 | (0x3ff & (c >> 10)); 1826 | out_str[i++] = 0xDC00 | (0x3ff & (c )); 1827 | } 1828 | } else 1829 | return NULL; 1830 | } 1831 | out_str[i] = 0; 1832 | return out_str; 1833 | } 1834 | 1835 | FTGDEF char * 1836 | ftg_wchar_to_u8(const ftg_wchar_t *in_str, char *out_str, size_t n) 1837 | { 1838 | unsigned int i = 0; 1839 | --n; 1840 | while (*in_str) { 1841 | if (*in_str < 0x80) { 1842 | if ((size_t)i+1 > n) return NULL; 1843 | out_str[i++] = (char) *in_str++; 1844 | } else if (*in_str < 0x800) { 1845 | if ((size_t)i+2 > n) return NULL; 1846 | out_str[i++] = (char)(0xc0 + (*in_str >> 6)); 1847 | out_str[i++] = 0x80 + (*in_str & 0x3f); 1848 | in_str += 1; 1849 | } else if (*in_str >= 0xd800 && *in_str < 0xdc00) { 1850 | uint32_t c; 1851 | if ((size_t)i+4 > n) return NULL; 1852 | c = ((in_str[0] - 0xd800) << 10) + ((in_str[1]) - 0xdc00) + 0x10000; 1853 | out_str[i++] = (char)(0xf0 + (c >> 18)); 1854 | out_str[i++] = 0x80 + ((c >> 12) & 0x3f); 1855 | out_str[i++] = 0x80 + ((c >> 6) & 0x3f); 1856 | out_str[i++] = 0x80 + ((c ) & 0x3f); 1857 | in_str += 2; 1858 | } else if (*in_str >= 0xdc00 && *in_str < 0xe000) { 1859 | return NULL; 1860 | } else { 1861 | if ((size_t)i+3 > n) return NULL; 1862 | out_str[i++] = 0xe0 + (*in_str >> 12); 1863 | out_str[i++] = 0x80 + ((*in_str >> 6) & 0x3f); 1864 | out_str[i++] = 0x80 + ((*in_str ) & 0x3f); 1865 | in_str += 1; 1866 | } 1867 | } 1868 | out_str[i] = 0; 1869 | return out_str; 1870 | } 1871 | #endif 1872 | 1873 | FILE * 1874 | ftg_fopen64(const char *filename, const char *type) 1875 | { 1876 | #ifdef _WIN32 1877 | /* win32 fopen is not utf-8, it's some stupid codepage */ 1878 | ftg_wchar_t wfilename[FTG_STRLEN_LONG]; 1879 | ftg_wchar_t wtype[4]; 1880 | 1881 | ftg_wchar_from_u8(filename, wfilename, FTG_STRLEN_LONG); 1882 | ftg_wchar_from_u8(type, wtype, 4); 1883 | return _wfopen(wfilename, wtype); 1884 | #else 1885 | 1886 | # if FTG__IO64_EXPLICIT 1887 | return fopen64(filename, type); 1888 | # else 1889 | return fopen(filename, type); 1890 | # endif 1891 | 1892 | #endif 1893 | } 1894 | 1895 | ftg_off_t 1896 | ftg_ftell64(FILE *stream) 1897 | { 1898 | #ifdef _WIN32 1899 | int64_t tell = _ftelli64(stream); 1900 | 1901 | #if FTG_DEBUG 1902 | if (tell == -1L) 1903 | { 1904 | const char *fail_reason = strerror(errno); 1905 | FTG_ASSERT_FAIL(fail_reason); 1906 | } 1907 | #endif 1908 | return tell; 1909 | 1910 | #else 1911 | 1912 | # if FTG__IO64_EXPLICIT 1913 | off64_t tell = ftello64(stream); 1914 | # else 1915 | off_t tell = ftello(stream); 1916 | # endif 1917 | 1918 | FTG_ASSERT(tell >= 0); 1919 | return tell; 1920 | #endif 1921 | } 1922 | 1923 | int64_t 1924 | ftg_fseek64(FILE *stream, ftg_off_t offset, int origin) 1925 | { 1926 | #ifdef _WIN32 1927 | int seek = _fseeki64(stream, offset, origin); 1928 | FTG_ASSERT(seek == 0); 1929 | return (int64_t)seek; 1930 | #else 1931 | 1932 | # if FTG__IO64_EXPLICIT 1933 | int seek = fseeko64(stream, offset, origin); 1934 | # else 1935 | int seek = fseeko(stream, offset, origin); 1936 | # endif 1937 | 1938 | FTG_ASSERT(seek != -1); 1939 | return (int64_t)seek; 1940 | #endif 1941 | } 1942 | 1943 | struct ftg_dirhandle_s { 1944 | #if defined(_WIN32) 1945 | HANDLE d; 1946 | # elif defined(FTG_POSIX_LIKE) 1947 | DIR *d; 1948 | #endif 1949 | }; 1950 | 1951 | /* see ftg__test_opendir for iteration example 1952 | returns *out, the first file in the directory. 1953 | */ 1954 | FTGDEF const char * 1955 | ftg_opendir(ftg_dirhandle_t *handle, const char *path, char *out, size_t out_len) 1956 | { 1957 | #ifdef FTG_POSIX_LIKE 1958 | struct dirent *dirent; 1959 | bool trunc; 1960 | 1961 | handle->d = opendir(path); 1962 | if (handle->d == NULL) 1963 | { 1964 | FTG_ASSERT_FAIL("OpenDir failed."); 1965 | 1966 | *out = 0; 1967 | return out; 1968 | } 1969 | 1970 | dirent = readdir(handle->d); 1971 | trunc = ftg_strncpy(out, dirent->d_name, out_len); 1972 | if (trunc) 1973 | { 1974 | FTG_ASSERT_FAIL(ftg_va("Truncation on ftg_opendir for path %s.", path)); 1975 | /* never return truncated paths */ 1976 | *out = 0; 1977 | return out; 1978 | } 1979 | 1980 | return out; 1981 | #elif defined(_WIN32) 1982 | WIN32_FIND_DATAW fd; 1983 | ftg_wchar_t search_path[MAX_PATH+3]; 1984 | 1985 | if (ftg_u8_strlen(path) > MAX_PATH + 3) 1986 | { 1987 | FTG_ASSERT_FAIL("path is too long"); 1988 | *out = 0; 1989 | return out; 1990 | } 1991 | 1992 | ftg_wchar_from_u8(path, search_path, MAX_PATH+3); 1993 | wcsncat(search_path, L"\\*", 2); 1994 | 1995 | handle->d = FindFirstFileW(search_path, &fd); 1996 | if (handle->d == INVALID_HANDLE_VALUE) 1997 | { 1998 | FTG_ASSERT_FAIL("OpenDir failed."); 1999 | *out = 0; 2000 | return out; 2001 | } 2002 | 2003 | ftg_wchar_to_u8(fd.cFileName, out, out_len); 2004 | 2005 | return out; 2006 | #endif 2007 | } 2008 | 2009 | FTGDEF const char * 2010 | ftg_readdir(ftg_dirhandle_t *handle, char *out, size_t out_len) 2011 | { 2012 | #ifdef FTG_POSIX_LIKE 2013 | struct dirent *dirent; 2014 | bool trunc; 2015 | 2016 | dirent = readdir(handle->d); 2017 | if (dirent==NULL) 2018 | { 2019 | *out = 0; 2020 | return out; 2021 | } 2022 | 2023 | trunc = ftg_strncpy(out, dirent->d_name, out_len); 2024 | if (trunc) 2025 | { 2026 | FTG_ASSERT_FAIL(ftg_va("Truncation on ftg_readdir for path %s.", 2027 | dirent->d_name)); 2028 | *out = 0; 2029 | return out; 2030 | } 2031 | 2032 | return out; 2033 | 2034 | #elif defined(_WIN32) 2035 | WIN32_FIND_DATAW fd; 2036 | 2037 | if (FindNextFileW(handle->d, &fd) != 0) 2038 | { 2039 | ftg_wchar_to_u8(fd.cFileName, out, out_len); 2040 | } 2041 | else 2042 | { 2043 | DWORD error = GetLastError(); 2044 | if (error == ERROR_NO_MORE_FILES) 2045 | { 2046 | *out = 0; 2047 | return out; 2048 | } 2049 | else 2050 | { 2051 | FTG_ASSERT_FAIL(ftg_va("ftg_readdir() error %d", error)); 2052 | *out = 0; 2053 | return out; 2054 | } 2055 | } 2056 | 2057 | return out; 2058 | #endif 2059 | } 2060 | 2061 | FTGDEF void 2062 | ftg_closedir(ftg_dirhandle_t *handle) 2063 | { 2064 | #ifdef FTG_POSIX_LIKE 2065 | int ret = closedir(handle->d); 2066 | FTG_ASSERT(ret != -1); 2067 | FTG_UNUSED(ret); 2068 | 2069 | return; 2070 | 2071 | #elif defined(_WIN32) 2072 | if (!FindClose(handle->d)) 2073 | { 2074 | FTG_ASSERT_FAIL(ftg_va("ftg_closedir() error: %d", GetLastError())); 2075 | } 2076 | #endif 2077 | } 2078 | 2079 | /* returns true if path exists and is a directory */ 2080 | FTGDEF bool 2081 | ftg_is_dir(const char *path) 2082 | { 2083 | #ifdef FTG_POSIX_LIKE 2084 | struct stat buf; 2085 | if (stat(path, &buf) == -1) 2086 | return false; /* does not exist */ 2087 | 2088 | return (S_ISDIR(buf.st_mode)); 2089 | #elif defined(_WIN32) 2090 | ftg_wchar_t wpath[FTG_STRLEN_LONG]; 2091 | 2092 | DWORD attr = GetFileAttributesW(ftg_wchar_from_u8(path, wpath, FTG_STRLEN_LONG)); 2093 | if (attr == INVALID_FILE_ATTRIBUTES) 2094 | return false; /* does not exist */ 2095 | 2096 | return ((attr & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY); 2097 | #endif 2098 | } 2099 | 2100 | /* returns true if the dir or file exists */ 2101 | FTGDEF bool 2102 | ftg_path_exists(const char *path) 2103 | { 2104 | #ifdef FTG_POSIX_LIKE 2105 | return access(path, F_OK) != -1; 2106 | #elif defined(_WIN32) 2107 | ftg_wchar_t wide_path[MAX_PATH]; 2108 | DWORD attrib; 2109 | 2110 | ftg_wchar_from_u8(path, wide_path, MAX_PATH); 2111 | attrib = GetFileAttributesW(wide_path); 2112 | 2113 | return (attrib != INVALID_FILE_ATTRIBUTES); 2114 | #endif 2115 | } 2116 | 2117 | /* make a directory that must not already exist */ 2118 | FTGDEF bool 2119 | ftg_mkdir(const char *path) 2120 | { 2121 | #ifdef FTG_POSIX_LIKE 2122 | int result = mkdir(path, FTG_DIRECTORY_MODE); 2123 | if (result != 0) 2124 | { 2125 | FTG_ASSERT_FAIL(strerror(errno)); 2126 | } 2127 | 2128 | return (result == 0); 2129 | #elif defined(_WIN32) 2130 | ftg_wchar_t wide_path[MAX_PATH]; 2131 | int result; 2132 | 2133 | ftg_wchar_from_u8(path, wide_path, MAX_PATH); 2134 | result = _wmkdir(wide_path); 2135 | if (result != 0) 2136 | { 2137 | FTG_ASSERT_FAIL(strerror(errno)); 2138 | } 2139 | 2140 | return (result == 0); 2141 | #endif 2142 | } 2143 | 2144 | /* recursively make all directories in *path that are not yet created */ 2145 | FTGDEF void 2146 | ftg_mkalldirs(const char *path) 2147 | { 2148 | if (!ftg_is_dir(path)) 2149 | { 2150 | if(path[0] != '\0') 2151 | { 2152 | bool trunc; 2153 | char parent_path[FTG_STRLEN_LONG] = {'\0'}; 2154 | 2155 | trunc = ftg_push_path(parent_path, path, FTG_STRLEN_LONG); 2156 | FTG_ASSERT(!trunc); 2157 | FTG_UNUSED(trunc); 2158 | ftg_pop_path(parent_path); 2159 | 2160 | ftg_mkalldirs(parent_path); 2161 | ftg_mkdir(path); 2162 | } 2163 | } 2164 | } 2165 | 2166 | static void 2167 | ftg__remove_dir_contents(const char *path) 2168 | { 2169 | ftg_dirhandle_t dir_handle; 2170 | char dir_path[FTG_STRLEN_LONG]; 2171 | const char *filename; 2172 | 2173 | int i = 5; 2174 | 2175 | filename = ftg_opendir(&dir_handle, path, dir_path, FTG_STRLEN_LONG); 2176 | while(*filename) 2177 | { 2178 | if (strcmp(filename, ".") && strcmp(filename, "..")) 2179 | { 2180 | char child_path[FTG_STRLEN_LONG] = {'\0'}; 2181 | ftg_push_path(child_path, path, FTG_STRLEN_LONG); 2182 | ftg_push_path(child_path, filename, FTG_STRLEN_LONG); 2183 | 2184 | i--; 2185 | if (i == 0) 2186 | exit(0); 2187 | ftg_rmalldirs(child_path); 2188 | } 2189 | 2190 | filename = ftg_readdir(&dir_handle, dir_path, FTG_STRLEN_LONG); 2191 | } 2192 | 2193 | ftg_closedir(&dir_handle); 2194 | } 2195 | 2196 | /* recursively remove all directories in *path, deleting contents */ 2197 | FTGDEF void 2198 | ftg_rmalldirs(const char *path) 2199 | { 2200 | if (ftg_is_dir(path)) 2201 | { 2202 | ftg__remove_dir_contents(path); 2203 | ftg_rmdir(path); 2204 | } 2205 | else 2206 | { 2207 | int result = remove(path); 2208 | FTG_ASSERT(result == 0); 2209 | FTG_UNUSED(result); 2210 | } 2211 | } 2212 | 2213 | /* remove a directory that must already exist and be empty */ 2214 | FTGDEF bool 2215 | ftg_rmdir(const char *path) 2216 | { 2217 | #ifdef FTG_POSIX_LIKE 2218 | int result = rmdir(path); 2219 | if (result != 0) 2220 | { 2221 | FTG_ASSERT_FAIL(strerror(errno)); 2222 | } 2223 | 2224 | return (result == 0); 2225 | #elif defined(_WIN32) 2226 | ftg_wchar_t wide_path[MAX_PATH]; 2227 | int result; 2228 | 2229 | ftg_wchar_from_u8(path, wide_path, MAX_PATH); 2230 | result = _wrmdir(wide_path); 2231 | if (result != 0) 2232 | { 2233 | FTG_ASSERT_FAIL(strerror(errno)); 2234 | } 2235 | 2236 | return (result == 0); 2237 | #endif 2238 | } 2239 | 2240 | /* add a console on windows -- exit silently on others. 2241 | note that only one console can exist per process */ 2242 | #ifdef _WIN32 2243 | static FILE *ftg__io[3] = {0,0,0}; 2244 | #endif 2245 | 2246 | FTGDEF void 2247 | ftg_alloc_console(void) 2248 | { 2249 | #if defined(_WIN32) 2250 | int i; 2251 | bool result = AllocConsole(); 2252 | 2253 | DWORD mode; 2254 | HANDLE handle_stdout; 2255 | 2256 | FTG_ASSERT(result); FTG_UNUSED(result); 2257 | if (!result) 2258 | return; 2259 | 2260 | #if FTG_DEBUG 2261 | for (i = 0; i < 3; i++) 2262 | FTG_ASSERT(!ftg__io[i]); 2263 | #endif 2264 | FTG_UNUSED(i); 2265 | 2266 | ftg__io[0] = freopen("conin$","r",stdin); 2267 | ftg__io[1] = freopen("conout$","w",stdout); 2268 | ftg__io[2] = freopen("conout$","w",stderr); 2269 | 2270 | // enable ansi codes 2271 | handle_stdout = GetStdHandle(STD_OUTPUT_HANDLE); 2272 | GetConsoleMode(handle_stdout, &mode); 2273 | mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; 2274 | mode |= DISABLE_NEWLINE_AUTO_RETURN; 2275 | SetConsoleMode(handle_stdout, mode); 2276 | #endif 2277 | } 2278 | 2279 | FTGDEF void 2280 | ftg_free_console(void) 2281 | { 2282 | #if defined(_WIN32) 2283 | int i; 2284 | 2285 | FreeConsole(); 2286 | for (i = 0; i < 3; i++) 2287 | { 2288 | if (!ftg__io[i]) continue; 2289 | fclose(ftg__io[i]); 2290 | ftg__io[i] = NULL; 2291 | } 2292 | #endif 2293 | } 2294 | 2295 | #endif /* !FTG_CORE_NO_STDIO */ 2296 | 2297 | 2298 | // Paul Hsieh hash (borrowed from stb.h with type changes for 2299 | // portability) 2300 | #define ftg__get16_slow(p) ((p)[0] + ((p)[1] << 8)) 2301 | #if defined(_MSC_VER) 2302 | #define ftg__get16(p) (*((unsigned short *) (p))) 2303 | #else 2304 | #define ftg__get16(p) ftg__get16_slow(p) 2305 | #endif 2306 | 2307 | FTGDEF uint32_t 2308 | ftg_hash_fast(const void *p, uint32_t len) 2309 | { 2310 | unsigned char *q = (unsigned char *) p; 2311 | uint32_t hash = len; 2312 | 2313 | if (len <= 0 || q == NULL) return 0; 2314 | 2315 | /* Main loop */ 2316 | if (((size_t) q & 1) == 0) { 2317 | for (;len > 3; len -= 4) { 2318 | uint32_t val; 2319 | hash += ftg__get16(q); 2320 | val = (ftg__get16(q+2) << 11); 2321 | hash = (hash << 16) ^ hash ^ val; 2322 | q += 4; 2323 | hash += hash >> 11; 2324 | } 2325 | } else { 2326 | for (;len > 3; len -= 4) { 2327 | uint32_t val; 2328 | hash += ftg__get16_slow(q); 2329 | val = (ftg__get16_slow(q+2) << 11); 2330 | hash = (hash << 16) ^ hash ^ val; 2331 | q += 4; 2332 | hash += hash >> 11; 2333 | } 2334 | } 2335 | 2336 | /* Handle end cases */ 2337 | switch (len) { 2338 | case 3: hash += ftg__get16_slow(q); 2339 | hash ^= hash << 16; 2340 | hash ^= q[2] << 18; 2341 | hash += hash >> 11; 2342 | break; 2343 | case 2: hash += ftg__get16_slow(q); 2344 | hash ^= hash << 11; 2345 | hash += hash >> 17; 2346 | break; 2347 | case 1: hash += q[0]; 2348 | hash ^= hash << 10; 2349 | hash += hash >> 1; 2350 | break; 2351 | case 0: break; 2352 | } 2353 | 2354 | /* Force "avalanching" of final 127 bits */ 2355 | hash ^= hash << 3; 2356 | hash += hash >> 5; 2357 | hash ^= hash << 4; 2358 | hash += hash >> 17; 2359 | hash ^= hash << 25; 2360 | hash += hash >> 6; 2361 | 2362 | return hash; 2363 | } 2364 | 2365 | FTGDEF uint32_t 2366 | ftg_hash_number(uint32_t hash) 2367 | { 2368 | hash ^= hash << 3; 2369 | hash += hash >> 5; 2370 | hash ^= hash << 4; 2371 | hash += hash >> 17; 2372 | hash ^= hash << 25; 2373 | hash += hash >> 6; 2374 | 2375 | return hash; 2376 | } 2377 | 2378 | 2379 | 2380 | 2381 | /* stopwatch (code timing routines) */ 2382 | 2383 | #ifdef FTG_ENABLE_STOPWATCH 2384 | 2385 | #ifdef _WIN32 2386 | # define WIN32_LEAN_AND_MEAN 2387 | # include 2388 | # undef WIN32_LEAN_AND_MEAN 2389 | #elif defined(__APPLE__) 2390 | # include 2391 | # define FTG__CLOCK_MACH 2392 | #else 2393 | # include 2394 | # define FTG__CLOCK_GETTIME 2395 | # ifdef CLOCK_MONOTONIC_RAW 2396 | # define FTG__CLOCK_MONOTONIC CLOCK_MONOTONIC_RAW 2397 | # else 2398 | # define FTG__CLOCK_MONOTONIC CLOCK_MONOTONIC 2399 | # endif 2400 | #endif 2401 | 2402 | #ifndef FTG_STOPWATCH_REPORTER 2403 | # define FTG_STOPWATCH_REPORTER ftg__default_stopwatch_reporter 2404 | #endif 2405 | 2406 | 2407 | #ifdef FTG__CLOCK_GETTIME 2408 | static uint64_t ftg__stopwatch_get_usec_since_init(void) 2409 | { 2410 | static struct timespec ftg__init_time = {0,0}; 2411 | struct timespec now, since_init; 2412 | uint64_t usec; 2413 | int ret; 2414 | 2415 | if (ftg__init_time.tv_sec == 0 && ftg__init_time.tv_nsec == 0) 2416 | { 2417 | ret = clock_gettime(FTG__CLOCK_MONOTONIC, &ftg__init_time); 2418 | FTG_ASSERT(ret==0); FTG_UNUSED(ret); 2419 | } 2420 | 2421 | ret = clock_gettime(FTG__CLOCK_MONOTONIC, &now); 2422 | FTG_ASSERT(ret==0); FTG_UNUSED(ret); 2423 | 2424 | since_init.tv_sec = now.tv_sec - ftg__init_time.tv_sec; 2425 | since_init.tv_nsec = now.tv_nsec - ftg__init_time.tv_nsec; 2426 | 2427 | usec = (since_init.tv_sec * 1000000) + (since_init.tv_nsec / 1000); 2428 | 2429 | return usec; 2430 | } 2431 | #endif 2432 | 2433 | 2434 | static uint64_t 2435 | ftg__stopwatch_elapsed_usec(struct ftg_stopwatch_s *sw) 2436 | { 2437 | #ifdef _WIN32 2438 | LARGE_INTEGER end_time, freq, elapsed; 2439 | 2440 | QueryPerformanceCounter(&end_time); 2441 | QueryPerformanceFrequency(&freq); 2442 | 2443 | // calculate elapsed time 2444 | elapsed.QuadPart = end_time.QuadPart - sw->start_time; 2445 | 2446 | // convert to microseconds 2447 | elapsed.QuadPart *= 1000000; 2448 | 2449 | // divide by ticks per second 2450 | elapsed.QuadPart /= freq.QuadPart; 2451 | 2452 | return (uint64_t)elapsed.QuadPart; 2453 | #elif defined(FTG__CLOCK_MACH) 2454 | static mach_timebase_info_data_t timebase = {0,0}; 2455 | 2456 | uint64_t end, elapsed; 2457 | 2458 | if (timebase.denom == 0) 2459 | mach_timebase_info(&timebase); 2460 | 2461 | end = mach_absolute_time(); 2462 | elapsed = end - sw->start_time; 2463 | 2464 | // convert to microseconds before applying timebase 2465 | elapsed /= 1000; 2466 | elapsed *= timebase.numer / timebase.denom; 2467 | 2468 | return elapsed; 2469 | 2470 | #elif defined(FTG__CLOCK_GETTIME) 2471 | return ftg__stopwatch_get_usec_since_init() - sw->start_time; 2472 | #endif 2473 | } 2474 | 2475 | static void 2476 | ftg__stopwatch_set_start_time(struct ftg_stopwatch_s *sw) 2477 | { 2478 | #ifdef _WIN32 2479 | LARGE_INTEGER start_time; 2480 | 2481 | QueryPerformanceCounter(&start_time); 2482 | sw->start_time = (int64_t)start_time.QuadPart; 2483 | 2484 | #elif defined(FTG__CLOCK_MACH) 2485 | sw->start_time = mach_absolute_time(); 2486 | 2487 | #elif defined(FTG__CLOCK_GETTIME) 2488 | sw->start_time = ftg__stopwatch_get_usec_since_init(); 2489 | #endif 2490 | } 2491 | 2492 | void 2493 | ftg__stopwatch_next_bucket(struct ftg_stopwatch_s *sw, const char *bucket_name, size_t bucket_idx) 2494 | { 2495 | // if this is hit then FTG_STOPWATCH_START was not called after FTG_STOPWATCH_DECL. 2496 | FTG_ASSERT(sw->current_bucket != FTG__MAX_STOPWATCH_TIMES+100); 2497 | 2498 | // it is possible to end up entering a sub-function that is being logged without coming 2499 | // from the parent which is logging. This guards against incorrectly logging the time. 2500 | if (sw->currently_logging == false) 2501 | return; 2502 | 2503 | if (sw->times[bucket_idx] == 0) 2504 | { 2505 | bool truncate = ftg_strncpy(sw->time_names[bucket_idx], bucket_name, FTG_STRLEN_SHORT); 2506 | FTG_UNUSED(truncate); 2507 | } 2508 | 2509 | sw->times[sw->current_bucket] += ftg__stopwatch_elapsed_usec(sw); 2510 | ftg__stopwatch_set_start_time(sw); 2511 | 2512 | if (bucket_idx >= FTG__MAX_STOPWATCH_TIMES) 2513 | { 2514 | FTG_ASSERT_FAIL("stopwatch with bucket index > FTG__MAX_STOPWATCH_TIMES specified. not dumped."); 2515 | sw->currently_logging = false; 2516 | return; 2517 | } 2518 | 2519 | sw->current_bucket = bucket_idx; 2520 | } 2521 | 2522 | void 2523 | ftg__stopwatch_start(struct ftg_stopwatch_s *sw, const char *name, const char *bucket_name, size_t bucket_idx) 2524 | { 2525 | bool truncate = ftg_strncpy(sw->name, name, FTG_STRLEN_SHORT); 2526 | FTG_UNUSED(truncate); 2527 | 2528 | sw->currently_logging = true; 2529 | ftg_bzero(sw->times, sizeof(sw->times)); 2530 | ftg_bzero(sw->time_names, sizeof(sw->time_names)); 2531 | 2532 | // todo: compress this 2533 | if (bucket_idx >= FTG__MAX_STOPWATCH_TIMES) 2534 | { 2535 | FTG_ASSERT_FAIL("stopwatch with bucket index > FTG__MAX_STOPWATCH_TIMES specified. not dumped."); 2536 | return; 2537 | } 2538 | 2539 | sw->current_bucket = bucket_idx; 2540 | truncate = ftg_strncpy(sw->time_names[bucket_idx], bucket_name, FTG_STRLEN_SHORT); 2541 | FTG_UNUSED(truncate); 2542 | 2543 | // start counter at the end of this function to avoid profiling setup work. 2544 | ftg__stopwatch_set_start_time(sw); 2545 | } 2546 | 2547 | void 2548 | ftg__stopwatch_stop(struct ftg_stopwatch_s *sw) 2549 | { 2550 | // if this is hit then FTG_STOPWATCH_START was not called after FTG_STOPWATCH_DECL. 2551 | FTG_ASSERT(sw->current_bucket != FTG__MAX_STOPWATCH_TIMES+100); 2552 | 2553 | sw->times[sw->current_bucket] += ftg__stopwatch_elapsed_usec(sw); 2554 | 2555 | sw->currently_logging = false; 2556 | FTG_STOPWATCH_REPORTER(sw); 2557 | } 2558 | 2559 | void 2560 | ftg__default_stopwatch_reporter(struct ftg_stopwatch_s *sw) 2561 | { 2562 | size_t i; 2563 | uint64_t accum_ms = 0; 2564 | #if FTG_DEBUG 2565 | const char debug_str[] = "FTG_DEBUG=1 "; 2566 | #else 2567 | const char debug_str[] = ""; 2568 | #endif 2569 | 2570 | printf("%sstopwatch %s completed:\n", debug_str, sw->name); 2571 | for (i = 0; i < FTG__MAX_STOPWATCH_TIMES; ++i) 2572 | { 2573 | if (sw->times[i] == 0 && sw->time_names[i][0] == '\0') 2574 | continue; 2575 | 2576 | accum_ms += sw->times[i] / 1000; 2577 | printf("\t%s[%" FTG_SPEC_SIZE_T "]: %" FTG_SPEC_UINT64 " usec\n", 2578 | sw->time_names[i], i, sw->times[i]); 2579 | } 2580 | 2581 | printf("total ms: %" FTG_SPEC_UINT64 "\n", accum_ms); 2582 | } 2583 | 2584 | #endif /* FTG_ENABLE_STOPWATCH */ 2585 | 2586 | static void 2587 | ftg__arena_grow(ftg_arena_t** arena, size_t min_size) 2588 | { 2589 | size_t size; 2590 | ftg_arena_t* a = *arena; 2591 | 2592 | if (a->ptr != NULL) { 2593 | ftg_arena_t* new_block = (ftg_arena_t*)FTG_MALLOC(sizeof(ftg_arena_t), 1); 2594 | 2595 | new_block->prev = a; 2596 | a = *arena = new_block; 2597 | } 2598 | 2599 | size = FTG_ALIGN_UP(FTG_MAX(FTG_ARENA_BLOCK_SIZE, min_size), FTG_ARENA_ALIGNMENT); 2600 | a->ptr = a->start = (uint8_t*)FTG_MALLOC(size, 1); 2601 | a->end = a->ptr + size; 2602 | } 2603 | 2604 | // alloc memory from arena. returns null if an alloc failed. 2605 | // arena pointer is altered if a new page is created 2606 | // zero thread safety 2607 | void* 2608 | ftg_arena_alloc(ftg_arena_t** arena, size_t size) 2609 | { 2610 | void* ptr; 2611 | if (size > (size_t)((*arena)->end - (*arena)->ptr)) { 2612 | ftg__arena_grow(arena, size); 2613 | FTG_ASSERT(size <= (size_t)((*arena)->end - (*arena)->ptr)); 2614 | if (!(*arena)->ptr) 2615 | return NULL; 2616 | } 2617 | 2618 | ptr = (*arena)->ptr; 2619 | (*arena)->ptr = (uint8_t*)FTG_ALIGN_UP_PTR((*arena)->ptr + size, FTG_ARENA_ALIGNMENT); 2620 | FTG_ASSERT((*arena)->ptr <= (*arena)->end); 2621 | FTG_ASSERT(ptr == FTG_ALIGN_DOWN_PTR(ptr, FTG_ARENA_ALIGNMENT)); 2622 | 2623 | return ptr; 2624 | } 2625 | 2626 | ftg_arena_t* 2627 | ftg_arena_new(void) 2628 | { 2629 | ftg_arena_t* arena = (ftg_arena_t*)FTG_MALLOC(sizeof(ftg_arena_t), 1); 2630 | ftg_bzero(arena, sizeof(ftg_arena_t)); 2631 | 2632 | return arena; 2633 | } 2634 | 2635 | void 2636 | ftg_arena_free(ftg_arena_t* arena) 2637 | { 2638 | ftg_arena_t *prev; 2639 | 2640 | while (arena) { 2641 | if (arena->start) { 2642 | FTG_FREE(arena->start); 2643 | } 2644 | 2645 | prev = arena->prev; 2646 | FTG_FREE(arena); 2647 | arena = prev; 2648 | } 2649 | } 2650 | 2651 | // ftg_strtof wraps strtof, returning 0 on success. 2652 | // out_float is assigned either way. 2653 | int 2654 | ftg_strtof(const char *str, char **endptr, float *out_float) { 2655 | char *end; 2656 | float val; 2657 | 2658 | val = strtof(str, &end); 2659 | *out_float = val; 2660 | if (end == str) { 2661 | if (endptr) 2662 | *endptr = end; 2663 | return 1; 2664 | } 2665 | 2666 | if (endptr) 2667 | *endptr = end; 2668 | 2669 | return 0; 2670 | } 2671 | 2672 | int 2673 | ftg_strtol(const char *str, char **endptr, int base, long int *out_long) { 2674 | char *end; 2675 | long int val; 2676 | 2677 | val = strtol(str, &end, base); 2678 | *out_long = val; 2679 | if (end == str) { 2680 | if (endptr) 2681 | *endptr = end; 2682 | return 1; 2683 | } 2684 | 2685 | if (endptr) 2686 | *endptr = end; 2687 | 2688 | return 0; 2689 | 2690 | } 2691 | 2692 | /* test suite 2693 | 2694 | ftg_decl_suite() should be called somewhere in the declaring 2695 | C file. 2696 | */ 2697 | #ifdef FTGT_TESTS_ENABLED 2698 | 2699 | #include 2700 | 2701 | struct ftg_testvars_s { 2702 | char *s1, *s2, *s3; 2703 | }; 2704 | 2705 | static struct ftg_testvars_s tv; 2706 | 2707 | static int ftg__test_setup(void) { 2708 | tv.s1 = (char*)FTG_MALLOC(sizeof(char*), FTG_STRLEN); 2709 | tv.s2 = (char*)FTG_MALLOC(sizeof(char*), FTG_STRLEN); 2710 | tv.s3 = (char*)FTG_MALLOC(sizeof(char*), FTG_STRLEN); 2711 | 2712 | strcpy(tv.s1, "catdoghamster"); 2713 | strcpy(tv.s2, "CATDOGhamster"); 2714 | strcpy(tv.s3, "fooballs"); 2715 | 2716 | return (tv.s1&&tv.s2&&tv.s3)?0:1; 2717 | } 2718 | 2719 | static int ftg__test_teardown(void) { 2720 | FTG_FREE(tv.s1); 2721 | FTG_FREE(tv.s2); 2722 | FTG_FREE(tv.s3); 2723 | 2724 | return 0; 2725 | } 2726 | 2727 | static int ftg__test_stricmp(void) 2728 | { 2729 | FTGT_ASSERT(ftg_stricmp(tv.s1,tv.s2)==0); 2730 | FTGT_ASSERT(ftg_stricmp(tv.s1,tv.s2)==0); 2731 | FTGT_ASSERT(ftg_stricmp(tv.s1,tv.s2)==0); 2732 | FTGT_ASSERT(ftg_stricmp(tv.s1,tv.s3)!=0); 2733 | FTGT_ASSERT(ftg_stricmp(tv.s1,tv.s1)==0); 2734 | 2735 | return ftgt_test_errorlevel(); 2736 | } 2737 | 2738 | static int ftg__test_stristr(void) 2739 | { 2740 | FTGT_ASSERT(ftg_stristr(tv.s1, "dog") == tv.s1+3); 2741 | FTGT_ASSERT(ftg_stristr(tv.s1, "DOG") == tv.s1+3); 2742 | FTGT_ASSERT(ftg_stristr(tv.s1, tv.s1) == tv.s1); 2743 | FTGT_ASSERT(ftg_stristr(tv.s1, "\0") == tv.s1+strlen(tv.s1)); 2744 | 2745 | return ftgt_test_errorlevel(); 2746 | } 2747 | 2748 | static int ftg__test_strncpy(void) 2749 | { 2750 | int trunc = 0; 2751 | char buf[FTG_STRLEN]; 2752 | trunc = ftg_strncpy(buf, tv.s1, FTG_STRLEN); 2753 | FTGT_ASSERT(strlen(buf)==strlen(tv.s1)); 2754 | FTGT_ASSERT(!trunc); 2755 | 2756 | trunc = ftg_strncpy(buf, tv.s1, 0); 2757 | FTGT_ASSERT(ftgt_test_errorlevel()); 2758 | 2759 | trunc = ftg_strncpy(buf, tv.s1, 7); 2760 | FTGT_ASSERT(strcmp(buf, "catdog")==0); 2761 | FTGT_ASSERT(trunc); 2762 | 2763 | return ftgt_test_errorlevel(); 2764 | } 2765 | 2766 | static int ftg__test_strcatall(void) 2767 | { 2768 | char *str = ftg_strcatall(3, "one", "two", "three"); 2769 | FTGT_ASSERT(strcmp(str, "onetwothree")==0); 2770 | FTG_FREE(str); 2771 | 2772 | str = ftg_strcatall(0); 2773 | FTGT_ASSERT(str[0] == '\0'); 2774 | FTG_FREE(str); 2775 | 2776 | return ftgt_test_errorlevel(); 2777 | } 2778 | 2779 | static int ftg__test_va(void) 2780 | { 2781 | char *buf = ftg_va("hello"); 2782 | FTGT_ASSERT(strcmp(buf, "hello")==0); 2783 | 2784 | buf = ftg_va("%d %d", 2, 3); 2785 | FTGT_ASSERT(strcmp(buf, "2 3")==0); 2786 | 2787 | buf = ftg_va("%d", (int)1048576); 2788 | FTGT_ASSERT(strcmp(buf, "1048576")==0); 2789 | 2790 | return ftgt_test_errorlevel(); 2791 | } 2792 | 2793 | static int ftg__test_correct_dirslash(void) 2794 | { 2795 | char dir[255]; 2796 | 2797 | strcpy(dir, "/dir/a"); 2798 | ftg_correct_dirslash(dir); 2799 | FTGT_ASSERT(dir[0] == FTG_DIRSLASH); 2800 | FTGT_ASSERT(dir[4] == FTG_DIRSLASH); 2801 | 2802 | strcpy(dir, "\\dir\\a"); 2803 | ftg_correct_dirslash(dir); 2804 | FTGT_ASSERT(dir[0] == FTG_DIRSLASH); 2805 | FTGT_ASSERT(dir[4] == FTG_DIRSLASH); 2806 | 2807 | return ftgt_test_errorlevel(); 2808 | } 2809 | 2810 | static int ftg__test_opendir(void) 2811 | { 2812 | #ifndef FTG_CORE_NO_STDIO 2813 | ftg_dirhandle_t dir; 2814 | char path_str[FTG_STRLEN_LONG]; 2815 | int dot_count = 0; 2816 | 2817 | ftg_opendir(&dir, ".", path_str, FTG_STRLEN_LONG); 2818 | while (*path_str) 2819 | { 2820 | if (strcmp(path_str, ".")==0) 2821 | dot_count++; 2822 | 2823 | if (strcmp(path_str, "..")==0) 2824 | dot_count++; 2825 | 2826 | /* finished with path_str, get the next one */ 2827 | ftg_readdir(&dir, path_str, FTG_STRLEN_LONG); 2828 | } 2829 | ftg_closedir(&dir); 2830 | 2831 | FTGT_ASSERT(dot_count==2); 2832 | #endif 2833 | return ftgt_test_errorlevel(); 2834 | } 2835 | 2836 | static int ftg__test_is_dir(void) 2837 | { 2838 | #ifndef FTG_CORE_NO_STDIO 2839 | 2840 | #ifdef FTG_POSIX_LIKE 2841 | FTGT_ASSERT(ftg_is_dir("/")); 2842 | #elif _WIN32 2843 | FTGT_ASSERT(ftg_is_dir("c:\\")); 2844 | #endif 2845 | 2846 | FTGT_ASSERT(!ftg_is_dir("this does not exist")); 2847 | 2848 | FTGT_ASSERT(ftg_is_dirslash('/')); 2849 | FTGT_ASSERT(ftg_is_dirslash('\\')); 2850 | FTGT_ASSERT(!ftg_is_dirslash(' ')); 2851 | #endif 2852 | 2853 | return ftgt_test_errorlevel(); 2854 | } 2855 | 2856 | static int ftg__test_get_filename_ext(void) 2857 | { 2858 | char *ext; 2859 | char str[1024]; 2860 | 2861 | /* regular */ 2862 | ext = ftg_get_filename_ext("hello.txt"); 2863 | FTGT_ASSERT(strcmp(ext, "txt") == 0); 2864 | 2865 | /* multiple extensions */ 2866 | ext = ftg_get_filename_ext("hello.wat.txt"); 2867 | FTGT_ASSERT(strcmp(ext, "txt") == 0); 2868 | 2869 | /* dot in directory part of path */ 2870 | strcpy(str, "/some.ext/hello.txt"); 2871 | ftg_correct_dirslash(str); 2872 | ext = ftg_get_filename_ext(str); 2873 | FTGT_ASSERT(strcmp(ext, "txt") == 0); 2874 | 2875 | strcpy(str, "/some.ext/hello"); 2876 | ftg_correct_dirslash(str); 2877 | ext = ftg_get_filename_ext(str); 2878 | FTGT_ASSERT(ext[0] == '\0'); 2879 | 2880 | strcpy(str, "/some.ext/"); 2881 | ftg_correct_dirslash(str); 2882 | ext = ftg_get_filename_ext(str); 2883 | FTGT_ASSERT(ext[0] == '\0'); 2884 | 2885 | /* no extension */ 2886 | ext = ftg_get_filename_ext("hello"); 2887 | FTGT_ASSERT(ext[0] == '\0'); 2888 | 2889 | /* empty */ 2890 | ext = ftg_get_filename_ext(""); 2891 | FTGT_ASSERT(ext[0] == '\0'); 2892 | 2893 | return ftgt_test_errorlevel(); 2894 | } 2895 | 2896 | static int ftg__test_get_filename_from_path(void) 2897 | { 2898 | int i; 2899 | const char *tests[][2] = 2900 | { 2901 | {"bar.txt", "bar.txt"}, 2902 | {"foo/bar.txt", "bar.txt"}, 2903 | {"/a/b/", ""}, 2904 | {NULL, NULL} 2905 | }; 2906 | 2907 | for (i = 0; tests[i][0] != NULL; i++) 2908 | { 2909 | const char *q = tests[i][0]; 2910 | const char *a = tests[i][1]; 2911 | char q_buf[FTG_STRLEN]; 2912 | bool trunc; 2913 | 2914 | trunc = ftg_strncpy(q_buf, q, FTG_STRLEN); 2915 | FTG_ASSERT(!trunc); 2916 | ftg_correct_dirslash(q_buf); 2917 | 2918 | FTGT_ASSERT(strcmp(ftg_get_filename_from_path(q_buf), a)==0); 2919 | } 2920 | 2921 | return ftgt_test_errorlevel(); 2922 | } 2923 | 2924 | static int ftg__test_file_rw(void) 2925 | { 2926 | #ifndef FTG_CORE_NO_STDIO 2927 | const char tmp_file[] = "ftg_core_tmp_file.txt"; 2928 | const char test_str[] = "hello\nworld!"; 2929 | unsigned char *read_str; 2930 | ftg_off_t read_len; 2931 | bool result; 2932 | 2933 | result = ftg_file_write(tmp_file, (uint8_t*)test_str, strlen(test_str)+1); 2934 | FTGT_ASSERT(result); 2935 | 2936 | read_str = ftg_file_read(tmp_file, true, &read_len); 2937 | FTGT_ASSERT(memcmp(read_str, test_str, strlen(test_str)+1)==0); 2938 | 2939 | FTG_FREE(read_str); 2940 | // todo: rm temp file 2941 | #endif 2942 | 2943 | return ftgt_test_errorlevel(); 2944 | } 2945 | 2946 | static int ftg__test_ia(void) 2947 | { 2948 | struct ftg_index_array_s ia = FTG_IA_INIT_ZERO; 2949 | size_t i; 2950 | FTGT_ASSERT(!ftg_ia_is_init(&ia)); 2951 | 2952 | for (i = 0; i < FTG_IA_INITIAL_RECORD_COUNT * 2; ++i) 2953 | { 2954 | bool success = ftg_ia_append(&ia, i*2); 2955 | FTGT_ASSERT(success); 2956 | } 2957 | 2958 | FTGT_ASSERT(ia.records > FTG_IA_INITIAL_RECORD_COUNT); 2959 | 2960 | for (i = 0; i < ia.count; i++) 2961 | { 2962 | FTGT_ASSERT(ia.indices[i] == i*2); 2963 | } 2964 | 2965 | ftg_ia_free(&ia); 2966 | 2967 | return ftgt_test_errorlevel(); 2968 | } 2969 | 2970 | static int ftg__test_dircreate(void) 2971 | { 2972 | #ifndef FTG_CORE_NO_STDIO 2973 | bool success; 2974 | if (ftg_is_dir("testdir")) 2975 | { 2976 | printf("removing testdir which shouldn't exist"); 2977 | success = ftg_rmdir("testdir"); 2978 | FTG_ASSERT(success); 2979 | } 2980 | 2981 | success = ftg_mkdir("testdir"); 2982 | FTGT_ASSERT(success); 2983 | FTGT_ASSERT(ftg_is_dir("testdir")); 2984 | 2985 | // this assumes FTG_DIRECTORY_MODE allows for the deletion 2986 | // of the created directory. 2987 | success = ftg_rmdir("testdir"); 2988 | FTGT_ASSERT(success); 2989 | FTGT_ASSERT(!ftg_is_dir("testdir")); 2990 | 2991 | ftg_mkalldirs("one/two/three"); 2992 | FTGT_ASSERT(ftg_is_dir("one/two/three")); 2993 | ftg_rmalldirs("one"); 2994 | FTGT_ASSERT(!ftg_is_dir("one")); 2995 | #endif 2996 | 2997 | return ftgt_test_errorlevel(); 2998 | } 2999 | 3000 | static int ftg__test_push_path(void) 3001 | { 3002 | char buffer[64]; 3003 | bool trunc; 3004 | int i; 3005 | 3006 | // [0] + [1] must equal [2] to pass the test 3007 | const char * tests[][3] = 3008 | { 3009 | { "/a", "b", "/a/b" }, 3010 | { "/a", "/b", "/a/b" }, 3011 | { "/a", "b/", "/a/b" }, 3012 | { "/a", "/b/", "/a/b" }, 3013 | { "/a/", "b", "/a/b" }, 3014 | { "/a/", "/b", "/a/b" }, 3015 | { "/a/", "b/", "/a/b" }, 3016 | { "/a/", "/b/", "/a/b" }, 3017 | 3018 | { "a", "b", "a/b" }, 3019 | { "a", "/b", "a/b" }, 3020 | { "a", "b/", "a/b" }, 3021 | { "a", "/b/", "a/b" }, 3022 | { "a/", "b", "a/b" }, 3023 | { "a/", "/b", "a/b" }, 3024 | { "a/", "b/", "a/b" }, 3025 | { "a/", "/b/", "a/b" }, 3026 | 3027 | { "", "b", "b" }, 3028 | { "", "/b", "/b" }, 3029 | { "", "b/", "b" }, 3030 | { "", "/b/", "/b" }, 3031 | { "/", "b", "/b" }, 3032 | { "/", "/b", "/b" }, 3033 | { "/", "b/", "/b" }, 3034 | { "/", "/b/", "/b" }, 3035 | 3036 | { "a", "", "a" }, 3037 | { "a/", "", "a" }, 3038 | { "/a", "", "/a" }, 3039 | { "/a/", "", "/a" }, 3040 | { "a", "/", "a" }, 3041 | { "a/", "/", "a" }, 3042 | { "/a", "/", "/a" }, 3043 | { "/a/", "/", "/a" }, 3044 | 3045 | { "", "", "" }, 3046 | { "/", "", "/" }, 3047 | { "", "/", "/" }, 3048 | { "/", "/", "/" }, 3049 | 3050 | { "//a", "", "/a" }, 3051 | { "a//", "", "a" }, 3052 | { "", "//b", "/b" }, 3053 | { "", "b//", "b" }, 3054 | { "//a//", "//b//","/a/b" }, 3055 | 3056 | { NULL, NULL, NULL } 3057 | }; 3058 | 3059 | for(i = 0; tests[i][0] != NULL; i++) 3060 | { 3061 | const char *a = tests[i][0]; 3062 | const char *b = tests[i][1]; 3063 | const char *c = tests[i][2]; 3064 | char c_buf[64]; 3065 | 3066 | trunc = ftg_strncpy(buffer, a, 8); 3067 | FTG_ASSERT(!trunc); 3068 | ftg_push_path(buffer, b, 8); 3069 | ftg_correct_dirslash(buffer); 3070 | 3071 | trunc = ftg_strncpy(c_buf, c, 64); 3072 | FTG_ASSERT(!trunc); 3073 | ftg_correct_dirslash(c_buf); 3074 | 3075 | FTGT_ASSERT(strcmp(buffer, c_buf) == 0); 3076 | } 3077 | 3078 | // test truncation cases 3079 | trunc = ftg_strncpy(buffer, "aaaaaaa", 8); 3080 | FTG_ASSERT(!trunc); 3081 | trunc = ftg_push_path(buffer, "a", 8); 3082 | FTGT_ASSERT(trunc); 3083 | FTGT_ASSERT(strcmp(buffer, "aaaaaaa") == 0); 3084 | 3085 | trunc = ftg_strncpy(buffer, "aaaaaa", 8); 3086 | FTG_ASSERT(!trunc); 3087 | trunc = ftg_push_path(buffer, "a", 8); 3088 | ftg_correct_dirslash(buffer); 3089 | FTGT_ASSERT(trunc); 3090 | FTGT_ASSERT(strcmp(buffer, ftg_va("aaaaaa%c", FTG_DIRSLASH))==0); 3091 | 3092 | trunc = ftg_strncpy(buffer, "aaaaaa/", 8); 3093 | FTG_ASSERT(!trunc); 3094 | trunc = ftg_push_path(buffer, "a", 8); 3095 | ftg_correct_dirslash(buffer); 3096 | FTGT_ASSERT(trunc); 3097 | FTGT_ASSERT(strcmp(buffer, ftg_va("aaaaaa%c", FTG_DIRSLASH))==0); 3098 | 3099 | trunc = ftg_strncpy(buffer, "a/", 8); 3100 | FTG_ASSERT(!trunc); 3101 | trunc = ftg_push_path(buffer, "aaaaaa", 8); 3102 | ftg_correct_dirslash(buffer); 3103 | FTGT_ASSERT(trunc); 3104 | FTGT_ASSERT(strcmp(buffer, ftg_va("a%caaaaa", FTG_DIRSLASH))==0); 3105 | 3106 | return ftgt_test_errorlevel(); 3107 | } 3108 | 3109 | 3110 | static int ftg__test_pop_path(void) 3111 | { 3112 | const char * paths[][2] = 3113 | { 3114 | { "/dir/file.ext", "/dir" }, 3115 | { "/dir/file", "/dir" }, 3116 | { "/dir/", "/" }, 3117 | { "/dir", "/" }, 3118 | { "/", "/" }, 3119 | { "dir", "" }, 3120 | { "", "" }, 3121 | { NULL, NULL } 3122 | }; 3123 | 3124 | int i; 3125 | for (i = 0; paths[i][0] != NULL; i++) 3126 | { 3127 | const char *a = paths[i][0]; 3128 | const char *b = paths[i][1]; 3129 | char path[FTG_STRLEN]; 3130 | bool trunc; 3131 | 3132 | trunc = ftg_strncpy(path, a, FTG_STRLEN); 3133 | FTG_ASSERT(!trunc); 3134 | ftg_pop_path(path); 3135 | FTGT_ASSERT(strcmp(path, b) == 0); 3136 | } 3137 | 3138 | return ftgt_test_errorlevel(); 3139 | } 3140 | 3141 | static int ftg__test_strsplit(void) 3142 | { 3143 | const char *p; 3144 | size_t i; 3145 | size_t len; 3146 | 3147 | const char PATH[] = "ab:cd:ef"; 3148 | const char WILL_REACH_END[] = "abc"; 3149 | const char EMPTY[] = ""; 3150 | 3151 | FTGT_ASSERT(ftg_strsplit(PATH, ':', 0, &len) == &PATH[0] && len == 2); 3152 | FTGT_ASSERT(ftg_strsplit(PATH, ':', 1, &len) == &PATH[3] && len == 2); 3153 | FTGT_ASSERT(ftg_strsplit(PATH, ':', 2, &len) == &PATH[6] && len == 2); 3154 | FTGT_ASSERT(ftg_strsplit(PATH, ':', 3, &len) == NULL && len == 0); 3155 | 3156 | 3157 | FTGT_ASSERT(ftg_strsplit(WILL_REACH_END, ':', 1, &len) == NULL && len == 0); 3158 | 3159 | FTGT_ASSERT(ftg_strsplit(EMPTY, ':', 0, &len) == &EMPTY[0] && len == 0); 3160 | FTGT_ASSERT(ftg_strsplit(EMPTY, ':', 1, &len) == NULL && len == 0); 3161 | 3162 | // iteration usage example 3163 | for (p = PATH, i = 0; (p = ftg_strsplit(PATH, ':', i, &len)) != NULL; i++) { 3164 | 3165 | switch(i) { 3166 | case 0: 3167 | FTGT_ASSERT(p == &PATH[0] && len == 2); 3168 | break; 3169 | 3170 | case 1: 3171 | FTGT_ASSERT(p == &PATH[3] && len == 2); 3172 | break; 3173 | 3174 | case 2: 3175 | FTGT_ASSERT(p == &PATH[6] && len == 2); 3176 | break; 3177 | 3178 | default: 3179 | FTGT_ASSERT(0); 3180 | } 3181 | 3182 | } 3183 | 3184 | return ftgt_test_errorlevel(); 3185 | } 3186 | 3187 | static int ftg__test_arena(void) 3188 | { 3189 | ftg_arena_t* arena = ftg_arena_new(); 3190 | void* ptr[5]; 3191 | size_t i; 3192 | 3193 | ptr[0] = ftg_arena_alloc(&arena, 1); 3194 | ptr[1] = ftg_arena_alloc(&arena, 1); 3195 | ptr[2] = ftg_arena_alloc(&arena, 1); 3196 | ptr[3] = ftg_arena_alloc(&arena, FTG_ARENA_BLOCK_SIZE + 1); 3197 | ptr[4] = ftg_arena_alloc(&arena, 1); 3198 | 3199 | for (i = 1; i < 5; i++) { 3200 | FTGT_ASSERT(ptr[i-1] < ptr[i]); 3201 | } 3202 | 3203 | ftg_arena_free(arena); 3204 | 3205 | return ftgt_test_errorlevel(); 3206 | } 3207 | 3208 | FTGDEF 3209 | void ftg_decl_suite(void) 3210 | { 3211 | ftgt_suite_s *suite = ftgt_create_suite(NULL, "ftg_core", ftg__test_setup, ftg__test_teardown); 3212 | FTGT_ADD_TEST(suite, ftg__test_stricmp); 3213 | FTGT_ADD_TEST(suite, ftg__test_stristr); 3214 | FTGT_ADD_TEST(suite, ftg__test_strncpy); 3215 | FTGT_ADD_TEST(suite, ftg__test_strcatall); 3216 | FTGT_ADD_TEST(suite, ftg__test_va); 3217 | FTGT_ADD_TEST(suite, ftg__test_correct_dirslash); 3218 | FTGT_ADD_TEST(suite, ftg__test_opendir); 3219 | FTGT_ADD_TEST(suite, ftg__test_is_dir); 3220 | FTGT_ADD_TEST(suite, ftg__test_get_filename_ext); 3221 | FTGT_ADD_TEST(suite, ftg__test_get_filename_from_path); 3222 | FTGT_ADD_TEST(suite, ftg__test_file_rw); 3223 | FTGT_ADD_TEST(suite, ftg__test_ia); 3224 | FTGT_ADD_TEST(suite, ftg__test_dircreate); 3225 | FTGT_ADD_TEST(suite, ftg__test_push_path); 3226 | FTGT_ADD_TEST(suite, ftg__test_pop_path); 3227 | FTGT_ADD_TEST(suite, ftg__test_strsplit); 3228 | FTGT_ADD_TEST(suite, ftg__test_arena); 3229 | } 3230 | 3231 | 3232 | #ifdef FTG_JUST_TEST 3233 | int main(void) 3234 | { 3235 | ftg_decl_suite(); 3236 | return ftgt_run_all_tests(NULL); 3237 | } 3238 | #endif /* FTG_JUST_TEST */ 3239 | #endif /* FTGT_TESTS_ENABLED */ 3240 | #endif /* FTG_IMPLEMENT_CORE */ 3241 | #endif /* eof */ 3242 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # ftg toolbox # 2 | 3 | Single C file libraries for C or C++, written by Frogtoss Games, and made public. 4 | 5 | 6 | ## ftg_bitbuffer ## 7 | 8 | [ftg_bitbuffer.h](https://github.com/frogtoss/ftg_toolbox_public/blob/main/ftg_bitbuffer.h) 9 | 10 | Tightly pack values by bits into a stream of bytes for reading / writing. 11 | 12 | For example, a 1-bit bool and a 32-bit integer are packed into 33 13 | bits. 14 | 15 | Bitbuffers are intended for small amounts of data, such as a few 16 | hundred network packets where size is important enough to remove 17 | padding bits, and the cpu overhead of packing/unpacking intermixed 18 | types is not a huge cost. 19 | 20 | - Compiles C99 warnings-free on clang and visual c++ 21 | 22 | - Pack integers with arbitrary numbers of bits 23 | 24 | - Supports quantized floating point packing 25 | 26 | - Possible to avoid all heap allocations and copies on read 27 | 28 | 29 | -------------------------------------------------------------------------------- /test/aireview-prompt.txt: -------------------------------------------------------------------------------- 1 | Please review these files for typical C-style coding errors: 2 | 3 | - off by one 4 | - buffer overflows 5 | - use before initialization 6 | - memory stomps 7 | 8 | Make code fixes where applicable. For every code fix, provide in-line 9 | comments near code changes that: a) explain in depth what the bug was, 10 | b) how to reproduce the bug, and c) your confidence level that there 11 | was a bug in the first place. 12 | --------------------------------------------------------------------------------