#if defined(DQN_IMPLEMENTATION) #define STB_SPRINTF_IMPLEMENTATION #endif // ------------------------------------------------------------------------------------------------- // // NOTE: stb_sprintf // // ------------------------------------------------------------------------------------------------- // stb_sprintf - v1.05 - public domain snprintf() implementation // originally by Jeff Roberts / RAD Game Tools, 2015/10/20 // http://github.com/nothings/stb // // allowed types: sc uidBboXx p AaGgEef n // lengths : h ll j z t I64 I32 I // // Contributors: // Fabian "ryg" Giesen (reformatting) // // Contributors (bugfixes): // github:d26435 // github:trex78 // Jari Komppa (SI suffixes) // Rohit Nirmal // Marcin Wojdyr // Leonard Ritter // // LICENSE: // // See end of file for license information. #ifndef STB_SPRINTF_H_INCLUDE #define STB_SPRINTF_H_INCLUDE /* Single file sprintf replacement. Originally written by Jeff Roberts at RAD Game Tools - 2015/10/20. Hereby placed in public domain. This is a full sprintf replacement that supports everything that the C runtime sprintfs support, including float/double, 64-bit integers, hex floats, field parameters (%*.*d stuff), length reads backs, etc. Why would you need this if sprintf already exists? Well, first off, it's *much* faster (see below). It's also much smaller than the CRT versions code-space-wise. We've also added some simple improvements that are super handy (commas in thousands, callbacks at buffer full, for example). Finally, the format strings for MSVC and GCC differ for 64-bit integers (among other small things), so this lets you use the same format strings in cross platform code. It uses the standard single file trick of being both the header file and the source itself. If you just include it normally, you just get the header file function definitions. To get the code, you include it from a C or C++ file and define STB_SPRINTF_IMPLEMENTATION first. It only uses va_args macros from the C runtime to do it's work. It does cast doubles to S64s and shifts and divides U64s, which does drag in CRT code on most platforms. It compiles to roughly 8K with float support, and 4K without. As a comparison, when using MSVC static libs, calling sprintf drags in 16K. API: ==== int stbsp_sprintf( char * buf, char const * fmt, ... ) int stbsp_snprintf( char * buf, int count, char const * fmt, ... ) Convert an arg list into a buffer. stbsp_snprintf always returns a zero-terminated string (unlike regular snprintf). int stbsp_vsprintf( char * buf, char const * fmt, va_list va ) int stbsp_vsnprintf( char * buf, int count, char const * fmt, va_list va ) Convert a va_list arg list into a buffer. stbsp_vsnprintf always returns a zero-terminated string (unlike regular snprintf). int stbsp_vsprintfcb( STBSP_SPRINTFCB * callback, void * user, char * buf, char const * fmt, va_list va ) typedef char * STBSP_SPRINTFCB( char const * buf, void * user, int len ); Convert into a buffer, calling back every STB_SPRINTF_MIN chars. Your callback can then copy the chars out, print them or whatever. This function is actually the workhorse for everything else. The buffer you pass in must hold at least STB_SPRINTF_MIN characters. // you return the next buffer to use or 0 to stop converting void stbsp_set_separators( char comma, char period ) Set the comma and period characters to use. FLOATS/DOUBLES: =============== This code uses a internal float->ascii conversion method that uses doubles with error correction (double-doubles, for ~105 bits of precision). This conversion is round-trip perfect - that is, an atof of the values output here will give you the bit-exact double back. One difference is that our insignificant digits will be different than with MSVC or GCC (but they don't match each other either). We also don't attempt to find the minimum length matching float (pre-MSVC15 doesn't either). If you don't need float or doubles at all, define STB_SPRINTF_NOFLOAT and you'll save 4K of code space. 64-BIT INTS: ============ This library also supports 64-bit integers and you can use MSVC style or GCC style indicators (%I64d or %lld). It supports the C99 specifiers for size_t and ptr_diff_t (%jd %zd) as well. EXTRAS: ======= Like some GCCs, for integers and floats, you can use a ' (single quote) specifier and commas will be inserted on the thousands: "%'d" on 12345 would print 12,345. For integers and floats, you can use a "$" specifier and the number will be converted to float and then divided to get kilo, mega, giga or tera and then printed, so "%$d" 1000 is "1.0 k", "%$.2d" 2536000 is "2.53 M", etc. For byte values, use two $:s, like "%$$d" to turn 2536000 to "2.42 Mi". If you prefer JEDEC suffixes to SI ones, use three $:s: "%$$$d" -> "2.42 M". To remove the space between the number and the suffix, add "_" specifier: "%_$d" -> "2.53M". In addition to octal and hexadecimal conversions, you can print integers in binary: "%b" for 256 would print 100. PERFORMANCE vs MSVC 2008 32-/64-bit (GCC is even slower than MSVC): =================================================================== "%d" across all 32-bit ints (4.8x/4.0x faster than 32-/64-bit MSVC) "%24d" across all 32-bit ints (4.5x/4.2x faster) "%x" across all 32-bit ints (4.5x/3.8x faster) "%08x" across all 32-bit ints (4.3x/3.8x faster) "%f" across e-10 to e+10 floats (7.3x/6.0x faster) "%e" across e-10 to e+10 floats (8.1x/6.0x faster) "%g" across e-10 to e+10 floats (10.0x/7.1x faster) "%f" for values near e-300 (7.9x/6.5x faster) "%f" for values near e+300 (10.0x/9.1x faster) "%e" for values near e-300 (10.1x/7.0x faster) "%e" for values near e+300 (9.2x/6.0x faster) "%.320f" for values near e-300 (12.6x/11.2x faster) "%a" for random values (8.6x/4.3x faster) "%I64d" for 64-bits with 32-bit values (4.8x/3.4x faster) "%I64d" for 64-bits > 32-bit values (4.9x/5.5x faster) "%s%s%s" for 64 char strings (7.1x/7.3x faster) "...512 char string..." ( 35.0x/32.5x faster!) */ #if defined(__has_feature) #if __has_feature(address_sanitizer) #define STBI__ASAN __attribute__((no_sanitize("address"))) #endif #endif #ifndef STBI__ASAN #define STBI__ASAN #endif #ifdef STB_SPRINTF_STATIC #define STBSP__PUBLICDEC static #define STBSP__PUBLICDEF static STBI__ASAN #else #ifdef __cplusplus #define STBSP__PUBLICDEC extern "C" #define STBSP__PUBLICDEF extern "C" STBI__ASAN #else #define STBSP__PUBLICDEC extern #define STBSP__PUBLICDEF STBI__ASAN #endif #endif #include // for va_list() #ifndef STB_SPRINTF_MIN #define STB_SPRINTF_MIN 512 // how many characters per callback #endif typedef char *STBSP_SPRINTFCB(char *buf, void *user, int len); #ifndef STB_SPRINTF_DECORATE #define STB_SPRINTF_DECORATE(name) stbsp_##name // define this before including if you want to change the names #endif STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(vsprintf)(char *buf, char const *fmt, va_list va); STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(vsnprintf)(char *buf, int count, char const *fmt, va_list va); STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(sprintf)(char *buf, char const *fmt, ...); STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(snprintf)(char *buf, int count, char const *fmt, ...); STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(vsprintfcb)(STBSP_SPRINTFCB *callback, void *user, char *buf, char const *fmt, va_list va); STBSP__PUBLICDEF void STB_SPRINTF_DECORATE(set_separators)(char comma, char period); #endif // STB_SPRINTF_H_INCLUDE #ifndef DQN_H #define DQN_H // ------------------------------------------------------------------------------------------------- // // NOTE: Typedefs, Macros, Utils // // ------------------------------------------------------------------------------------------------- #define DQN_ABS(val) (((val) < 0) ? (-(val)) : (val)) #define DQN_SQUARED(val) ((val) * (val)) #define DQN_MIN(a, b) ((a < b) ? (a) : (b)) #define DQN_MAX(a, b) ((a > b) ? (a) : (b)) #define DQN_SWAP(a, b) \ do \ { \ auto tmp = a; \ a = b; \ b = tmp; \ } while (0) #define DQN_LEN_AND_STR(string) Dqn_CharCount(str), string #define DQN_STR_AND_LEN(string) string, Dqn_CharCount(string) #define DQN_STR_AND_LEN_I(string) string, (int)Dqn_CharCount(string) #define DQN_FOR_EACH(i, limit) for (isize (i) = 0; (i) < (isize)(limit); ++(i)) #define DQN_FOR_EACH_REVERSE(i, limit) for (isize (i) = (isize)(limit-1); (i) >= 0; --(i)) #define DQN_BYTES(val) (val) #define DQN_KILOBYTES(val) (1024ULL * DQN_BYTES(val)) #define DQN_MEGABYTES(val) (1024ULL * DQN_KILOBYTES(val)) #define DQN_GIGABYTES(val) (1024ULL * DQN_MEGABYTES(val)) #define DQN_INVALID_CODE_PATH 0 #define DQN_ASSERT(expr) DQN_ASSERT_MSG(expr, "") #define DQN_ASSERT_MSG(expr, fmt, ...) \ if (!(expr)) \ { \ DQN_LOG_E("Assert: [" #expr "] " fmt, ##__VA_ARGS__); \ __debugbreak(); \ } #define DQN_SECONDS_TO_MS(val) ((val) * 1000.0f) #define DQN_MATH_PI 3.14159265359f #define DQN_DEGREE_TO_RADIAN(val) (val) * (DQN_MATH_PI / 180.0f) #define FILE_SCOPE static #define LOCAL_PERSIST static #include #include using usize = size_t; using isize = ptrdiff_t; using f64 = double; using f32 = float; using i64 = int64_t; using i32 = int32_t; using i16 = int16_t; using i8 = int8_t; using uchar = unsigned char; using uint = unsigned int; using u64 = uint64_t; using u32 = uint32_t; using u16 = uint16_t; using u8 = uint8_t; using b32 = int32_t; const i32 I32_MAX = INT32_MAX; const u32 U32_MAX = UINT32_MAX; const f32 F32_MAX = FLT_MAX; const isize ISIZE_MAX = PTRDIFF_MAX; const usize USIZE_MAX = SIZE_MAX; template constexpr usize Dqn_ArrayCount (T const (&)[N]) { return N; } template constexpr isize Dqn_ArrayCountI(T const (&)[N]) { return N; } template constexpr usize Dqn_CharCount (char const (&)[N]) { return N - 1; } template constexpr isize Dqn_CharCountI (char const (&)[N]) { return N - 1; } template struct DqnDefer { Procedure proc; DqnDefer(Procedure p) : proc(p) {} ~DqnDefer() { proc(); } }; struct DqnDeferHelper { template DqnDefer operator+(Lambda lambda) { return DqnDefer(lambda); }; }; #define DQN_TOKEN_COMBINE(x, y) x ## y #define DQN_TOKEN_COMBINE2(x, y) DQN_TOKEN_COMBINE(x, y) #define DQN_UNIQUE_NAME(prefix) DQN_TOKEN_COMBINE2(prefix, __COUNTER__) #define DQN_DEFER const auto DQN_UNIQUE_NAME(defer_lambda_) = DqnDeferHelper() + [&]() enum struct Dqn_LogType { Debug, Error, Warning, Info, Memory, }; constexpr inline char const *Dqn_LogTypeTag(Dqn_LogType type) { if (type == Dqn_LogType::Debug) return " DBG"; else if (type == Dqn_LogType::Error) return " ERR"; else if (type == Dqn_LogType::Warning) return "WARN"; else if (type == Dqn_LogType::Info) return "INFO"; else if (type == Dqn_LogType::Memory) return " MEM"; return " XXX"; } // NOTE: Set the callback to get called whenever a log message has been printed #define DQN_LOG_CALLBACK(name) void name(Dqn_LogType type, char const *file, usize file_len, char const *func, usize func_len, usize line, char const *log_str) typedef DQN_LOG_CALLBACK(Dqn_LogCallback); Dqn_LogCallback *Dqn_log_callback; #define DQN_LOG_E(fmt, ...) Dqn_Log(Dqn_LogType::Error, DQN_STR_AND_LEN(__FILE__), DQN_STR_AND_LEN(__func__), __LINE__, fmt, ## __VA_ARGS__) #define DQN_LOG_D(fmt, ...) Dqn_Log(Dqn_LogType::Debug, DQN_STR_AND_LEN(__FILE__), DQN_STR_AND_LEN(__func__), __LINE__, fmt, ## __VA_ARGS__) #define DQN_LOG_W(fmt, ...) Dqn_Log(Dqn_LogType::Warning, DQN_STR_AND_LEN(__FILE__), DQN_STR_AND_LEN(__func__), __LINE__, fmt, ## __VA_ARGS__) #define DQN_LOG_I(fmt, ...) Dqn_Log(Dqn_LogType::Info, DQN_STR_AND_LEN(__FILE__), DQN_STR_AND_LEN(__func__), __LINE__, fmt, ## __VA_ARGS__) #define DQN_LOG_M(fmt, ...) Dqn_Log(Dqn_LogType::Info, DQN_STR_AND_LEN(__FILE__), DQN_STR_AND_LEN(__func__), __LINE__, fmt, ## __VA_ARGS__) #define DQN_LOG(log_type, fmt, ...) Dqn_Log(log_type, DQN_STR_AND_LEN(__FILE__), DQN_STR_AND_LEN(__func__), __LINE__, fmt, ## __VA_ARGS__) // ------------------------------------------------------------------------------------------------- // // NOTE: Math // // ------------------------------------------------------------------------------------------------- union Dqn_V2I { struct { i32 x, y; }; struct { i32 w, h; }; struct { i32 min, max; }; i32 e[2]; constexpr Dqn_V2I() = default; constexpr Dqn_V2I(f32 x_, f32 y_): x((i32)x_), y((i32)y_) {} constexpr Dqn_V2I(i32 x_, i32 y_): x(x_), y(y_) {} constexpr Dqn_V2I(i32 xy): x(xy), y(xy) {} constexpr bool operator!=(Dqn_V2I other) const { return !(*this == other); } constexpr bool operator==(Dqn_V2I other) const { return (x == other.x) && (y == other.y); } constexpr bool operator>=(Dqn_V2I other) const { return (x >= other.x) && (y >= other.y); } constexpr bool operator<=(Dqn_V2I other) const { return (x <= other.x) && (y <= other.y); } constexpr bool operator< (Dqn_V2I other) const { return (x < other.x) && (y < other.y); } constexpr bool operator> (Dqn_V2I other) const { return (x > other.x) && (y > other.y); } constexpr Dqn_V2I operator- (Dqn_V2I other) const { Dqn_V2I result(x - other.x, y - other.y); return result; } constexpr Dqn_V2I operator+ (Dqn_V2I other) const { Dqn_V2I result(x + other.x, y + other.y); return result; } constexpr Dqn_V2I operator* (Dqn_V2I other) const { Dqn_V2I result(x * other.x, y * other.y); return result; } constexpr Dqn_V2I operator* (f32 other) const { Dqn_V2I result(x * other, y * other); return result; } constexpr Dqn_V2I operator* (i32 other) const { Dqn_V2I result(x * other, y * other); return result; } constexpr Dqn_V2I operator/ (Dqn_V2I other) const { Dqn_V2I result(x / other.x, y / other.y); return result; } constexpr Dqn_V2I operator/ (f32 other) const { Dqn_V2I result(x / other, y / other); return result; } constexpr Dqn_V2I operator/ (i32 other) const { Dqn_V2I result(x / other, y / other); return result; } constexpr Dqn_V2I &operator*=(Dqn_V2I other) { *this = *this * other; return *this; } constexpr Dqn_V2I &operator*=(f32 other) { *this = *this * other; return *this; } constexpr Dqn_V2I &operator*=(i32 other) { *this = *this * other; return *this; } constexpr Dqn_V2I &operator-=(Dqn_V2I other) { *this = *this - other; return *this; } constexpr Dqn_V2I &operator+=(Dqn_V2I other) { *this = *this + other; return *this; } }; union Dqn_V2 { struct { f32 x, y; }; struct { f32 w, h; }; struct { f32 min, max; }; f32 e[2]; constexpr Dqn_V2() = default; constexpr Dqn_V2(f32 a) : x(a), y(a) {} constexpr Dqn_V2(i32 a) : x((f32)a), y((f32)a) {} constexpr Dqn_V2(f32 x_, f32 y_): x(x_), y(y_) {} constexpr Dqn_V2(i32 x_, i32 y_): x((f32)x_), y((f32)y_) {} constexpr Dqn_V2(Dqn_V2I a) : x((f32)a.x), y((f32)a.y) {} constexpr bool operator!=(Dqn_V2 other) const { return !(*this == other); } constexpr bool operator==(Dqn_V2 other) const { return (x == other.x) && (y == other.y); } constexpr bool operator>=(Dqn_V2 other) const { return (x >= other.x) && (y >= other.y); } constexpr bool operator<=(Dqn_V2 other) const { return (x <= other.x) && (y <= other.y); } constexpr bool operator< (Dqn_V2 other) const { return (x < other.x) && (y < other.y); } constexpr bool operator> (Dqn_V2 other) const { return (x > other.x) && (y > other.y); } constexpr Dqn_V2 operator- (Dqn_V2 other) const { Dqn_V2 result(x - other.x, y - other.y); return result; } constexpr Dqn_V2 operator+ (Dqn_V2 other) const { Dqn_V2 result(x + other.x, y + other.y); return result; } constexpr Dqn_V2 operator* (Dqn_V2 other) const { Dqn_V2 result(x * other.x, y * other.y); return result; } constexpr Dqn_V2 operator* (f32 other) const { Dqn_V2 result(x * other, y * other); return result; } constexpr Dqn_V2 operator* (i32 other) const { Dqn_V2 result(x * other, y * other); return result; } constexpr Dqn_V2 operator/ (Dqn_V2 other) const { Dqn_V2 result(x / other.x, y / other.y); return result; } constexpr Dqn_V2 operator/ (f32 other) const { Dqn_V2 result(x / other, y / other); return result; } constexpr Dqn_V2 operator/ (i32 other) const { Dqn_V2 result(x / other, y / other); return result; } constexpr Dqn_V2 &operator*=(Dqn_V2 other) { *this = *this * other; return *this; } constexpr Dqn_V2 &operator*=(f32 other) { *this = *this * other; return *this; } constexpr Dqn_V2 &operator*=(i32 other) { *this = *this * other; return *this; } constexpr Dqn_V2 &operator/=(Dqn_V2 other) { *this = *this / other; return *this; } constexpr Dqn_V2 &operator/=(f32 other) { *this = *this / other; return *this; } constexpr Dqn_V2 &operator/=(i32 other) { *this = *this / other; return *this; } constexpr Dqn_V2 &operator-=(Dqn_V2 other) { *this = *this - other; return *this; } constexpr Dqn_V2 &operator+=(Dqn_V2 other) { *this = *this + other; return *this; } }; union Dqn_V3 { struct { f32 x, y, z; }; struct { f32 r, g, b; }; Dqn_V2 xy; f32 e[3]; constexpr Dqn_V3() = default; constexpr Dqn_V3(f32 a) : x(a), y(a), z(a) {} constexpr Dqn_V3(i32 a) : x((f32)a), y((f32)a), z((f32)a) {} constexpr Dqn_V3(f32 x_, f32 y_, f32 z_): x(x_), y(y_), z(z_) {} constexpr Dqn_V3(i32 x_, i32 y_, f32 z_): x((f32)x_), y((f32)y_), z((f32)z_) {} constexpr Dqn_V3(Dqn_V2 xy, f32 z_) : x(xy.x), y(xy.y), z(z_) {} constexpr bool operator!= (Dqn_V3 other) const { return !(*this == other); } constexpr bool operator== (Dqn_V3 other) const { return (x == other.x) && (y == other.y) && (z == other.z); } constexpr bool operator>= (Dqn_V3 other) const { return (x >= other.x) && (y >= other.y) && (z >= other.z); } constexpr bool operator<= (Dqn_V3 other) const { return (x <= other.x) && (y <= other.y) && (z <= other.z); } constexpr bool operator< (Dqn_V3 other) const { return (x < other.x) && (y < other.y) && (z < other.z); } constexpr bool operator> (Dqn_V3 other) const { return (x > other.x) && (y > other.y) && (z > other.z); } constexpr Dqn_V3 operator- (Dqn_V3 other) const { Dqn_V3 result(x - other.x, y - other.y, z - other.z); return result; } constexpr Dqn_V3 operator+ (Dqn_V3 other) const { Dqn_V3 result(x + other.x, y + other.y, z + other.z); return result; } constexpr Dqn_V3 operator* (Dqn_V3 other) const { Dqn_V3 result(x * other.x, y * other.y, z * other.z); return result; } constexpr Dqn_V3 operator* (f32 other) const { Dqn_V3 result(x * other, y * other, z * other); return result; } constexpr Dqn_V3 operator* (i32 other) const { Dqn_V3 result(x * other, y * other, z * other); return result; } constexpr Dqn_V3 operator/ (Dqn_V3 other) const { Dqn_V3 result(x / other.x, y / other.y, z / other.z); return result; } constexpr Dqn_V3 operator/ (f32 other) const { Dqn_V3 result(x / other, y / other, z / other); return result; } constexpr Dqn_V3 operator/ (i32 other) const { Dqn_V3 result(x / other, y / other, z / other); return result; } constexpr Dqn_V3 &operator*=(Dqn_V3 other) { *this = *this * other; return *this; } constexpr Dqn_V3 &operator*=(f32 other) { *this = *this * other; return *this; } constexpr Dqn_V3 &operator*=(i32 other) { *this = *this * other; return *this; } constexpr Dqn_V3 &operator/=(Dqn_V3 other) { *this = *this / other; return *this; } constexpr Dqn_V3 &operator/=(f32 other) { *this = *this / other; return *this; } constexpr Dqn_V3 &operator/=(i32 other) { *this = *this / other; return *this; } constexpr Dqn_V3 &operator-=(Dqn_V3 other) { *this = *this - other; return *this; } constexpr Dqn_V3 &operator+=(Dqn_V3 other) { *this = *this + other; return *this; } }; union Dqn_V4 { struct { f32 x, y, z, w; }; struct { f32 r, g, b, a; }; Dqn_V3 rgb; f32 e[4]; constexpr Dqn_V4() = default; constexpr Dqn_V4(f32 xyzw) : x(xyzw), y(xyzw), z(xyzw), w(xyzw) {} constexpr Dqn_V4(f32 x_, f32 y_, f32 z_, f32 w_): x(x_), y(y_), z(z_), w(w_) {} constexpr Dqn_V4(i32 x_, i32 y_, i32 z_, i32 w_): x((f32)x_), y((f32)y_), z((f32)z_), w((f32)w_) {} constexpr Dqn_V4(Dqn_V3 xyz, f32 w_) : x(xyz.x), y(xyz.y), z(xyz.z), w(w_) {} constexpr bool operator!=(Dqn_V4 other) const { return !(*this == other); } constexpr bool operator==(Dqn_V4 other) const { return (x == other.x) && (y == other.y) && (z == other.z) && (w == other.w); } constexpr bool operator>=(Dqn_V4 other) const { return (x >= other.x) && (y >= other.y) && (z >= other.z) && (w >= other.w); } constexpr bool operator<=(Dqn_V4 other) const { return (x <= other.x) && (y <= other.y) && (z <= other.z) && (w <= other.w); } constexpr bool operator< (Dqn_V4 other) const { return (x < other.x) && (y < other.y) && (z < other.z) && (w < other.w); } constexpr bool operator> (Dqn_V4 other) const { return (x > other.x) && (y > other.y) && (z > other.z) && (w > other.w); } constexpr Dqn_V4 operator- (Dqn_V4 other) const { Dqn_V4 result(x - other.x, y - other.y, z - other.z, w - other.w); return result; } constexpr Dqn_V4 operator+ (Dqn_V4 other) const { Dqn_V4 result(x + other.x, y + other.y, z + other.z, w + other.w); return result; } constexpr Dqn_V4 operator* (Dqn_V4 other) const { Dqn_V4 result(x * other.x, y * other.y, z * other.z, w * other.w); return result; } constexpr Dqn_V4 operator* (f32 other) const { Dqn_V4 result(x * other, y * other, z * other, w * other); return result; } constexpr Dqn_V4 operator* (i32 other) const { Dqn_V4 result(x * other, y * other, z * other, w * other); return result; } constexpr Dqn_V4 operator/ (f32 other) const { Dqn_V4 result(x / other, y / other, z / other, w / other); return result; } constexpr Dqn_V4 &operator*=(Dqn_V4 other) { *this = *this * other; return *this; } constexpr Dqn_V4 &operator*=(f32 other) { *this = *this * other; return *this; } constexpr Dqn_V4 &operator*=(i32 other) { *this = *this * other; return *this; } constexpr Dqn_V4 &operator-=(Dqn_V4 other) { *this = *this - other; return *this; } constexpr Dqn_V4 &operator+=(Dqn_V4 other) { *this = *this + other; return *this; } }; struct Dqn_Rect { Dqn_V2 min, max; Dqn_Rect() = default; Dqn_Rect(Dqn_V2 min, Dqn_V2 max) : min(min), max(max) {} Dqn_Rect(Dqn_V2I min, Dqn_V2I max) : min(min), max(max) {} }; struct Dqn_RectI32 { Dqn_V2I min, max; Dqn_RectI32() = default; Dqn_RectI32(Dqn_V2I min, Dqn_V2I max) : min(min), max(max) {} }; union Dqn_Mat4 { f32 e[16]; Dqn_V4 row[4]; f32 row_major[4][4]; f32 operator[](usize i) const { return e[i]; } }; template int Dqn_MemCmpType(T const *ptr1, T const *ptr2) { int result = memcmp(ptr1, ptr2, sizeof(T)); return result; } template T *Dqn_MemZero(T *src) { T *result = static_cast(Dqn_MemSet(src, 0, sizeof(T))); return result; } // ------------------------------------------------------------------------------------------------- // // NOTE: Dqn_MemArena // // ------------------------------------------------------------------------------------------------- using MemSize = usize; struct MemBlock { // NOTE: Read only state b32 allocated_by_user_or_fixed_mem; void *memory; MemSize size; MemSize used; MemBlock *prev; MemBlock *next; }; enum Dqn_MemArenaFlag { Dqn_MemArenaFlag_NoCRTAllocation = (1 << 0), // If my_calloc/my_free aren't null, it defaults to calloc and free, setting this flag disables that }; // TODO(doyle): We should try support function pointer syntax (maybe) for inspection typedef void *(Dqn_MemArenaCallocFunction)(size_t bytes); typedef void (Dqn_MemArenaFreeFunction) (void *ptr, size_t bytes_to_free); struct Dqn_MemArena { // NOTE: Configuration (fill once) Dqn_MemArenaCallocFunction *my_calloc; // If nullptr, use CRT calloc unless disabled in flags Dqn_MemArenaFreeFunction *my_free; // If nullptr, use CRT free unless disabled in flags u32 flags; // NOTE: Read Only u8 fixed_mem[DQN_KILOBYTES(16)]; MemBlock fixed_mem_block; MemBlock *curr_mem_block; MemBlock *top_mem_block; MemSize highest_used_mark; int total_allocated_mem_blocks; }; struct Dqn_MemArenaScopedRegion { Dqn_MemArenaScopedRegion(Dqn_MemArena *arena); ~Dqn_MemArenaScopedRegion(); Dqn_MemArena *arena; MemBlock *curr_mem_block; usize curr_mem_block_used; MemBlock *top_mem_block; }; #define DQN_DEBUG_MEM_ARENA_LOGGING #if defined(DQN_DEBUG_MEM_ARENA_LOGGING) #define DQN_DEBUG_ARGS , char const *file, isize file_len, char const *func, isize func_len, isize line #define DQN_DEBUG_PARAMS , DQN_STR_AND_LEN(__FILE__), DQN_STR_AND_LEN(__func__), __LINE__ #else #define DQN_DEBUG_ARGS #define DQN_DEBUG_PARAMS #endif #define MEM_ARENA_ALLOC(arena, size) Dqn_MemArena_Alloc(arena, size DQN_DEBUG_PARAMS); #define MEM_ARENA_ALLOC_ARRAY(arena, T, num) (T *)Dqn_MemArena_Alloc(arena, sizeof(T) * num DQN_DEBUG_PARAMS); #define MEM_ARENA_ALLOC_STRUCT(arena, T) (T *)Dqn_MemArena_Alloc(arena, sizeof(T) DQN_DEBUG_PARAMS); #define MEM_ARENA_RESERVE(arena, size) Dqn_MemArena_Reserve(arena, size DQN_DEBUG_PARAMS); #define MEM_ARENA_RESERVE_FROM(arena, src, size) Dqn_MemArena_ReserveFrom(arena, src, size DQN_DEBUG_PARAMS); #define MEM_ARENA_CLEAR_USED(arena) Dqn_MemArena_ClearUsed(arena DQN_DEBUG_PARAMS); #define MEM_ARENA_FREE(arena) Dqn_MemArena_Free // ------------------------------------------------------------------------------------------------- // // NOTE: String Builder // // ------------------------------------------------------------------------------------------------- struct Dqn_StringBuilderBuffer { char *mem; MemSize size; MemSize used; Dqn_StringBuilderBuffer *next; }; usize constexpr DQN_STRING_BUILDER_MIN_MEM_BUF_ALLOC_SIZE = DQN_KILOBYTES(4); template struct Dqn_StringBuilder { void *(*my_malloc)(size_t bytes) = malloc; // Set to nullptr to disable heap allocation void (*my_free) (void *ptr) = free; // Set to nullptr to disable heap allocation char fixed_mem[N]; MemSize fixed_mem_used; Dqn_StringBuilderBuffer *next_mem_buf; Dqn_StringBuilderBuffer *last_mem_buf; isize string_len; }; DQN_HEADER_COPY_PROTOTYPE( template FILE_SCOPE char *, Dqn_StringBuilder__GetWriteBufferAndUpdateUsage(Dqn_StringBuilder *builder, usize size_required)) { char *result = builder->fixed_mem + builder->fixed_mem_used; usize space = Dqn_ArrayCount(builder->fixed_mem) - builder->fixed_mem_used; usize *usage = &builder->fixed_mem_used; if (builder->last_mem_buf) { Dqn_StringBuilderBuffer *last_buf = builder->last_mem_buf; result = last_buf->mem + last_buf->used; space = last_buf->size - last_buf->used; usage = &last_buf->used; } if (space < size_required) { DQN_ASSERT(builder->my_malloc); if (!builder->my_malloc) return nullptr; // NOTE: Need to allocate new buf usize allocation_size = sizeof(*builder->last_mem_buf) + DQN_MAX(size_required, DQN_STRING_BUILDER_MIN_MEM_BUF_ALLOC_SIZE); void *memory = builder->my_malloc(allocation_size); auto *new_buf = reinterpret_cast(memory); *new_buf = {}; new_buf->mem = static_cast(memory) + sizeof(*new_buf); new_buf->size = allocation_size; result = new_buf->mem; usage = &new_buf->used; if (builder->last_mem_buf) { builder->last_mem_buf->next = new_buf; } else { builder->next_mem_buf = new_buf; builder->last_mem_buf = new_buf; } } if (size_required > 0 && *usage > 0 && result[-1] == 0) { // NOTE: Not first time writing into buffer using sprintf, sprintf always writes a null terminator, so we must // subtract one (*usage)--; result--; } *usage += size_required; return result; } DQN_HEADER_COPY_PROTOTYPE( template FILE_SCOPE void, Dqn_StringBuilder__BuildOutput(Dqn_StringBuilder const *builder, char *dest, isize dest_size)) { // NOTE: No data appended to builder, just allocate am empty string. But // always allocate, so we avoid adding making nullptr part of the possible // return values and makes using Dqn_StringBuilder more complex. if (dest_size == 1) { dest[0] = 0; return; } char const *end = dest + dest_size; char *buf_ptr = dest; memcpy(buf_ptr, builder->fixed_mem, builder->fixed_mem_used); buf_ptr += builder->fixed_mem_used; isize remaining_space = end - buf_ptr; DQN_ASSERT(remaining_space >= 0); for (Dqn_StringBuilderBuffer *string_buf = builder->next_mem_buf; string_buf && remaining_space > 0; string_buf = string_buf->next) { buf_ptr--; // We always copy the null terminator from the buffers, so if we know we have another buffer to copy from, remove the null terminator memcpy(buf_ptr, string_buf->mem, string_buf->used); buf_ptr += string_buf->used; remaining_space = end - buf_ptr; DQN_ASSERT(remaining_space >= 0); } DQN_ASSERT(buf_ptr == dest + dest_size); } // ------------------------------------------------------------------------------------------------- // // NOTE: String Builder // // ------------------------------------------------------------------------------------------------- DQN_HEADER_COPY_PROTOTYPE_AND_COMMENT( "The necessary length to build the string, it returns the length including the null-terminator", template isize, Dqn_StringBuilder_BuildLen(Dqn_StringBuilder const *builder)) { isize result = builder->string_len + 1; return result; } DQN_HEADER_COPY_PROTOTYPE( template void, Dqn_StringBuilder_BuildInBuffer(Dqn_StringBuilder const *builder, char *dest, usize dest_size)) { Dqn_StringBuilder__BuildOutput(builder, dest, dest_size); } DQN_HEADER_COPY_PROTOTYPE_AND_COMMENT( "len: Return the length of the allocated string including the null-terminator", template , char *Dqn_StringBuilder_BuildFromMalloc(Dqn_StringBuilder *builder, isize *len = nullptr)) { isize len_w_null_terminator = Dqn_StringBuilder_BuildLen(builder); auto *result = static_cast(malloc(len_w_null_terminator)); if (len) *len = len_w_null_terminator; Dqn_StringBuilder__BuildOutput(builder, result, len_w_null_terminator); return result; } DQN_HEADER_COPY_PROTOTYPE( template , char *Dqn_StringBuilder_BuildFromArena(Dqn_StringBuilder *builder, Dqn_MemArena *arena, isize *len = nullptr)) { isize len_w_null_terminator = Dqn_StringBuilder_BuildLen(builder); char *result = MEM_ARENA_ALLOC_ARRAY(arena, char, len_w_null_terminator); if (len) *len = len_w_null_terminator; Dqn_StringBuilder__BuildOutput(builder, result, len_w_null_terminator); return result; } DQN_HEADER_COPY_PROTOTYPE( template , void Dqn_StringBuilder_VFmtAppend(Dqn_StringBuilder *builder, char const *fmt, va_list va)) { if (!fmt) return; isize require = stbsp_vsnprintf(nullptr, 0, fmt, va) + 1; char *buf = Dqn_StringBuilder__GetWriteBufferAndUpdateUsage(builder, require); stbsp_vsnprintf(buf, static_cast(require), fmt, va); builder->string_len += (require - 1); // -1 to exclude null terminator } DQN_HEADER_COPY_PROTOTYPE( template , void Dqn_StringBuilder_FmtAppend(Dqn_StringBuilder *builder, char const *fmt, ...)) { va_list va; va_start(va, fmt); Dqn_StringBuilder_VFmtAppend(builder, fmt, va); va_end(va); } DQN_HEADER_COPY_PROTOTYPE( template , void Dqn_StringBuilder_Append(Dqn_StringBuilder *builder, char const *str, isize len = -1)) { if (!str) return; if (len == -1) len = (isize)strlen(str); isize len_w_null_terminator = len + 1; char *buf = Dqn_StringBuilder__GetWriteBufferAndUpdateUsage(builder, len_w_null_terminator); Dqn_MemCopy(buf, str, len); builder->string_len += len; buf[len] = 0; } DQN_HEADER_COPY_PROTOTYPE( template , void Dqn_StringBuilder_AppendChar(Dqn_StringBuilder *builder, char ch)) { char *buf = Dqn_StringBuilder__GetWriteBufferAndUpdateUsage(builder, 1 + 1 /*null terminator*/); *buf++ = ch; builder->string_len++; buf[1] = 0; } // ------------------------------------------------------------------------------------------------- // // NOTE: (Memory) Slices // // ------------------------------------------------------------------------------------------------- template struct Slice { union { T *data; T *buf; T *str; }; union { isize size; isize len; }; Slice() = default; Slice(T *str, isize len) { this->str = str; this->len = len; } T const &operator[] (isize i) const { DQN_ASSERT_MSG(i >= 0 && i < len, "%d >= 0 && %d < %d", i, len); return data[i]; } T &operator[] (isize i) { DQN_ASSERT_MSG(i >= 0 && i < len, "%d >= 0 && %d < %d", i, len); return data[i]; } T const *begin () const { return data; } T const *end () const { return data + len; } T *begin () { return data; } T *end () { return data + len; } T const *operator+ (isize i) const { DQN_ASSERT_MSG(i >= 0 && i < len, "%d >= 0 && %d < %d", i, len); return data + i; } T *operator+ (isize i) { DQN_ASSERT_MSG(i >= 0 && i < len, "%d >= 0 && %d < %d", i, len); return data + i; } }; #define SLICE_LITERAL(string) Slice(DQN_STR_AND_LEN(string)) DQN_HEADER_COPY_PROTOTYPE( template , inline Slice Slice_CopyNullTerminated(Dqn_MemArena *arena, T const *src, isize len)) { Slice result = {}; result.len = len; result.buf = MEM_ARENA_ALLOC_ARRAY(arena, T, len + 1); Dqn_MemCopy(result.buf, src, len * sizeof(T)); result.buf[len] = 0; return result; } DQN_HEADER_COPY_PROTOTYPE( template , inline Slice Slice_CopyNullTerminated(Dqn_MemArena *arena, Slice const src)) { Slice result = Slice_CopyNullTerminated(arena, src.buf, src.len); return result; } DQN_HEADER_COPY_PROTOTYPE( template , inline Slice Slice_Copy(Dqn_MemArena *arena, T const *src, isize len)) { Slice result = {}; result.len = len; result.buf = MEM_ARENA_ALLOC_ARRAY(arena, T, len); MemCopy(result.buf, src, len * sizeof(T)); return result; } DQN_HEADER_COPY_PROTOTYPE( template , inline Slice Slice_Copy(Dqn_MemArena *arena, Slice const src)) { Slice result = Slice_Copy(arena, src.buf, src.len); return result; } // ------------------------------------------------------------------------------------------------- // // NOTE: Asprintf (Allocate Sprintf) // // ------------------------------------------------------------------------------------------------- DQN_HEADER_COPY_PROTOTYPE( template Slice, AsprintfSlice(T *arena, char const *fmt, va_list va)) { Slice result = {}; result.len = stbsp_vsnprintf(nullptr, 0, fmt, va) + 1; result.buf = MEM_ARENA_ALLOC_ARRAY(arena, char, result.len); stbsp_vsnprintf(result.buf, Dqn_SafeTruncateISizeToInt(result.len), fmt, va); result.buf[result.len - 1] = 0; return result; } DQN_HEADER_COPY_PROTOTYPE( template Slice, AsprintfSlice(T *arena, char const *fmt, ...)) { va_list va; va_start(va, fmt); Slice result = AsprintfSlice(arena, fmt, va); va_end(va); return result; } DQN_HEADER_COPY_PROTOTYPE( template char *, Asprintf(T *arena, int *len, char const *fmt, ...)) { va_list va; va_start(va, fmt); Slice result = AsprintfSlice(arena, fmt, va); va_end(va); if (len) *len = result.len; return result.str; } DQN_HEADER_COPY_PROTOTYPE( template char *, Asprintf(T *arena, char const *fmt, ...)) { va_list va; va_start(va, fmt); Slice result = AsprintfSlice(arena, fmt, va); va_end(va); return result.str; } // ------------------------------------------------------------------------------------------------- // // NOTE: Dqn_FixedArray // // ------------------------------------------------------------------------------------------------- template void EraseStableFromCArray(T *array, isize len, isize max, isize index) { DQN_ASSERT(index >= 0 && index < len); DQN_ASSERT(len <= max); isize next_index = DQN_MIN(index + 1, len); usize bytes_to_copy = (len - next_index) * sizeof(T); Dqn_MemMove(array + index, array + next_index, bytes_to_copy); } #define FIXED_ARRAY_TEMPLATE_DECL template FIXED_ARRAY_TEMPLATE_DECL struct Dqn_FixedArray { T data[MAX_]; isize len; isize Max() const { return MAX_; } T const &operator[] (isize i) const { DQN_ASSERT_MSG(i >= 0 && i < len, "%d >= 0 && %d < %d", i, len); return data[i]; } T &operator[] (isize i) { DQN_ASSERT_MSG(i >= 0 && i < len, "%d >= 0 && %d < %d", i, len); return data[i]; } T const *begin () const { return data; } T const *end () const { return data + len; } T *begin () { return data; } T *end () { return data + len; } T const *operator+ (isize i) const { DQN_ASSERT_MSG(i >= 0 && i < len, "%d >= 0 && %d < %d", i, len); return data + i; } T *operator+ (isize i) { DQN_ASSERT_MSG(i >= 0 && i < len, "%d >= 0 && %d < %d", i, len); return data + i; } }; FIXED_ARRAY_TEMPLATE_DECL Dqn_FixedArray Dqn_FixedArray_Init (T const *item, int num) { Dqn_FixedArray result = {}; Dqn_FixedArray_Add(&result, item, num); return result; } FIXED_ARRAY_TEMPLATE_DECL T *Dqn_FixedArray_Add (Dqn_FixedArray *a, T const *items, isize num) { DQN_ASSERT(a->len + num <= MAX_); T *result = static_cast(Dqn_MemCopy(a->data + a->len, items, sizeof(T) * num)); a->len += num; return result; } FIXED_ARRAY_TEMPLATE_DECL T *Dqn_FixedArray_Add (Dqn_FixedArray *a, T const item) { DQN_ASSERT(a->len < MAX_); a->data[a->len++] = item; return &a->data[a->len - 1]; } FIXED_ARRAY_TEMPLATE_DECL T *Dqn_FixedArray_Make (Dqn_FixedArray *a, isize num) { DQN_ASSERT(a->len + num <= MAX_); T *result = a->data + a->len; a->len += num; return result;} FIXED_ARRAY_TEMPLATE_DECL void Dqn_FixedArray_Clear (Dqn_FixedArray *a) { a->len = 0; } FIXED_ARRAY_TEMPLATE_DECL void Dqn_FixedArray_EraseStable (Dqn_FixedArray *a, isize index) { EraseStableFromCArray(a->data, a->len--, a->Max(), index); } FIXED_ARRAY_TEMPLATE_DECL void Dqn_FixedArray_EraseUnstable(Dqn_FixedArray *a, isize index) { DQN_ASSERT(index >= 0 && index < a->len); if (--a->len == 0) return; a->data[index] = a->data[a->len]; } FIXED_ARRAY_TEMPLATE_DECL void Dqn_FixedArray_Pop (Dqn_FixedArray *a, isize num) { DQN_ASSERT(a->len - num >= 0); a->len -= num; } FIXED_ARRAY_TEMPLATE_DECL T *Dqn_FixedArray_Peek (Dqn_FixedArray *a) { T *result = (a->len == 0) ? nullptr : a->data + (a->len - 1); return result; } FIXED_ARRAY_TEMPLATE_DECL isize Dqn_FixedArray_GetIndex (Dqn_FixedArray *a, T const *entry) { isize result = a->end() - entry; return result; } template T *Dqn_FixedArray_Find(Dqn_FixedArray *a, EqualityProc IsEqual) { for (T &entry : (*a)) { if (IsEqual(&entry)) return &entry; } return nullptr; } FIXED_ARRAY_TEMPLATE_DECL T *Dqn_FixedArray_Find(Dqn_FixedArray *a, T *entry) { for (T &entry : (*a)) { if (T *result == entry) return result; } return nullptr; } // ------------------------------------------------------------------------------------------------- // // NOTE: Dqn_FixedStack // // ------------------------------------------------------------------------------------------------- template using Dqn_FixedStack = Dqn_FixedArray; template T Dqn_FixedStack_Pop (Dqn_FixedStack *array) { T result = *Dqn_FixedArray_Peek(array); Dqn_FixedArray_Pop(array, 1); return result; } template T *Dqn_FixedStack_Peek (Dqn_FixedStack *array) { return Dqn_FixedArray_Peek(array); } template T *Dqn_FixedStack_Push (Dqn_FixedStack *array, T item) { return Dqn_FixedArray_Add(array, item); } template void Dqn_FixedStack_Clear(Dqn_FixedStack *array) { Dqn_FixedArray_Clear(array); } // ------------------------------------------------------------------------------------------------- // // NOTE: Dqn_StaticArray // // ------------------------------------------------------------------------------------------------- template struct Dqn_StaticArray { T *data; isize len; isize max; T const operator[](isize i) const { DQN_ASSERT_MSG(i >= 0 && i < len, "%d >= 0 && %d < %d", i, len); return data[i]; } T operator[](isize i) { DQN_ASSERT_MSG(i >= 0 && i < len, "%d >= 0 && %d < %d", i, len); return data[i]; } T const *begin () const { return data; } T const *end () const { return data + len; } T *begin () { return data; } T *end () { return data + len; } T const *operator+(isize i) const { DQN_ASSERT_MSG(i >= 0 && i < len, "%d >= 0 && %d < %d", i, len); return data + i; } T *operator+(isize i) { DQN_ASSERT_MSG(i >= 0 && i < len, "%d >= 0 && %d < %d", i, len); return data + i; } }; template Dqn_StaticArray Dqn_StaticArray_InitMemory (T *memory, isize max, isize len = 0) { Dqn_StaticArray result = {}; result.data = memory; result.len = len; result.max = max; return result; } template T *Dqn_StaticArray_Add (Dqn_StaticArray *a, T const *items, isize num) { DQN_ASSERT(a->len + num <= a->max); T *result = static_cast(Dqn_MemCopy(a->data + a->len, items, sizeof(T) * num)); a->len += num; return result; } template T *Dqn_StaticArray_Add (Dqn_StaticArray *a, T const item) { DQN_ASSERT(a->len < a->max); a->data[a->len++] = item; return &a->data[a->len - 1]; } template T *Dqn_StaticArray_Make (Dqn_StaticArray *a, isize num) { DQN_ASSERT(a->len + num <= a->max); T *result = a->data + a->len; a->len += num; return result;} template void Dqn_StaticArray_Clear (Dqn_StaticArray *a) { a->len = 0; } template void Dqn_StaticArray_EraseStable (Dqn_StaticArray *a, isize index) { EraseStableFromCArray(a->data, a->len--, a->max, index); } template void Dqn_StaticArray_EraseUnstable(Dqn_StaticArray *a, isize index) { DQN_ASSERT(index >= 0 && index < a->len); if (--a->len == 0) return; a->data[index] = a->data[a->len]; } template void Dqn_StaticArray_Pop (Dqn_StaticArray *a, isize num) { DQN_ASSERT(a->len - num >= 0); a->len -= num; } template T *Dqn_StaticArray_Peek (Dqn_StaticArray *a) { T *result = (a->len == 0) ? nullptr : a->data + (a->len - 1); return result; } // ------------------------------------------------------------------------------------------------- // // NOTE: Dqn_FixedString // // ------------------------------------------------------------------------------------------------- template struct Dqn_FixedString { union { char data[MAX_]; char str[MAX_]; char buf[MAX_]; }; isize len; isize Max() const { return MAX_; } Dqn_FixedString() { data[0] = 0; len = 0; } Dqn_FixedString(char const *fmt, ...); char const &operator[] (isize i) const { DQN_ASSERT_MSG(i >= 0 && i < len, "%d >= 0 && %d < %d", i, len); return data[i]; } char &operator[] (isize i) { DQN_ASSERT_MSG(i >= 0 && i < len, "%d >= 0 && %d < %d", i, len); return data[i]; } char const *begin () const { return data; } char const *end () const { return data + len; } char *begin () { return data; } char *end () { return data + len; } }; template void Dqn_FixedString_Clear (Dqn_FixedString *str) { *str = {}; } template void Dqn_FixedString_AppendVFmt(Dqn_FixedString *str, char const *fmt, va_list va) { isize require = stbsp_vsnprintf(nullptr, 0, fmt, va) + 1; isize space = MAX_ - str->len; DQN_ASSERT(require <= space); str->len += stbsp_vsnprintf(str->data + str->len, static_cast(space), fmt, va); } template void Dqn_FixedString_AppendFmt(Dqn_FixedString *str, char const *fmt, ...) { va_list va; va_start(va, fmt); Dqn_FixedString_AppendVFmt(str, fmt, va); va_end(va); } template void Dqn_FixedString_Append(Dqn_FixedString *str, char const *src, isize len = -1) { if (len == -1) len = (isize)strlen(src); isize space = MAX_ - str->len; DQN_ASSERT(len <= space); memcpy(str->data + str->len, src, len); str->len += len; str->str[str->len] = 0; } template Dqn_FixedString::Dqn_FixedString(char const *fmt, ...) { *this = {}; va_list va; va_start(va, fmt); Dqn_FixedString_AppendVFmt(this, fmt, va); va_end(va); } struct U64Str { // Points to the start of the str in the buffer, not necessarily buf since // we write into the buffer in reverse char *start; char buf[27]; // NOTE(doyle): 27 is the maximum size of u64 including commas int len; }; #endif // DQN_H #ifdef DQN_IMPLEMENTATION #include #include #include #include #include // ------------------------------------------------------------------------------------------------- // // NOTE: Helpers // // ------------------------------------------------------------------------------------------------- int Dqn_MemCmp(void const *ptr1, void const *ptr2, size_t num_bytes) { int result = memcmp(ptr1, ptr2, num_bytes); return result; } void *Dqn_MemCopy(void *dest, void const *src, size_t num_bytes) { void *result = memcpy(dest, src, num_bytes); return result; } void *Dqn_MemMove(void *dest, void const *src, size_t num_bytes) { void *result = memmove(dest, src, num_bytes); return result; } void *Dqn_MemSet(void *src, char ch, usize num_bytes) { void *result = memset(src, ch, num_bytes); return result; } // ------------------------------------------------------------------------------------------------- // // NOTE: Logging // // ------------------------------------------------------------------------------------------------- void Dqn_LogV(Dqn_LogType type, char const *file, usize file_len, char const *func, usize func_len, usize line, char const *fmt, va_list va) { char const *file_ptr = file; usize file_ptr_len = file_len; for (usize i = (file_ptr_len - 1); i >= 0; --i) { if (file_ptr[i] == '\\' || file_ptr[i] == '/') { char const *file_end = file_ptr + file_ptr_len; file_ptr = file_ptr + (i + 1); file_ptr_len = static_cast(file_end - file_ptr); break; } } FILE *handle = (type == Dqn_LogType::Error) ? stderr : stdout; fprintf(handle, "%.*s %05zu %.*s %s ", (int)file_ptr_len, file_ptr, line, (int)func_len, func, Dqn_LogTypeTag(type)); vfprintf(handle, fmt, va); fprintf(handle, "\n"); } void Dqn_Log(Dqn_LogType type, char const *file, usize file_len, char const *func, usize func_len, usize line, char const *fmt, ...) { va_list va; va_start(va, fmt); Dqn_LogV(type, file, file_len, func, func_len, line, fmt, va); va_end(va); } // ------------------------------------------------------------------------------------------------- // // NOTE: Dqn_MemArena Internal // // ------------------------------------------------------------------------------------------------- FILE_SCOPE MemBlock *Dqn_MemArena__AllocateBlock(Dqn_MemArena *arena, usize requested_size) { usize mem_block_size = DQN_MAX(Dqn_ArrayCount(arena->fixed_mem), requested_size); usize const allocate_size = sizeof(*arena->curr_mem_block) + mem_block_size; MemBlock *result = nullptr; if (arena->my_calloc) { DQN_ASSERT(arena->my_free); result = static_cast(arena->my_calloc(allocate_size)); } else { if (!(arena->flags & Dqn_MemArenaFlag_NoCRTAllocation)) result = static_cast(calloc(1, allocate_size)); } if (!result) return result; *result = {}; result->size = mem_block_size; result->memory = reinterpret_cast(result) + sizeof(*result); arena->total_allocated_mem_blocks++; return result; } FILE_SCOPE void Dqn_MemArena__FreeBlock(Dqn_MemArena *arena, MemBlock *block) { if (!block) return; if (block->next) block->next->prev = block->prev; if (block->prev) block->prev->next = block->next; if (!block->allocated_by_user_or_fixed_mem) { if (arena->my_free) { DQN_ASSERT(arena->my_calloc); arena->my_free(block, sizeof(*block) + block->size); } else { if (!(arena->flags & Dqn_MemArenaFlag_NoCRTAllocation)) free(block); } } } FILE_SCOPE void Dqn_MemArena__AttachBlock(Dqn_MemArena *arena, MemBlock *new_block) { DQN_ASSERT(arena->curr_mem_block); DQN_ASSERT(arena->top_mem_block->next == nullptr); arena->top_mem_block->next = new_block; new_block->prev = arena->top_mem_block; arena->top_mem_block = new_block; } FILE_SCOPE void Dqn_MemArena__LazyInit(Dqn_MemArena *arena) { if (!arena->curr_mem_block) { DQN_ASSERT(!arena->top_mem_block); arena->fixed_mem_block = {}; arena->fixed_mem_block.allocated_by_user_or_fixed_mem = true; arena->fixed_mem_block.memory = arena->fixed_mem; arena->fixed_mem_block.size = Dqn_ArrayCount(arena->fixed_mem); arena->curr_mem_block = &arena->fixed_mem_block; arena->top_mem_block = arena->curr_mem_block; } } // ------------------------------------------------------------------------------------------------- // // NOTE: Dqn_MemArena // // ------------------------------------------------------------------------------------------------- void *Dqn_MemArena_Alloc(Dqn_MemArena *arena, usize size DQN_DEBUG_ARGS) { #if defined(DQN_DEBUG_MEM_ARENA_LOGGING) (void)file; (void)file_len; (void)func; (void)func_len; (void)line; #endif Dqn_MemArena__LazyInit(arena); b32 need_new_mem_block = true; for (MemBlock *mem_block = arena->curr_mem_block; mem_block; mem_block = mem_block->next) { b32 can_fit_in_block = (mem_block->used + size) <= mem_block->size; if (can_fit_in_block) { arena->curr_mem_block = mem_block; need_new_mem_block = false; break; } } if (need_new_mem_block) { MemBlock *new_block = Dqn_MemArena__AllocateBlock(arena, size); if (!new_block) return nullptr; Dqn_MemArena__AttachBlock(arena, new_block); arena->curr_mem_block = arena->top_mem_block; } void *result = static_cast(arena->curr_mem_block->memory) + arena->curr_mem_block->used; arena->curr_mem_block->used += size; DQN_ASSERT(arena->curr_mem_block->used <= arena->curr_mem_block->size); return result; } void Dqn_MemArena_Free(Dqn_MemArena *arena DQN_DEBUG_ARGS) { #if defined(DQN_DEBUG_MEM_ARENA_LOGGING) (void)file; (void)file_len; (void)func; (void)func_len; (void)line; #endif for (MemBlock *mem_block = arena->top_mem_block; mem_block;) { MemBlock *block_to_free = mem_block; mem_block = block_to_free->prev; Dqn_MemArena__FreeBlock(arena, block_to_free); } auto my_calloc = arena->my_calloc; auto my_free = arena->my_free; auto highest_used_mark = arena->highest_used_mark; *arena = {}; arena->highest_used_mark = highest_used_mark; arena->my_calloc = my_calloc; arena->my_free = my_free; } b32 Dqn_MemArena_Reserve(Dqn_MemArena *arena, usize size DQN_DEBUG_ARGS) { #if defined(DQN_DEBUG_MEM_ARENA_LOGGING) (void)file; (void)file_len; (void)func; (void)func_len; (void)line; #endif Dqn_MemArena__LazyInit(arena); MemSize remaining_space = arena->top_mem_block->size - arena->top_mem_block->used; if (remaining_space >= size) return true; MemBlock *new_block = Dqn_MemArena__AllocateBlock(arena, size); if (!new_block) return false; Dqn_MemArena__AttachBlock(arena, new_block); return true; } void Dqn_MemArena_ReserveFrom(Dqn_MemArena *arena, void *memory, usize size DQN_DEBUG_ARGS) { #if defined(DQN_DEBUG_MEM_ARENA_LOGGING) (void)file; (void)file_len; (void)func; (void)func_len; (void)line; #endif DQN_ASSERT_MSG(size >= sizeof(*arena->curr_mem_block), "(%zu >= %zu) There needs to be enough space to encode the MemBlock struct into the memory buffer", size, sizeof(*arena->curr_mem_block)); Dqn_MemArena__LazyInit(arena); auto *mem_block = static_cast(memory); *mem_block = {}; mem_block->memory = static_cast(memory) + sizeof(*mem_block); mem_block->size = size - sizeof(*mem_block); mem_block->allocated_by_user_or_fixed_mem = true; Dqn_MemArena__AttachBlock(arena, mem_block); } void Dqn_MemArena_ClearUsed(Dqn_MemArena *arena DQN_DEBUG_ARGS) { #if defined(DQN_DEBUG_MEM_ARENA_LOGGING) (void)file; (void)file_len; (void)func; (void)func_len; (void)line; #endif for (MemBlock *mem_block = arena->top_mem_block; mem_block; mem_block = mem_block->prev) mem_block->used = 0; arena->curr_mem_block = &arena->fixed_mem_block; } Dqn_MemArenaScopedRegion Dqn_MemArena_MakeScopedRegion(Dqn_MemArena *arena) { return Dqn_MemArenaScopedRegion(arena); } Dqn_MemArenaScopedRegion::Dqn_MemArenaScopedRegion(Dqn_MemArena *arena) { this->arena = arena; this->curr_mem_block = arena->curr_mem_block; this->curr_mem_block_used = (arena->curr_mem_block) ? arena->curr_mem_block->used : 0; this->top_mem_block = arena->top_mem_block; } Dqn_MemArenaScopedRegion::~Dqn_MemArenaScopedRegion() { while (this->top_mem_block != this->arena->top_mem_block) { MemBlock *block_to_free = this->arena->top_mem_block; this->arena->top_mem_block = block_to_free->prev; Dqn_MemArena__FreeBlock(this->arena, block_to_free); } for (MemBlock *mem_block = this->arena->top_mem_block; mem_block != this->curr_mem_block; mem_block = mem_block->prev) mem_block->used = 0; this->arena->curr_mem_block->used = this->curr_mem_block_used; } // ------------------------------------------------------------------------------------------------- // // NOTE: Vectors // // ------------------------------------------------------------------------------------------------- Dqn_V2I Dqn_V2_ToV2I(Dqn_V2 a) { Dqn_V2I result(static_cast(a.x), static_cast(a.y)); return result; } Dqn_V2 Dqn_V2_Max(Dqn_V2 a, Dqn_V2 b) { Dqn_V2 result = Dqn_V2(DQN_MAX(a.x, b.x), DQN_MAX(a.y, b.y)); return result; } Dqn_V2 Dqn_V2_Abs(Dqn_V2 a) { Dqn_V2 result = Dqn_V2(DQN_ABS(a.x), DQN_ABS(a.y)); return result; } f32 Dqn_V2_Dot(Dqn_V2 a, Dqn_V2 b) { f32 result = (a.x * b.x) + (a.y * b.y); return result; } f32 Dqn_V2_LengthSq(Dqn_V2 a, Dqn_V2 b) { f32 x_side = b.x - a.x; f32 y_side = b.y - a.y; f32 result = DQN_SQUARED(x_side) + DQN_SQUARED(y_side); return result; } Dqn_V2 Dqn_V2_Normalise(Dqn_V2 a) { f32 length_sq = DQN_SQUARED(a.x) + DQN_SQUARED(a.y); f32 length = sqrtf(length_sq); Dqn_V2 result = a / length; return result; } Dqn_V2 Dqn_V2_Perpendicular(Dqn_V2 a) { Dqn_V2 result = Dqn_V2(-a.y, a.x); return result; } f32 Dqn_V4_Dot(Dqn_V4 const *a, Dqn_V4 const *b) { f32 result = (a->x * b->x) + (a->y * b->y) + (a->z * b->z) + (a->w * b->w); return result; } // ------------------------------------------------------------------------------------------------- // // NOTE: Rect // // ------------------------------------------------------------------------------------------------- Dqn_Rect Dqn_Rect_InitFromPosAndSize(Dqn_V2 pos, Dqn_V2 size) { Dqn_Rect result = {}; result.min = pos; if (size.w < 0) result.min.x -= size.w; if (size.h < 0) result.min.y -= size.h; result.max = result.min + Dqn_V2_Abs(size); return result; } Dqn_V2 Dqn_Rect_Center(Dqn_Rect rect) { Dqn_V2 size = rect.max - rect.min; Dqn_V2 result = rect.min + (size * 0.5f); return result; } b32 Dqn_Rect_ContainsPoint(Dqn_Rect rect, Dqn_V2 p) { b32 result = (p.x >= rect.min.x && p.x <= rect.max.x && p.y >= rect.min.y && p.y <= rect.max.y); return result; } b32 Dqn_Rect_ContainsRect(Dqn_Rect a, Dqn_Rect b) { b32 result = (b.min >= a.min && b.max <= a.max); return result; } Dqn_V2 Dqn_Rect_Size(Dqn_Rect rect) { Dqn_V2 result = rect.max - rect.min; return result; } Dqn_Rect Dqn_Rect_Move(Dqn_Rect src, Dqn_V2 move_amount) { Dqn_Rect result = src; result.min += move_amount; result.max += move_amount; return result; } Dqn_Rect Dqn_Rect_Union(Dqn_Rect a, Dqn_Rect b) { Dqn_Rect result = {}; result.min.x = DQN_MIN(a.min.x, b.min.x); result.min.y = DQN_MIN(a.min.y, b.min.y); result.max.x = DQN_MAX(a.max.x, b.max.x); result.max.y = DQN_MAX(a.max.y, b.max.y); return result; } Dqn_Rect Dqn_Rect_FromRectI32(Dqn_RectI32 a) { Dqn_Rect result = Dqn_Rect(a.min, a.max); return result; } Dqn_V2I Dqn_RectI32_Size(Dqn_RectI32 rect) { Dqn_V2I result = rect.max - rect.min; return result; } // ------------------------------------------------------------------------------------------------- // // NOTE: Math Utils // // ------------------------------------------------------------------------------------------------- Dqn_V2 Dqn_LerpV2(Dqn_V2 a, f32 t, Dqn_V2 b) { Dqn_V2 result = {}; result.x = a.x + ((b.x - a.x) * t); result.y = a.y + ((b.y - a.y) * t); return result; } // ------------------------------------------------------------------------------------------------- // // NOTE: Dqn_Mat4 // // ------------------------------------------------------------------------------------------------- Dqn_Mat4 Dqn_Mat4_Identity() { Dqn_Mat4 result = { 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, }; return result; } Dqn_Mat4 Dqn_Mat4_Scale3f(f32 x, f32 y, f32 z) { Dqn_Mat4 result = { x, 0, 0, 0, 0, y, 0, 0, 0, 0, z, 0, 0, 0, 0, 1, }; return result; } Dqn_Mat4 Dqn_Mat4_ScaleV3(Dqn_V3 vec) { Dqn_Mat4 result = Dqn_Mat4_Scale3f(vec.x, vec.y, vec.z); return result; } Dqn_Mat4 Dqn_Mat4_Translate3f(f32 x, f32 y, f32 z) { Dqn_Mat4 result = { 1, 0, 0, x, 0, 1, 0, y, 0, 0, 1, z, 0, 0, 0, 1, }; return result; } Dqn_Mat4 Dqn_Mat4_TranslateV3(Dqn_V3 vec) { Dqn_Mat4 result = Dqn_Mat4_Translate3f(vec.x, vec.y, vec.z); return result; } Dqn_Mat4 operator*(Dqn_Mat4 const &a, Dqn_Mat4 const &b) { Dqn_V4 const *row1 = a.row + 0; Dqn_V4 const *row2 = a.row + 1; Dqn_V4 const *row3 = a.row + 2; Dqn_V4 const *row4 = a.row + 3; Dqn_V4 const col1 = Dqn_V4(b.row_major[0][0], b.row_major[1][0], b.row_major[2][0], b.row_major[3][0]); Dqn_V4 const col2 = Dqn_V4(b.row_major[0][1], b.row_major[1][1], b.row_major[2][1], b.row_major[3][1]); Dqn_V4 const col3 = Dqn_V4(b.row_major[0][2], b.row_major[1][2], b.row_major[2][2], b.row_major[3][3]); Dqn_V4 const col4 = Dqn_V4(b.row_major[0][3], b.row_major[1][3], b.row_major[2][3], b.row_major[3][3]); Dqn_Mat4 result = { Dqn_V4_Dot(row1, &col1), Dqn_V4_Dot(row1, &col2), Dqn_V4_Dot(row1, &col3), Dqn_V4_Dot(row1, &col4), Dqn_V4_Dot(row2, &col1), Dqn_V4_Dot(row2, &col2), Dqn_V4_Dot(row2, &col3), Dqn_V4_Dot(row2, &col4), Dqn_V4_Dot(row3, &col1), Dqn_V4_Dot(row3, &col2), Dqn_V4_Dot(row3, &col3), Dqn_V4_Dot(row3, &col4), Dqn_V4_Dot(row4, &col1), Dqn_V4_Dot(row4, &col2), Dqn_V4_Dot(row4, &col3), Dqn_V4_Dot(row4, &col4), }; return result; } Dqn_V4 operator*(Dqn_Mat4 const &mat, Dqn_V4 const &vec) { f32 x = vec.x, y = vec.y, z = vec.z, w = vec.w; Dqn_V4 result = { (mat[0] * x) + (mat[1] * y) + (mat[2] * z) + (mat[3] * w), (mat[4] * x) + (mat[5] * y) + (mat[6] * z) + (mat[7] * w), (mat[8] * x) + (mat[9] * y) + (mat[10] * z) + (mat[11] * w), (mat[12] * x) + (mat[13] * y) + (mat[14] * z) + (mat[15] * w), }; return result; } // ------------------------------------------------------------------------------------------------- // // NOTE: Helper Functions // // ------------------------------------------------------------------------------------------------- void Dqn_BitUnsetInplace(u32 *flags, u32 bitfield) { *flags = (*flags & ~bitfield); } void Dqn_BitSetInplace(u32 *flags, u32 bitfield) { *flags = (*flags | bitfield); } b32 Dqn_BitIsSet(u32 flags, u32 bitfield) { b32 result = (flags & bitfield); return result; } b32 Dqn_BitIsNotSet(u32 flags, u32 bitfield) { b32 result = !(flags & bitfield); return result; } // ------------------------------------------------------------------------------------------------- // // NOTE: Safe Arithmetic // // ------------------------------------------------------------------------------------------------- i64 Dqn_SafeAddI64(i64 a, i64 b) { DQN_ASSERT_MSG(a <= INT64_MAX - b, "%zu <= %zu", a, INT64_MAX - b); i64 result = a + b; return result; } i64 Dqn_SafeMulI64(i64 a, i64 b) { DQN_ASSERT_MSG(a <= INT64_MAX / b , "%zu <= %zu", a, INT64_MAX / b); i64 result = a * b; return result; } u64 Dqn_SafeAddU64(u64 a, u64 b) { DQN_ASSERT_MSG(a <= UINT64_MAX - b, "%zu <= %zu", a, UINT64_MAX - b); u64 result = a + b; return result; } u64 Dqn_SafeMulU64(u64 a, u64 b) { DQN_ASSERT_MSG(a <= UINT64_MAX / b , "%zu <= %zu", a, UINT64_MAX / b); u64 result = a * b; return result; } // ------------------------------------------------------------------------------------------------- // // NOTE: SafeTruncate // // ------------------------------------------------------------------------------------------------- int Dqn_SafeTruncateISizeToInt(isize val) { DQN_ASSERT_MSG(val >= INT_MIN && val <= INT_MAX, "%zd >= %zd && %zd <= %zd", val, INT_MIN, val, INT_MAX); auto result = (int)val; return result; } i32 Dqn_SafeTruncateISizeToI32(isize val) { DQN_ASSERT_MSG(val >= INT32_MIN && val <= INT32_MAX, "%zd >= %zd && %zd <= %zd", val, INT32_MIN, val, INT32_MAX); auto result = (i32)val; return result; } i8 SafeTruncateISizeToI8(isize val) { DQN_ASSERT_MSG(val >= INT8_MIN && val <= INT8_MAX, "%zd >= %zd && %zd <= %zd", val, INT8_MIN, val, INT8_MAX); auto result = (i8)val; return result; } u32 Dqn_SafeTruncateUSizeToU32(u64 val) { DQN_ASSERT_MSG(val <= UINT32_MAX, "%zu <= %zu", val, UINT32_MAX); auto result = (u32)val; return result; } int Dqn_SafeTruncateUSizeToI32(usize val) { DQN_ASSERT_MSG(val <= INT32_MAX, "%zu <= %zd", val, INT32_MAX); auto result = (int)val; return result; } int Dqn_SafeTruncateUSizeToInt(usize val) { DQN_ASSERT_MSG(val <= INT_MAX, "%zu <= %zd", val, INT_MAX); auto result = (int)val; return result; } // ------------------------------------------------------------------------------------------------- // // NOTE: Char Helpers // // ------------------------------------------------------------------------------------------------- b32 Dqn_CharIsAlpha(char ch) { b32 result = (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z'); return result; } b32 Dqn_CharIsDigit(char ch) { b32 result = (ch >= '0' && ch <= '9'); return result; } b32 Dqn_CharIsAlphaNum(char ch) { b32 result = Dqn_CharIsAlpha(ch) || Dqn_CharIsDigit(ch); return result; } b32 Dqn_CharIsWhitespace(char ch) { b32 result = (ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r'); return result; } // ------------------------------------------------------------------------------------------------- // // NOTE: String Helpers // // ------------------------------------------------------------------------------------------------- b32 Dqn_StrCmp(char const *a, isize a_len, char const *b, isize b_len = -1) { if (b_len == -1) b_len = strlen(b); if (a_len != b_len) return false; return (strncmp(a, b, a_len) == 0); } b32 Dqn_StrCmp(Slice const a, Slice const b) { b32 result = Dqn_StrCmp(a.str, a.len, b.str, b.len); return result; } b32 Dqn_StrCmp(Slice const a, Slice const b) { b32 result = Dqn_StrCmp(a.str, a.len, b.str, b.len); return result; } char const *Dqn_StrFind(char const *src, int src_len, char const *find, int find_len) { if (find_len == -1) find_len = Dqn_SafeTruncateUSizeToInt(strlen(find)); char const *buf_ptr = src; char const *buf_end = buf_ptr + src_len; char const *result = nullptr; for (;*buf_ptr; ++buf_ptr) { int len_remaining = static_cast(buf_end - buf_ptr); if (len_remaining < find_len) break; if (strncmp(buf_ptr, find, find_len) == 0) { result = buf_ptr; break; } } return result; } b32 Dqn_StrMatch(char const *src, char const *find, int find_len) { if (find_len == -1) find_len = Dqn_SafeTruncateUSizeToInt(strlen(find)); b32 result = (strncmp(src, find, find_len) == 0); return result; } char const *Dqn_StrSkipToChar(char const *src, char ch) { char const *result = src; while (result && result[0] && result[0] != ch) ++result; return result; } char const *Dqn_StrSkipToNextAlphaNum(char const *src) { char const *result = src; while (result && result[0] && !Dqn_CharIsAlphaNum(result[0])) ++result; return result; } char const *Dqn_StrSkipToNextDigit(char const *src) { char const *result = src; while (result && result[0] && !Dqn_CharIsDigit(result[0])) ++result; return result; } char const *Dqn_StrSkipToNextChar(char const *src) { char const *result = src; while (result && result[0] && !Dqn_CharIsAlpha(result[0])) ++result; return result; } char const *Dqn_StrSkipToNextWord(char const *src) { char const *result = src; while (result && result[0] && !Dqn_CharIsWhitespace(result[0])) ++result; while (result && result[0] && Dqn_CharIsWhitespace(result[0])) ++result; return result; } char const *Dqn_StrSkipToNextWhitespace(char const *src) { char const *result = src; while (result && result[0] && !Dqn_CharIsWhitespace(result[0])) ++result; return result; } char const *Dqn_StrSkipWhitespace(char const *src) { char const *result = src; while (result && result[0] && Dqn_CharIsWhitespace(result[0])) ++result; return result; } char const *Dqn_StrSkipToCharInPlace(char const **src, char ch) { *src = Dqn_StrSkipToChar(*src, ch); return *src; } char const *Dqn_StrSkipToNextAlphaNumInPlace(char const **src) { *src = Dqn_StrSkipToNextAlphaNum(*src); return *src; } char const *Dqn_StrSkipToNextCharInPlace(char const **src) { *src = Dqn_StrSkipToNextChar(*src); return *src; } char const *Dqn_StrSkipToNextWhitespaceInPlace(char const **src) { *src = Dqn_StrSkipToNextWhitespace(*src); return *src; } char const *Dqn_StrSkipToNextWordInPlace(char const **src) { *src = Dqn_StrSkipToNextWord(*src); return *src; } char const *Dqn_StrSkipWhitespaceInPlace(char const **src) { *src = Dqn_StrSkipWhitespace(*src); return *src; } u64 Dqn_StrToU64(char const *buf, int len = -1) { u64 result = 0; if (!buf) return result; if (len == -1) len = Dqn_SafeTruncateUSizeToInt(strlen(buf)); if (len == 0) return result; char const *buf_ptr = Dqn_StrSkipWhitespace(buf); len -= static_cast(buf_ptr - buf); for (int buf_index = 0; buf_index < len; ++buf_index) { char ch = buf_ptr[buf_index]; if (ch == ',') continue; if (ch < '0' || ch > '9') break; u64 val = ch - '0'; result = Dqn_SafeAddU64(result, val); result = Dqn_SafeMulU64(result, 10); } result /= 10; return result; } i64 Dqn_StrToI64(char const *buf, int len = -1) { i64 result = 0; if (!buf) return result; if (len == -1) len = Dqn_SafeTruncateUSizeToInt(strlen(buf)); if (len == 0) return result; char const *buf_ptr = Dqn_StrSkipWhitespace(buf); len -= static_cast(buf_ptr - buf); b32 negative = (buf[0] == '-'); if (negative) { ++buf_ptr; --len; } for (int buf_index = 0; buf_index < len; ++buf_index) { char ch = buf_ptr[buf_index]; if (ch == ',') continue; if (ch < '0' || ch > '9') break; i64 val = ch - '0'; result = Dqn_SafeAddI64(result, val); result = Dqn_SafeMulI64(result, 10); } result /= 10; if (negative) result *= -1; return result; } #endif // DQN_IMPLEMENTATION #ifdef STB_SPRINTF_IMPLEMENTATION #include // for va_arg() #define stbsp__uint32 unsigned int #define stbsp__int32 signed int #ifdef _MSC_VER #define stbsp__uint64 unsigned __int64 #define stbsp__int64 signed __int64 #else #define stbsp__uint64 unsigned long long #define stbsp__int64 signed long long #endif #define stbsp__uint16 unsigned short #ifndef stbsp__uintptr #if defined(__ppc64__) || defined(__aarch64__) || defined(_M_X64) || defined(__x86_64__) || defined(__x86_64) #define stbsp__uintptr stbsp__uint64 #else #define stbsp__uintptr stbsp__uint32 #endif #endif #ifndef STB_SPRINTF_MSVC_MODE // used for MSVC2013 and earlier (MSVC2015 matches GCC) #if defined(_MSC_VER) && (_MSC_VER < 1900) #define STB_SPRINTF_MSVC_MODE #endif #endif #ifdef STB_SPRINTF_NOUNALIGNED // define this before inclusion to force stbsp_sprintf to always use aligned accesses #define STBSP__UNALIGNED(code) #else #define STBSP__UNALIGNED(code) code #endif #ifndef STB_SPRINTF_NOFLOAT // internal float utility functions static stbsp__int32 stbsp__real_to_str(char const **start, stbsp__uint32 *len, char *out, stbsp__int32 *decimal_pos, double value, stbsp__uint32 frac_digits); static stbsp__int32 stbsp__real_to_parts(stbsp__int64 *bits, stbsp__int32 *expo, double value); #define STBSP__SPECIAL 0x7000 #endif static char stbsp__period = '.'; static char stbsp__comma = ','; static char stbsp__digitpair[201] = "0001020304050607080910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576" "7778798081828384858687888990919293949596979899"; STBSP__PUBLICDEF void STB_SPRINTF_DECORATE(set_separators)(char pcomma, char pperiod) { stbsp__period = pperiod; stbsp__comma = pcomma; } #define STBSP__LEFTJUST 1 #define STBSP__LEADINGPLUS 2 #define STBSP__LEADINGSPACE 4 #define STBSP__LEADING_0X 8 #define STBSP__LEADINGZERO 16 #define STBSP__INTMAX 32 #define STBSP__TRIPLET_COMMA 64 #define STBSP__NEGATIVE 128 #define STBSP__METRIC_SUFFIX 256 #define STBSP__HALFWIDTH 512 #define STBSP__METRIC_NOSPACE 1024 #define STBSP__METRIC_1024 2048 #define STBSP__METRIC_JEDEC 4096 static void stbsp__lead_sign(stbsp__uint32 fl, char *sign) { sign[0] = 0; if (fl & STBSP__NEGATIVE) { sign[0] = 1; sign[1] = '-'; } else if (fl & STBSP__LEADINGSPACE) { sign[0] = 1; sign[1] = ' '; } else if (fl & STBSP__LEADINGPLUS) { sign[0] = 1; sign[1] = '+'; } } STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(vsprintfcb)(STBSP_SPRINTFCB *callback, void *user, char *buf, char const *fmt, va_list va) { static char hex[] = "0123456789abcdefxp"; static char hexu[] = "0123456789ABCDEFXP"; char *bf; char const *f; int tlen = 0; bf = buf; f = fmt; for (;;) { stbsp__int32 fw, pr, tz; stbsp__uint32 fl; // macros for the callback buffer stuff #define stbsp__chk_cb_bufL(bytes) \ { \ int len = (int)(bf - buf); \ if ((len + (bytes)) >= STB_SPRINTF_MIN) { \ tlen += len; \ if (0 == (bf = buf = callback(buf, user, len))) \ goto done; \ } \ } #define stbsp__chk_cb_buf(bytes) \ { \ if (callback) { \ stbsp__chk_cb_bufL(bytes); \ } \ } #define stbsp__flush_cb() \ { \ stbsp__chk_cb_bufL(STB_SPRINTF_MIN - 1); \ } // flush if there is even one byte in the buffer #define stbsp__cb_buf_clamp(cl, v) \ cl = v; \ if (callback) { \ int lg = STB_SPRINTF_MIN - (int)(bf - buf); \ if (cl > lg) \ cl = lg; \ } // fast copy everything up to the next % (or end of string) for (;;) { while (((stbsp__uintptr)f) & 3) { schk1: if (f[0] == '%') goto scandd; schk2: if (f[0] == 0) goto endfmt; stbsp__chk_cb_buf(1); *bf++ = f[0]; ++f; } for (;;) { // Check if the next 4 bytes contain %(0x25) or end of string. // Using the 'hasless' trick: // https://graphics.stanford.edu/~seander/bithacks.html#HasLessInWord stbsp__uint32 v, c; v = *(stbsp__uint32 *)f; c = (~v) & 0x80808080; if (((v ^ 0x25252525) - 0x01010101) & c) goto schk1; if ((v - 0x01010101) & c) goto schk2; if (callback) if ((STB_SPRINTF_MIN - (int)(bf - buf)) < 4) goto schk1; *(stbsp__uint32 *)bf = v; bf += 4; f += 4; } } scandd: ++f; // ok, we have a percent, read the modifiers first fw = 0; pr = -1; fl = 0; tz = 0; // flags for (;;) { switch (f[0]) { // if we have left justify case '-': fl |= STBSP__LEFTJUST; ++f; continue; // if we have leading plus case '+': fl |= STBSP__LEADINGPLUS; ++f; continue; // if we have leading space case ' ': fl |= STBSP__LEADINGSPACE; ++f; continue; // if we have leading 0x case '#': fl |= STBSP__LEADING_0X; ++f; continue; // if we have thousand commas case '\'': fl |= STBSP__TRIPLET_COMMA; ++f; continue; // if we have kilo marker (none->kilo->kibi->jedec) case '$': if (fl & STBSP__METRIC_SUFFIX) { if (fl & STBSP__METRIC_1024) { fl |= STBSP__METRIC_JEDEC; } else { fl |= STBSP__METRIC_1024; } } else { fl |= STBSP__METRIC_SUFFIX; } ++f; continue; // if we don't want space between metric suffix and number case '_': fl |= STBSP__METRIC_NOSPACE; ++f; continue; // if we have leading zero case '0': fl |= STBSP__LEADINGZERO; ++f; goto flags_done; default: goto flags_done; } } flags_done: // get the field width if (f[0] == '*') { fw = va_arg(va, stbsp__uint32); ++f; } else { while ((f[0] >= '0') && (f[0] <= '9')) { fw = fw * 10 + f[0] - '0'; f++; } } // get the precision if (f[0] == '.') { ++f; if (f[0] == '*') { pr = va_arg(va, stbsp__uint32); ++f; } else { pr = 0; while ((f[0] >= '0') && (f[0] <= '9')) { pr = pr * 10 + f[0] - '0'; f++; } } } // handle integer size overrides switch (f[0]) { // are we halfwidth? case 'h': fl |= STBSP__HALFWIDTH; ++f; break; // are we 64-bit (unix style) case 'l': ++f; if (f[0] == 'l') { fl |= STBSP__INTMAX; ++f; } break; // are we 64-bit on intmax? (c99) case 'j': fl |= STBSP__INTMAX; ++f; break; // are we 64-bit on size_t or ptrdiff_t? (c99) case 'z': case 't': fl |= ((sizeof(char *) == 8) ? STBSP__INTMAX : 0); ++f; break; // are we 64-bit (msft style) case 'I': if ((f[1] == '6') && (f[2] == '4')) { fl |= STBSP__INTMAX; f += 3; } else if ((f[1] == '3') && (f[2] == '2')) { f += 3; } else { fl |= ((sizeof(void *) == 8) ? STBSP__INTMAX : 0); ++f; } break; default: break; } // handle each replacement switch (f[0]) { #define STBSP__NUMSZ 512 // big enough for e308 (with commas) or e-307 char num[STBSP__NUMSZ]; char lead[8]; char tail[8]; char *s; char const *h; stbsp__uint32 l, n, cs; stbsp__uint64 n64; #ifndef STB_SPRINTF_NOFLOAT double fv; #endif stbsp__int32 dp; char const *sn; case 's': // get the string s = va_arg(va, char *); if (s == 0) s = (char *)"null"; // get the length sn = s; for (;;) { if ((((stbsp__uintptr)sn) & 3) == 0) break; lchk: if (sn[0] == 0) goto ld; ++sn; } n = 0xffffffff; if (pr >= 0) { n = (stbsp__uint32)(sn - s); if (n >= (stbsp__uint32)pr) goto ld; n = ((stbsp__uint32)(pr - n)) >> 2; } while (n) { stbsp__uint32 v = *(stbsp__uint32 *)sn; if ((v - 0x01010101) & (~v) & 0x80808080UL) goto lchk; sn += 4; --n; } goto lchk; ld: l = (stbsp__uint32)(sn - s); // clamp to precision if (l > (stbsp__uint32)pr) l = pr; lead[0] = 0; tail[0] = 0; pr = 0; dp = 0; cs = 0; // copy the string in goto scopy; case 'c': // char // get the character s = num + STBSP__NUMSZ - 1; *s = (char)va_arg(va, int); l = 1; lead[0] = 0; tail[0] = 0; pr = 0; dp = 0; cs = 0; goto scopy; case 'n': // weird write-bytes specifier { int *d = va_arg(va, int *); *d = tlen + (int)(bf - buf); } break; #ifdef STB_SPRINTF_NOFLOAT case 'A': // float case 'a': // hex float case 'G': // float case 'g': // float case 'E': // float case 'e': // float case 'f': // float va_arg(va, double); // eat it s = (char *)"No float"; l = 8; lead[0] = 0; tail[0] = 0; pr = 0; dp = 0; cs = 0; goto scopy; #else case 'A': // hex float case 'a': // hex float h = (f[0] == 'A') ? hexu : hex; fv = va_arg(va, double); if (pr == -1) pr = 6; // default is 6 // read the double into a string if (stbsp__real_to_parts((stbsp__int64 *)&n64, &dp, fv)) fl |= STBSP__NEGATIVE; s = num + 64; stbsp__lead_sign(fl, lead); if (dp == -1023) dp = (n64) ? -1022 : 0; else n64 |= (((stbsp__uint64)1) << 52); n64 <<= (64 - 56); if (pr < 15) n64 += ((((stbsp__uint64)8) << 56) >> (pr * 4)); // add leading chars #ifdef STB_SPRINTF_MSVC_MODE *s++ = '0'; *s++ = 'x'; #else lead[1 + lead[0]] = '0'; lead[2 + lead[0]] = 'x'; lead[0] += 2; #endif *s++ = h[(n64 >> 60) & 15]; n64 <<= 4; if (pr) *s++ = stbsp__period; sn = s; // print the bits n = pr; if (n > 13) n = 13; if (pr > (stbsp__int32)n) tz = pr - n; pr = 0; while (n--) { *s++ = h[(n64 >> 60) & 15]; n64 <<= 4; } // print the expo tail[1] = h[17]; if (dp < 0) { tail[2] = '-'; dp = -dp; } else tail[2] = '+'; n = (dp >= 1000) ? 6 : ((dp >= 100) ? 5 : ((dp >= 10) ? 4 : 3)); tail[0] = (char)n; for (;;) { tail[n] = '0' + dp % 10; if (n <= 3) break; --n; dp /= 10; } dp = (int)(s - sn); l = (int)(s - (num + 64)); s = num + 64; cs = 1 + (3 << 24); goto scopy; case 'G': // float case 'g': // float h = (f[0] == 'G') ? hexu : hex; fv = va_arg(va, double); if (pr == -1) pr = 6; else if (pr == 0) pr = 1; // default is 6 // read the double into a string if (stbsp__real_to_str(&sn, &l, num, &dp, fv, (pr - 1) | 0x80000000)) fl |= STBSP__NEGATIVE; // clamp the precision and delete extra zeros after clamp n = pr; if (l > (stbsp__uint32)pr) l = pr; while ((l > 1) && (pr) && (sn[l - 1] == '0')) { --pr; --l; } // should we use %e if ((dp <= -4) || (dp > (stbsp__int32)n)) { if (pr > (stbsp__int32)l) pr = l - 1; else if (pr) --pr; // when using %e, there is one digit before the decimal goto doexpfromg; } // this is the insane action to get the pr to match %g sematics for %f if (dp > 0) { pr = (dp < (stbsp__int32)l) ? l - dp : 0; } else { pr = -dp + ((pr > (stbsp__int32)l) ? l : pr); } goto dofloatfromg; case 'E': // float case 'e': // float h = (f[0] == 'E') ? hexu : hex; fv = va_arg(va, double); if (pr == -1) pr = 6; // default is 6 // read the double into a string if (stbsp__real_to_str(&sn, &l, num, &dp, fv, pr | 0x80000000)) fl |= STBSP__NEGATIVE; doexpfromg: tail[0] = 0; stbsp__lead_sign(fl, lead); if (dp == STBSP__SPECIAL) { s = (char *)sn; cs = 0; pr = 0; goto scopy; } s = num + 64; // handle leading chars *s++ = sn[0]; if (pr) *s++ = stbsp__period; // handle after decimal if ((l - 1) > (stbsp__uint32)pr) l = pr + 1; for (n = 1; n < l; n++) *s++ = sn[n]; // trailing zeros tz = pr - (l - 1); pr = 0; // dump expo tail[1] = h[0xe]; dp -= 1; if (dp < 0) { tail[2] = '-'; dp = -dp; } else tail[2] = '+'; #ifdef STB_SPRINTF_MSVC_MODE n = 5; #else n = (dp >= 100) ? 5 : 4; #endif tail[0] = (char)n; for (;;) { tail[n] = '0' + dp % 10; if (n <= 3) break; --n; dp /= 10; } cs = 1 + (3 << 24); // how many tens goto flt_lead; case 'f': // float fv = va_arg(va, double); doafloat: // do kilos if (fl & STBSP__METRIC_SUFFIX) { double divisor; divisor = 1000.0f; if (fl & STBSP__METRIC_1024) divisor = 1024.0; while (fl < 0x4000000) { if ((fv < divisor) && (fv > -divisor)) break; fv /= divisor; fl += 0x1000000; } } if (pr == -1) pr = 6; // default is 6 // read the double into a string if (stbsp__real_to_str(&sn, &l, num, &dp, fv, pr)) fl |= STBSP__NEGATIVE; dofloatfromg: tail[0] = 0; stbsp__lead_sign(fl, lead); if (dp == STBSP__SPECIAL) { s = (char *)sn; cs = 0; pr = 0; goto scopy; } s = num + 64; // handle the three decimal varieties if (dp <= 0) { stbsp__int32 i; // handle 0.000*000xxxx *s++ = '0'; if (pr) *s++ = stbsp__period; n = -dp; if ((stbsp__int32)n > pr) n = pr; i = n; while (i) { if ((((stbsp__uintptr)s) & 3) == 0) break; *s++ = '0'; --i; } while (i >= 4) { *(stbsp__uint32 *)s = 0x30303030; s += 4; i -= 4; } while (i) { *s++ = '0'; --i; } if ((stbsp__int32)(l + n) > pr) l = pr - n; i = l; while (i) { *s++ = *sn++; --i; } tz = pr - (n + l); cs = 1 + (3 << 24); // how many tens did we write (for commas below) } else { cs = (fl & STBSP__TRIPLET_COMMA) ? ((600 - (stbsp__uint32)dp) % 3) : 0; if ((stbsp__uint32)dp >= l) { // handle xxxx000*000.0 n = 0; for (;;) { if ((fl & STBSP__TRIPLET_COMMA) && (++cs == 4)) { cs = 0; *s++ = stbsp__comma; } else { *s++ = sn[n]; ++n; if (n >= l) break; } } if (n < (stbsp__uint32)dp) { n = dp - n; if ((fl & STBSP__TRIPLET_COMMA) == 0) { while (n) { if ((((stbsp__uintptr)s) & 3) == 0) break; *s++ = '0'; --n; } while (n >= 4) { *(stbsp__uint32 *)s = 0x30303030; s += 4; n -= 4; } } while (n) { if ((fl & STBSP__TRIPLET_COMMA) && (++cs == 4)) { cs = 0; *s++ = stbsp__comma; } else { *s++ = '0'; --n; } } } cs = (int)(s - (num + 64)) + (3 << 24); // cs is how many tens if (pr) { *s++ = stbsp__period; tz = pr; } } else { // handle xxxxx.xxxx000*000 n = 0; for (;;) { if ((fl & STBSP__TRIPLET_COMMA) && (++cs == 4)) { cs = 0; *s++ = stbsp__comma; } else { *s++ = sn[n]; ++n; if (n >= (stbsp__uint32)dp) break; } } cs = (int)(s - (num + 64)) + (3 << 24); // cs is how many tens if (pr) *s++ = stbsp__period; if ((l - dp) > (stbsp__uint32)pr) l = pr + dp; while (n < l) { *s++ = sn[n]; ++n; } tz = pr - (l - dp); } } pr = 0; // handle k,m,g,t if (fl & STBSP__METRIC_SUFFIX) { char idx; idx = 1; if (fl & STBSP__METRIC_NOSPACE) idx = 0; tail[0] = idx; tail[1] = ' '; { if (fl >> 24) { // SI kilo is 'k', JEDEC and SI kibits are 'K'. if (fl & STBSP__METRIC_1024) tail[idx + 1] = "_KMGT"[fl >> 24]; else tail[idx + 1] = "_kMGT"[fl >> 24]; idx++; // If printing kibits and not in jedec, add the 'i'. if (fl & STBSP__METRIC_1024 && !(fl & STBSP__METRIC_JEDEC)) { tail[idx + 1] = 'i'; idx++; } tail[0] = idx; } } }; flt_lead: // get the length that we copied l = (stbsp__uint32)(s - (num + 64)); s = num + 64; goto scopy; #endif case 'B': // upper binary case 'b': // lower binary h = (f[0] == 'B') ? hexu : hex; lead[0] = 0; if (fl & STBSP__LEADING_0X) { lead[0] = 2; lead[1] = '0'; lead[2] = h[0xb]; } l = (8 << 4) | (1 << 8); goto radixnum; case 'o': // octal h = hexu; lead[0] = 0; if (fl & STBSP__LEADING_0X) { lead[0] = 1; lead[1] = '0'; } l = (3 << 4) | (3 << 8); goto radixnum; case 'p': // pointer fl |= (sizeof(void *) == 8) ? STBSP__INTMAX : 0; pr = sizeof(void *) * 2; fl &= ~STBSP__LEADINGZERO; // 'p' only prints the pointer with zeros // fall through - to X case 'X': // upper hex case 'x': // lower hex h = (f[0] == 'X') ? hexu : hex; l = (4 << 4) | (4 << 8); lead[0] = 0; if (fl & STBSP__LEADING_0X) { lead[0] = 2; lead[1] = '0'; lead[2] = h[16]; } radixnum: // get the number if (fl & STBSP__INTMAX) n64 = va_arg(va, stbsp__uint64); else n64 = va_arg(va, stbsp__uint32); s = num + STBSP__NUMSZ; dp = 0; // clear tail, and clear leading if value is zero tail[0] = 0; if (n64 == 0) { lead[0] = 0; if (pr == 0) { l = 0; cs = (((l >> 4) & 15)) << 24; goto scopy; } } // convert to string for (;;) { *--s = h[n64 & ((1 << (l >> 8)) - 1)]; n64 >>= (l >> 8); if (!((n64) || ((stbsp__int32)((num + STBSP__NUMSZ) - s) < pr))) break; if (fl & STBSP__TRIPLET_COMMA) { ++l; if ((l & 15) == ((l >> 4) & 15)) { l &= ~15; *--s = stbsp__comma; } } }; // get the tens and the comma pos cs = (stbsp__uint32)((num + STBSP__NUMSZ) - s) + ((((l >> 4) & 15)) << 24); // get the length that we copied l = (stbsp__uint32)((num + STBSP__NUMSZ) - s); // copy it goto scopy; case 'u': // unsigned case 'i': case 'd': // integer // get the integer and abs it if (fl & STBSP__INTMAX) { stbsp__int64 i64 = va_arg(va, stbsp__int64); n64 = (stbsp__uint64)i64; if ((f[0] != 'u') && (i64 < 0)) { n64 = (stbsp__uint64)-i64; fl |= STBSP__NEGATIVE; } } else { stbsp__int32 i = va_arg(va, stbsp__int32); n64 = (stbsp__uint32)i; if ((f[0] != 'u') && (i < 0)) { n64 = (stbsp__uint32)-i; fl |= STBSP__NEGATIVE; } } #ifndef STB_SPRINTF_NOFLOAT if (fl & STBSP__METRIC_SUFFIX) { if (n64 < 1024) pr = 0; else if (pr == -1) pr = 1; fv = (double)(stbsp__int64)n64; goto doafloat; } #endif // convert to string s = num + STBSP__NUMSZ; l = 0; for (;;) { // do in 32-bit chunks (avoid lots of 64-bit divides even with constant denominators) char *o = s - 8; if (n64 >= 100000000) { n = (stbsp__uint32)(n64 % 100000000); n64 /= 100000000; } else { n = (stbsp__uint32)n64; n64 = 0; } if ((fl & STBSP__TRIPLET_COMMA) == 0) { do { s -= 2; *(stbsp__uint16 *)s = *(stbsp__uint16 *)&stbsp__digitpair[(n % 100) * 2]; n /= 100; } while (n); } while (n) { if ((fl & STBSP__TRIPLET_COMMA) && (l++ == 3)) { l = 0; *--s = stbsp__comma; --o; } else { *--s = (char)(n % 10) + '0'; n /= 10; } } if (n64 == 0) { if ((s[0] == '0') && (s != (num + STBSP__NUMSZ))) ++s; break; } while (s != o) if ((fl & STBSP__TRIPLET_COMMA) && (l++ == 3)) { l = 0; *--s = stbsp__comma; --o; } else { *--s = '0'; } } tail[0] = 0; stbsp__lead_sign(fl, lead); // get the length that we copied l = (stbsp__uint32)((num + STBSP__NUMSZ) - s); if (l == 0) { *--s = '0'; l = 1; } cs = l + (3 << 24); if (pr < 0) pr = 0; scopy: // get fw=leading/trailing space, pr=leading zeros if (pr < (stbsp__int32)l) pr = l; n = pr + lead[0] + tail[0] + tz; if (fw < (stbsp__int32)n) fw = n; fw -= n; pr -= l; // handle right justify and leading zeros if ((fl & STBSP__LEFTJUST) == 0) { if (fl & STBSP__LEADINGZERO) // if leading zeros, everything is in pr { pr = (fw > pr) ? fw : pr; fw = 0; } else { fl &= ~STBSP__TRIPLET_COMMA; // if no leading zeros, then no commas } } // copy the spaces and/or zeros if (fw + pr) { stbsp__int32 i; stbsp__uint32 c; // copy leading spaces (or when doing %8.4d stuff) if ((fl & STBSP__LEFTJUST) == 0) while (fw > 0) { stbsp__cb_buf_clamp(i, fw); fw -= i; while (i) { if ((((stbsp__uintptr)bf) & 3) == 0) break; *bf++ = ' '; --i; } while (i >= 4) { *(stbsp__uint32 *)bf = 0x20202020; bf += 4; i -= 4; } while (i) { *bf++ = ' '; --i; } stbsp__chk_cb_buf(1); } // copy leader sn = lead + 1; while (lead[0]) { stbsp__cb_buf_clamp(i, lead[0]); lead[0] -= (char)i; while (i) { *bf++ = *sn++; --i; } stbsp__chk_cb_buf(1); } // copy leading zeros c = cs >> 24; cs &= 0xffffff; cs = (fl & STBSP__TRIPLET_COMMA) ? ((stbsp__uint32)(c - ((pr + cs) % (c + 1)))) : 0; while (pr > 0) { stbsp__cb_buf_clamp(i, pr); pr -= i; if ((fl & STBSP__TRIPLET_COMMA) == 0) { while (i) { if ((((stbsp__uintptr)bf) & 3) == 0) break; *bf++ = '0'; --i; } while (i >= 4) { *(stbsp__uint32 *)bf = 0x30303030; bf += 4; i -= 4; } } while (i) { if ((fl & STBSP__TRIPLET_COMMA) && (cs++ == c)) { cs = 0; *bf++ = stbsp__comma; } else *bf++ = '0'; --i; } stbsp__chk_cb_buf(1); } } // copy leader if there is still one sn = lead + 1; while (lead[0]) { stbsp__int32 i; stbsp__cb_buf_clamp(i, lead[0]); lead[0] -= (char)i; while (i) { *bf++ = *sn++; --i; } stbsp__chk_cb_buf(1); } // copy the string n = l; while (n) { stbsp__int32 i; stbsp__cb_buf_clamp(i, n); n -= i; STBSP__UNALIGNED(while (i >= 4) { *(stbsp__uint32 *)bf = *(stbsp__uint32 *)s; bf += 4; s += 4; i -= 4; }) while (i) { *bf++ = *s++; --i; } stbsp__chk_cb_buf(1); } // copy trailing zeros while (tz) { stbsp__int32 i; stbsp__cb_buf_clamp(i, tz); tz -= i; while (i) { if ((((stbsp__uintptr)bf) & 3) == 0) break; *bf++ = '0'; --i; } while (i >= 4) { *(stbsp__uint32 *)bf = 0x30303030; bf += 4; i -= 4; } while (i) { *bf++ = '0'; --i; } stbsp__chk_cb_buf(1); } // copy tail if there is one sn = tail + 1; while (tail[0]) { stbsp__int32 i; stbsp__cb_buf_clamp(i, tail[0]); tail[0] -= (char)i; while (i) { *bf++ = *sn++; --i; } stbsp__chk_cb_buf(1); } // handle the left justify if (fl & STBSP__LEFTJUST) if (fw > 0) { while (fw) { stbsp__int32 i; stbsp__cb_buf_clamp(i, fw); fw -= i; while (i) { if ((((stbsp__uintptr)bf) & 3) == 0) break; *bf++ = ' '; --i; } while (i >= 4) { *(stbsp__uint32 *)bf = 0x20202020; bf += 4; i -= 4; } while (i--) *bf++ = ' '; stbsp__chk_cb_buf(1); } } break; default: // unknown, just copy code s = num + STBSP__NUMSZ - 1; *s = f[0]; l = 1; fw = fl = 0; lead[0] = 0; tail[0] = 0; pr = 0; dp = 0; cs = 0; goto scopy; } ++f; } endfmt: if (!callback) *bf = 0; else stbsp__flush_cb(); done: return tlen + (int)(bf - buf); } // cleanup #undef STBSP__LEFTJUST #undef STBSP__LEADINGPLUS #undef STBSP__LEADINGSPACE #undef STBSP__LEADING_0X #undef STBSP__LEADINGZERO #undef STBSP__INTMAX #undef STBSP__TRIPLET_COMMA #undef STBSP__NEGATIVE #undef STBSP__METRIC_SUFFIX #undef STBSP__NUMSZ #undef stbsp__chk_cb_bufL #undef stbsp__chk_cb_buf #undef stbsp__flush_cb #undef stbsp__cb_buf_clamp // ============================================================================ // wrapper functions STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(sprintf)(char *buf, char const *fmt, ...) { int result; va_list va; va_start(va, fmt); result = STB_SPRINTF_DECORATE(vsprintfcb)(0, 0, buf, fmt, va); va_end(va); return result; } typedef struct stbsp__context { char *buf; int count; char tmp[STB_SPRINTF_MIN]; } stbsp__context; static char *stbsp__clamp_callback(char *buf, void *user, int len) { stbsp__context *c = (stbsp__context *)user; if (len > c->count) len = c->count; if (len) { if (buf != c->buf) { char *s, *d, *se; d = c->buf; s = buf; se = buf + len; do { *d++ = *s++; } while (s < se); } c->buf += len; c->count -= len; } if (c->count <= 0) return 0; return (c->count >= STB_SPRINTF_MIN) ? c->buf : c->tmp; // go direct into buffer if you can } static char * stbsp__count_clamp_callback( char * buf, void * user, int len ) { (void)buf; stbsp__context * c = (stbsp__context*)user; c->count += len; return c->tmp; // go direct into buffer if you can } STBSP__PUBLICDEF int STB_SPRINTF_DECORATE( vsnprintf )( char * buf, int count, char const * fmt, va_list va ) { stbsp__context c; int l; if ( (count == 0) && !buf ) { c.count = 0; STB_SPRINTF_DECORATE( vsprintfcb )( stbsp__count_clamp_callback, &c, c.tmp, fmt, va ); l = c.count; } else { if ( count == 0 ) return 0; c.buf = buf; c.count = count; STB_SPRINTF_DECORATE( vsprintfcb )( stbsp__clamp_callback, &c, stbsp__clamp_callback(0,&c,0), fmt, va ); // zero-terminate l = (int)( c.buf - buf ); if ( l >= count ) // should never be greater, only equal (or less) than count l = count - 1; buf[l] = 0; } return l; } STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(snprintf)(char *buf, int count, char const *fmt, ...) { int result; va_list va; va_start(va, fmt); result = STB_SPRINTF_DECORATE(vsnprintf)(buf, count, fmt, va); va_end(va); return result; } STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(vsprintf)(char *buf, char const *fmt, va_list va) { return STB_SPRINTF_DECORATE(vsprintfcb)(0, 0, buf, fmt, va); } // ======================================================================= // low level float utility functions #ifndef STB_SPRINTF_NOFLOAT // copies d to bits w/ strict aliasing (this compiles to nothing on /Ox) #define STBSP__COPYFP(dest, src) \ { \ int cn; \ for (cn = 0; cn < 8; cn++) \ ((char *)&dest)[cn] = ((char *)&src)[cn]; \ } // get float info static stbsp__int32 stbsp__real_to_parts(stbsp__int64 *bits, stbsp__int32 *expo, double value) { double d; stbsp__int64 b = 0; // load value and round at the frac_digits d = value; STBSP__COPYFP(b, d); *bits = b & ((((stbsp__uint64)1) << 52) - 1); *expo = (stbsp__int32)(((b >> 52) & 2047) - 1023); return (stbsp__int32)(b >> 63); } static double const stbsp__bot[23] = { 1e+000, 1e+001, 1e+002, 1e+003, 1e+004, 1e+005, 1e+006, 1e+007, 1e+008, 1e+009, 1e+010, 1e+011, 1e+012, 1e+013, 1e+014, 1e+015, 1e+016, 1e+017, 1e+018, 1e+019, 1e+020, 1e+021, 1e+022 }; static double const stbsp__negbot[22] = { 1e-001, 1e-002, 1e-003, 1e-004, 1e-005, 1e-006, 1e-007, 1e-008, 1e-009, 1e-010, 1e-011, 1e-012, 1e-013, 1e-014, 1e-015, 1e-016, 1e-017, 1e-018, 1e-019, 1e-020, 1e-021, 1e-022 }; static double const stbsp__negboterr[22] = { -5.551115123125783e-018, -2.0816681711721684e-019, -2.0816681711721686e-020, -4.7921736023859299e-021, -8.1803053914031305e-022, 4.5251888174113741e-023, 4.5251888174113739e-024, -2.0922560830128471e-025, -6.2281591457779853e-026, -3.6432197315497743e-027, 6.0503030718060191e-028, 2.0113352370744385e-029, -3.0373745563400371e-030, 1.1806906454401013e-032, -7.7705399876661076e-032, 2.0902213275965398e-033, -7.1542424054621921e-034, -7.1542424054621926e-035, 2.4754073164739869e-036, 5.4846728545790429e-037, 9.2462547772103625e-038, -4.8596774326570872e-039 }; static double const stbsp__top[13] = { 1e+023, 1e+046, 1e+069, 1e+092, 1e+115, 1e+138, 1e+161, 1e+184, 1e+207, 1e+230, 1e+253, 1e+276, 1e+299 }; static double const stbsp__negtop[13] = { 1e-023, 1e-046, 1e-069, 1e-092, 1e-115, 1e-138, 1e-161, 1e-184, 1e-207, 1e-230, 1e-253, 1e-276, 1e-299 }; static double const stbsp__toperr[13] = { 8388608, 6.8601809640529717e+028, -7.253143638152921e+052, -4.3377296974619174e+075, -1.5559416129466825e+098, -3.2841562489204913e+121, -3.7745893248228135e+144, -1.7356668416969134e+167, -3.8893577551088374e+190, -9.9566444326005119e+213, 6.3641293062232429e+236, -5.2069140800249813e+259, -5.2504760255204387e+282 }; static double const stbsp__negtoperr[13] = { 3.9565301985100693e-040, -2.299904345391321e-063, 3.6506201437945798e-086, 1.1875228833981544e-109, -5.0644902316928607e-132, -6.7156837247865426e-155, -2.812077463003139e-178, -5.7778912386589953e-201, 7.4997100559334532e-224, -4.6439668915134491e-247, -6.3691100762962136e-270, -9.436808465446358e-293, 8.0970921678014997e-317 }; #if defined(_MSC_VER) && (_MSC_VER <= 1200) static stbsp__uint64 const stbsp__powten[20] = { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000, 10000000000, 100000000000, 1000000000000, 10000000000000, 100000000000000, 1000000000000000, 10000000000000000, 100000000000000000, 1000000000000000000, 10000000000000000000U }; #define stbsp__tento19th ((stbsp__uint64)1000000000000000000) #else static stbsp__uint64 const stbsp__powten[20] = { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000, 10000000000ULL, 100000000000ULL, 1000000000000ULL, 10000000000000ULL, 100000000000000ULL, 1000000000000000ULL, 10000000000000000ULL, 100000000000000000ULL, 1000000000000000000ULL, 10000000000000000000ULL }; #define stbsp__tento19th (1000000000000000000ULL) #endif #define stbsp__ddmulthi(oh, ol, xh, yh) \ { \ double ahi = 0, alo, bhi = 0, blo; \ stbsp__int64 bt; \ oh = xh * yh; \ STBSP__COPYFP(bt, xh); \ bt &= ((~(stbsp__uint64)0) << 27); \ STBSP__COPYFP(ahi, bt); \ alo = xh - ahi; \ STBSP__COPYFP(bt, yh); \ bt &= ((~(stbsp__uint64)0) << 27); \ STBSP__COPYFP(bhi, bt); \ blo = yh - bhi; \ ol = ((ahi * bhi - oh) + ahi * blo + alo * bhi) + alo * blo; \ } #define stbsp__ddtoS64(ob, xh, xl) \ { \ double ahi = 0, alo, vh, t; \ ob = (stbsp__int64)ph; \ vh = (double)ob; \ ahi = (xh - vh); \ t = (ahi - xh); \ alo = (xh - (ahi - t)) - (vh + t); \ ob += (stbsp__int64)(ahi + alo + xl); \ } #define stbsp__ddrenorm(oh, ol) \ { \ double s; \ s = oh + ol; \ ol = ol - (s - oh); \ oh = s; \ } #define stbsp__ddmultlo(oh, ol, xh, xl, yh, yl) ol = ol + (xh * yl + xl * yh); #define stbsp__ddmultlos(oh, ol, xh, yl) ol = ol + (xh * yl); static void stbsp__raise_to_power10(double *ohi, double *olo, double d, stbsp__int32 power) // power can be -323 to +350 { double ph, pl; if ((power >= 0) && (power <= 22)) { stbsp__ddmulthi(ph, pl, d, stbsp__bot[power]); } else { stbsp__int32 e, et, eb; double p2h, p2l; e = power; if (power < 0) e = -e; et = (e * 0x2c9) >> 14; /* %23 */ if (et > 13) et = 13; eb = e - (et * 23); ph = d; pl = 0.0; if (power < 0) { if (eb) { --eb; stbsp__ddmulthi(ph, pl, d, stbsp__negbot[eb]); stbsp__ddmultlos(ph, pl, d, stbsp__negboterr[eb]); } if (et) { stbsp__ddrenorm(ph, pl); --et; stbsp__ddmulthi(p2h, p2l, ph, stbsp__negtop[et]); stbsp__ddmultlo(p2h, p2l, ph, pl, stbsp__negtop[et], stbsp__negtoperr[et]); ph = p2h; pl = p2l; } } else { if (eb) { e = eb; if (eb > 22) eb = 22; e -= eb; stbsp__ddmulthi(ph, pl, d, stbsp__bot[eb]); if (e) { stbsp__ddrenorm(ph, pl); stbsp__ddmulthi(p2h, p2l, ph, stbsp__bot[e]); stbsp__ddmultlos(p2h, p2l, stbsp__bot[e], pl); ph = p2h; pl = p2l; } } if (et) { stbsp__ddrenorm(ph, pl); --et; stbsp__ddmulthi(p2h, p2l, ph, stbsp__top[et]); stbsp__ddmultlo(p2h, p2l, ph, pl, stbsp__top[et], stbsp__toperr[et]); ph = p2h; pl = p2l; } } } stbsp__ddrenorm(ph, pl); *ohi = ph; *olo = pl; } // given a float value, returns the significant bits in bits, and the position of the // decimal point in decimal_pos. +/-INF and NAN are specified by special values // returned in the decimal_pos parameter. // frac_digits is absolute normally, but if you want from first significant digits (got %g and %e), or in 0x80000000 static stbsp__int32 stbsp__real_to_str(char const **start, stbsp__uint32 *len, char *out, stbsp__int32 *decimal_pos, double value, stbsp__uint32 frac_digits) { double d; stbsp__int64 bits = 0; stbsp__int32 expo, e, ng, tens; d = value; STBSP__COPYFP(bits, d); expo = (stbsp__int32)((bits >> 52) & 2047); ng = (stbsp__int32)(bits >> 63); if (ng) d = -d; if (expo == 2047) // is nan or inf? { *start = (bits & ((((stbsp__uint64)1) << 52) - 1)) ? "NaN" : "Inf"; *decimal_pos = STBSP__SPECIAL; *len = 3; return ng; } if (expo == 0) // is zero or denormal { if ((bits << 1) == 0) // do zero { *decimal_pos = 1; *start = out; out[0] = '0'; *len = 1; return ng; } // find the right expo for denormals { stbsp__int64 v = ((stbsp__uint64)1) << 51; while ((bits & v) == 0) { --expo; v >>= 1; } } } // find the decimal exponent as well as the decimal bits of the value { double ph, pl; // log10 estimate - very specifically tweaked to hit or undershoot by no more than 1 of log10 of all expos 1..2046 tens = expo - 1023; tens = (tens < 0) ? ((tens * 617) / 2048) : (((tens * 1233) / 4096) + 1); // move the significant bits into position and stick them into an int stbsp__raise_to_power10(&ph, &pl, d, 18 - tens); // get full as much precision from double-double as possible stbsp__ddtoS64(bits, ph, pl); // check if we undershot if (((stbsp__uint64)bits) >= stbsp__tento19th) ++tens; } // now do the rounding in integer land frac_digits = (frac_digits & 0x80000000) ? ((frac_digits & 0x7ffffff) + 1) : (tens + frac_digits); if ((frac_digits < 24)) { stbsp__uint32 dg = 1; if ((stbsp__uint64)bits >= stbsp__powten[9]) dg = 10; while ((stbsp__uint64)bits >= stbsp__powten[dg]) { ++dg; if (dg == 20) goto noround; } if (frac_digits < dg) { stbsp__uint64 r; // add 0.5 at the right position and round e = dg - frac_digits; if ((stbsp__uint32)e >= 24) goto noround; r = stbsp__powten[e]; bits = bits + (r / 2); if ((stbsp__uint64)bits >= stbsp__powten[dg]) ++tens; bits /= r; } noround:; } // kill long trailing runs of zeros if (bits) { stbsp__uint32 n; for (;;) { if (bits <= 0xffffffff) break; if (bits % 1000) goto donez; bits /= 1000; } n = (stbsp__uint32)bits; while ((n % 1000) == 0) n /= 1000; bits = n; donez:; } // convert to string out += 64; e = 0; for (;;) { stbsp__uint32 n; char *o = out - 8; // do the conversion in chunks of U32s (avoid most 64-bit divides, worth it, constant denomiators be damned) if (bits >= 100000000) { n = (stbsp__uint32)(bits % 100000000); bits /= 100000000; } else { n = (stbsp__uint32)bits; bits = 0; } while (n) { out -= 2; *(stbsp__uint16 *)out = *(stbsp__uint16 *)&stbsp__digitpair[(n % 100) * 2]; n /= 100; e += 2; } if (bits == 0) { if ((e) && (out[0] == '0')) { ++out; --e; } break; } while (out != o) { *--out = '0'; ++e; } } *decimal_pos = tens; *start = out; *len = e; return ng; } #undef stbsp__ddmulthi #undef stbsp__ddrenorm #undef stbsp__ddmultlo #undef stbsp__ddmultlos #undef STBSP__SPECIAL #undef STBSP__COPYFP #endif // STB_SPRINTF_NOFLOAT // clean up #undef stbsp__uint16 #undef stbsp__uint32 #undef stbsp__int32 #undef stbsp__uint64 #undef stbsp__int64 #undef STBSP__UNALIGNED #endif // STB_SPRINTF_IMPLEMENTATION /* ------------------------------------------------------------------------------ This software is available under 2 licenses -- choose whichever you prefer. ------------------------------------------------------------------------------ ALTERNATIVE A - MIT License Copyright (c) 2017 Sean Barrett Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ------------------------------------------------------------------------------ ALTERNATIVE B - Public Domain (www.unlicense.org) This is free and unencumbered software released into the public domain. Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means. In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ------------------------------------------------------------------------------ */