From 4d0b209adcd2086063399b357b8079dc031a4b04 Mon Sep 17 00:00:00 2001 From: Scott Duensing Date: Thu, 2 Jun 2022 19:15:53 -0500 Subject: [PATCH] DJGPP backend working. --- client/client.pro | 4 + client/src/gui/gui.c | 11 +- client/src/gui/gui.h | 14 +- client/src/main.c | 23 +- client/src/os.h | 5 + client/src/platform/djgpp.c | 257 +++++- client/src/platform/platform.h | 4 +- shared/array.h | 1 + shared/log.c | 93 ++ shared/log.h | 19 + shared/macros.h | 7 + shared/memory.c | 50 + shared/memory.h | 6 + shared/thirdparty/stb_ds.h | 1590 ++++++++++++++++---------------- shared/util.c | 181 ++++ shared/util.h | 26 + 16 files changed, 1451 insertions(+), 840 deletions(-) create mode 100644 shared/log.c create mode 100644 shared/log.h create mode 100644 shared/util.c create mode 100644 shared/util.h diff --git a/client/client.pro b/client/client.pro index b81db1c..e361768 100644 --- a/client/client.pro +++ b/client/client.pro @@ -23,6 +23,8 @@ HEADERS += \ $$SHARED/stddclmr.h \ $$SHARED/thirdparty/memwatch/memwatch.h \ $$SHARED/thirdparty/stb_ds.h \ + $$SHARED/log.h \ + $$SHARED/util.h \ src/gui/font.h \ src/gui/gui.h \ src/gui/image.h \ @@ -38,6 +40,8 @@ SOURCES += \ $$SHARED/array.c \ $$SHARED/memory.c \ $$SHARED/thirdparty/memwatch/memwatch.c \ + $$SHARED/log.c \ + $$SHARED/util.c \ src/gui/font.c \ src/gui/gui.c \ src/gui/image.c \ diff --git a/client/src/gui/gui.c b/client/src/gui/gui.c index 3452961..35c9e56 100644 --- a/client/src/gui/gui.c +++ b/client/src/gui/gui.c @@ -25,7 +25,7 @@ static ColorT _mouseTransparency; void guiRun(void) { - EventT event; + EventT event = { 0 }; while (_guiRunning) { // Read mouse & keyboard. @@ -75,14 +75,15 @@ void guiShutdown(void) { } -void guiStartup(int16_t width, int16_t height, int16_t depth) { +uint8_t guiStartup(int16_t width, int16_t height, int16_t depth) { - platformStartup(width, height, depth); + if (platformStartup(width, height, depth) == FAIL) return FAIL; __guiScreenBuffer = videoSurfaceScreenGet(); __guiBackBuffer = videoSurfaceCreate(videoDisplayWidthGet(), videoDisplayHeightGet()); + videoSurfaceSet(__guiBackBuffer); - _mousePointer = imageLoad("mouse.png"); + _mousePointer = imageLoad("mouse.png"); _mouseTransparency = videoSurfacePixelGet(_mousePointer, videoSurfaceWidthGet(_mousePointer) - 2, 0); // Find our transparency color. __guiFontVGA8x14 = fontLoad("vga8x14.dat"); @@ -90,6 +91,8 @@ void guiStartup(int16_t width, int16_t height, int16_t depth) { wmStartup(); guiRegister(windowRegister); + + return SUCCESS; } diff --git a/client/src/gui/gui.h b/client/src/gui/gui.h index 214cd04..c6ce8dc 100644 --- a/client/src/gui/gui.h +++ b/client/src/gui/gui.h @@ -70,13 +70,13 @@ extern FontT *__guiFontVGA8x14; #define GUI_WHITE __guiBaseColors[15] -void guiModesShow(void); -void guiRegister(WidgetRegisterT widgetRegister); -void guiRun(void); -void guiShutdown(void); -void guiStartup(int16_t width, int16_t height, int16_t depth); -void guiStop(void); -void guiWidgetBaseSet(WidgetT *widget, uint8_t magic, uint16_t x, uint16_t y, uint16_t w, uint16_t h); +void guiModesShow(void); +void guiRegister(WidgetRegisterT widgetRegister); +void guiRun(void); +void guiShutdown(void); +uint8_t guiStartup(int16_t width, int16_t height, int16_t depth); +void guiStop(void); +void guiWidgetBaseSet(WidgetT *widget, uint8_t magic, uint16_t x, uint16_t y, uint16_t w, uint16_t h); #endif // GUI_H diff --git a/client/src/main.c b/client/src/main.c index b593cd9..61008a6 100644 --- a/client/src/main.c +++ b/client/src/main.c @@ -7,19 +7,22 @@ int main(int argc, char *argv[]) { uint16_t i; (void)argc; - (void)argv; - guiStartup(800, 600, 24); + memoryStartup(argv[0]); + logOpenByHandle(memoryLogHandleGet()); - i = 1; - //for (i=1; i<4; i++) { - sprintf(title, "Testing %d", i); - windowCreate(i * 50, i * 50, 300, 200, title, WIN_CLOSE | WIN_MAXIMIZE | WIN_MINIMIZE | WIN_RESIZE); - //} + if (guiStartup(800, 600, 32) == SUCCESS) { + i = 1; + //for (i=1; i<4; i++) { + sprintf(title, "Testing %d", i); + windowCreate(i * 50, i * 50, 300, 200, title, WIN_CLOSE | WIN_MAXIMIZE | WIN_MINIMIZE | WIN_RESIZE); + //} + guiRun(); + guiShutdown(); + } - guiRun(); - - guiShutdown(); + logClose(); + memoryShutdown(); return 0; } diff --git a/client/src/os.h b/client/src/os.h index 8d8b3ee..6c7ec46 100644 --- a/client/src/os.h +++ b/client/src/os.h @@ -7,9 +7,14 @@ #include #include +#include +#include #include +#include #include "macros.h" +#include "util.h" +#include "log.h" #include "memory.h" #include "stddclmr.h" diff --git a/client/src/platform/djgpp.c b/client/src/platform/djgpp.c index f90e964..be49d91 100644 --- a/client/src/platform/djgpp.c +++ b/client/src/platform/djgpp.c @@ -1,6 +1,8 @@ #ifdef BACKEND_DJGPP +#include "os.h" + #include #include #include @@ -8,19 +10,52 @@ #include #include #include -#include #include "djgpp.h" -#define DIVISIBLE_BY_EIGHT(x) ((((x) >> 3) << 3) == (x)) - - // These are all we support #define VBE_MM_PACKED 4 #define VBE_MM_DCOLOR 6 +// Based on http://www.brackeen.com/vga/source/djgpp20/mouse.c.html +#define MOUSE_INT 0x33 + +#define MOUSE_RESET 0x00 +#define MOUSE_STATUS 0x03 +#define MOUSE_GETMOTION 0x0B + +#define MOUSE_LEFT_BUTTON 0x01 +#define MOUSE_RIGHT_BUTTON 0x02 +#define MOUSE_MIDDLE_BUTTON 0x04 + + +#define KEYBOARD_READ_EXTENDED 0x10 +#define KEYBOARD_CHECK_EXTENDED 0x11 +#define KEYBOARD_META_EXTENDED 0x12 + + +enum MetaBitsE { + KEY_META_SHIFT_RIGHT = 0, + KEY_META_SHIFT_LEFT, + KEY_META_CONTROL, + KEY_META_ALT, + KEY_META_SCROLL_LOCKED, + KEY_META_NUM_LOCKED, + KEY_META_CAPS_LOCKED, + KEY_META_INSERT_LOCKED, + KEY_META_CONTROL_LEFT, + KEY_META_ALT_LEFT, + KEY_META_CONTROL_RIGHT, + KEY_META_ALT_RIGHT, + KEY_META_SCROLL_LOCK, + KEY_META_NUM_LOCK, + KEY_META_CAPS_LOCK, + KEY_META_SYSREQ +}; + + typedef struct VBEInfoS { char vbeSignature[4]; // 'VESA' 4 byte signature int16_t vbeVersion; // VBE version number @@ -121,6 +156,9 @@ typedef struct VBESurfaceS { } VBESurfaceT; +extern ColorT *__guiBaseColors; + + static VBESurfaceT _vbeSurface; static VBEInfoT _vbeInfo; static VBEModeInfoT _vbeModeInfo; @@ -148,7 +186,98 @@ void (*videoSurfacePixelSet)(uint16_t x, uint16_t y, ColorT color); void platformEventGet(EventT *event) { + int32_t x; + int32_t y; + int16_t dx; + int16_t dy; + int16_t h = videoDisplayHeightGet(); + int16_t w = videoDisplayWidthGet(); + int16_t ext = bioskey(KEYBOARD_CHECK_EXTENDED); + int16_t meta = bioskey(KEYBOARD_META_EXTENDED); + int16_t key = 0; + union REGS regs; + // Read mouse motion. + regs.x.ax = MOUSE_GETMOTION; + int86(MOUSE_INT, ®s, ®s); + dx = regs.x.cx; // Temporary assignment changes values to signed. + dy = regs.x.dx; // Don't skip this step. :-) + x = event->x + dx; + y = event->y + dy; + if (x < 0) x = 0; + if (x > w - 1) x = w - 1; + if (y < 0) y = 0; + if (y > h - 1) y = h - 1; + event->x = x; + event->y = y; + + // Read mouse buttons. + regs.x.ax = MOUSE_STATUS; + int86(MOUSE_INT, ®s, ®s); + + // Was the left button down? + if (event->buttons & BUTTON_LEFT) { + // Yes. Is it still down? + if ((regs.x.bx & MOUSE_LEFT_BUTTON) > 0) { + // Yes. Do nothing. + } else { + // No! Clear it and set UP event. + event->buttons &= ~BUTTON_LEFT; + event->flags |= EVENT_FLAG_LEFT_UP; + } + } else { + // No. Is it down now? + if ((regs.x.bx & MOUSE_LEFT_BUTTON) > 0) { + // Yes! Set bit and DOWN event. + event->buttons |= BUTTON_LEFT; + event->flags |= EVENT_FLAG_LEFT_DOWN; + } else { + // No. Do nothing. + } + } + + // Was the right button down? + if (event->buttons & BUTTON_RIGHT) { + // Yes. Is it still down? + if ((regs.x.bx & MOUSE_RIGHT_BUTTON) > 0) { + // Yes. Do nothing. + } else { + // No! Clear it and set UP event. + event->buttons &= ~BUTTON_RIGHT; + event->flags |= EVENT_FLAG_RIGHT_UP; + } + } else { + // No. Is it down now? + if ((regs.x.bx & MOUSE_RIGHT_BUTTON) > 0) { + // Yes! Set bit and DOWN event. + event->buttons |= BUTTON_RIGHT; + event->flags |= EVENT_FLAG_RIGHT_DOWN; + } else { + // No. Do nothing. + } + } + + // Read keyboard. + event->key = 0; + event->kbstat = 0; + if (ext > 0) { + key = bioskey(KEYBOARD_READ_EXTENDED); + // The value returned is a combination of the key's scan code in the high 8 bits + // and its ASCII code in the low 8 bits. For non-alphanumeric keys, such as the + // arrow keys, the low 8 bits are zeroed. + // Extended keys have the E0h prefix in the low 8 bits. + event->flags |= EVENT_FLAG_KEYPRESS; + if (LOW_BYTE(key) == 0xE0) { + //_extended = 1; + event->key = HIGH_BYTE(key); + } else { + //_extended = 0; + event->key = LOW_BYTE(key); + } + if ((meta & (1 << KEY_META_ALT)) + (meta & (1 << KEY_META_ALT_LEFT)) + (meta & (1 << KEY_META_ALT_RIGHT))) event->kbstat |= META_ALT; + if ((meta & (1 << KEY_META_CONTROL)) + (meta & (1 << KEY_META_CONTROL_LEFT)) + (meta & (1 << KEY_META_CONTROL_RIGHT))) event->kbstat |= META_CTRL; + if ((meta & (1 << KEY_META_SHIFT_LEFT)) + (meta & (1 << KEY_META_SHIFT_RIGHT))) event->kbstat |= META_SHIFT; + } } @@ -156,6 +285,8 @@ void platformShutdown(void) { __dpmi_regs r; __dpmi_meminfo m; + free(__guiBaseColors); + if (_vbeSurface.vbeInitBoolean == 0) { r.x.ax = 0x03; // make sure we're in 3h __dpmi_int(0x10, &r); // for buggy vesa implementations @@ -192,32 +323,51 @@ void platformShutdown(void) { } -void platformStartup(int16_t width, int16_t height, int16_t depth) { +uint8_t platformStartup(int16_t width, int16_t height, int16_t depth) { uint16_t vbeModeNumber; + uint8_t i; + uint8_t EGA[16][3] = { + { 0, 0, 0 }, /* black */ + { 0, 0, 170 }, /* blue */ + { 0, 170, 0 }, /* green */ + { 0, 170, 170 }, /* cyan */ + { 170, 0, 0 }, /* red */ + { 170, 0, 170 }, /* magenta */ + { 170, 85, 0 }, /* brown */ + { 170, 170, 170 }, /* light gray */ + { 85, 85, 85 }, /* dark gray */ + { 85, 85, 255 }, /* light blue */ + { 85, 255, 85 }, /* light green */ + { 85, 255, 255 }, /* light cyan */ + { 255, 85, 85 }, /* light red */ + { 255, 85, 255 }, /* light magenta */ + { 255, 255, 85 }, /* yellow */ + { 255, 255, 255 } /* white */ + }; if (vbeGetInfo() == NULL) { - //logWrite("No VESA BIOS Extensions found.\n"); - //vbeShutdown(); - return; + logWrite("No VESA BIOS Extensions found.\n"); + platformShutdown(); + return FAIL; } if (_vbeInfo.vbeVersion < 0x0200) { - //logWrite("VBE Version 2.0 or better required.\n"); - //vbeShutdown(); - return; + logWrite("VBE Version 2.0 or better required.\n"); + platformShutdown(); + return FAIL; } vbeGetPmodeInterface(); if ((vbeModeNumber = vbeSelectModeNumber(width, height, depth)) == 0) { - //logWrite("No appropriate video mode available.\n"); - //vbeShutdown(); - return; + logWrite("No appropriate video mode available.\n"); + platformShutdown(); + return FAIL; } if (vbeSetMode(vbeModeNumber) == NULL) { - //vbeShutdown(); - return; + platformShutdown(); + return FAIL; } if (_vbeSurface.bitsPerPixel == 8) videoSurfacePixelSet = videoSurfacePixelSet8; @@ -225,6 +375,12 @@ void platformStartup(int16_t width, int16_t height, int16_t depth) { if (_vbeSurface.bitsPerPixel == 15) videoSurfacePixelSet = videoSurfacePixelSet16; if (_vbeSurface.bitsPerPixel == 32) videoSurfacePixelSet = videoSurfacePixelSet32; + __guiBaseColors = (ColorT *)malloc(sizeof(ColorT) * 16); + for (i=0; i<16; i++) { + __guiBaseColors[i] = videoColorMake(EGA[i][0], EGA[i][1], EGA[i][2]); + } + + return SUCCESS; } @@ -414,12 +570,15 @@ static uint8_t vbeIsDesiredMode(void) { if (((_vbeModeInfo.modeAttributes) & (1<<7)) >> 7) { // Packed or Direct Color mode. if (_vbeModeInfo.memoryModel == VBE_MM_PACKED || _vbeModeInfo.memoryModel == VBE_MM_DCOLOR) { - // Resolution minimum of 640x480. - if (_vbeModeInfo.xResolution >= 640 && _vbeModeInfo.yResolution >= 480) { - // Multiple of 8 - if (DIVISIBLE_BY_EIGHT(_vbeModeInfo.xResolution) && DIVISIBLE_BY_EIGHT(_vbeModeInfo.yResolution)) { - // Valid mode! - return 1; + // We only handle these bit depths. + if ((_vbeModeInfo.bitsPerPixel == 8) || (_vbeModeInfo.bitsPerPixel == 15) || (_vbeModeInfo.bitsPerPixel == 16) || (_vbeModeInfo.bitsPerPixel == 32)) { + // Resolution minimum of 640x480. + if (_vbeModeInfo.xResolution >= 640 && _vbeModeInfo.yResolution >= 480) { + // Multiple of 8 + if (DIVISIBLE_BY_EIGHT(_vbeModeInfo.xResolution) && DIVISIBLE_BY_EIGHT(_vbeModeInfo.yResolution)) { + // Valid mode! + return 1; + } } } } @@ -568,7 +727,61 @@ uint16_t videoDisplayWidthGet(void) { void videoModesShow(void) { + int8_t counter; + // 0 1 2 3 4 5 6 7 8 + // 12345678901234567890123456789012345678901234567890123456789012345678901234567890 + //printf("VBE 2.0 driver v1.0 (c) 2021, Scott Duensing \n"); + //printf("Based on: VBE 2.0 driver v1.0 (c) 1999, Tobias Koch \n\n"); + + if (vbeGetInfo() == NULL) { + logWrite("No VESA BIOS Extensions found.\n"); + platformShutdown(); + return; + } + logWrite( + "Video Memory - %d KB\n" + "VBE Version - %d.%d detected\n" + "OEM Specification - %s\n", + _vbeInfo.totalMemory * 64, + _vbeInfo.vbeVersion >> 8, _vbeInfo.vbeVersion & 0x00FF, + _vbeInfo.oemStringPtr); + + if (_vbeInfo.vbeVersion >= 0x0200) { + logWrite( + "OEM Software Revision - %d.%d\n" + "OEM Vendor Name - %s\n" + "OEM Product Name - %s\n" + "OEM Product Revision - %s\n" + "Protected Mode Interface - %s\n\n", + _vbeInfo.oemSoftwareRev >> 8, _vbeInfo.oemSoftwareRev & 0x00FF, + _vbeInfo.oemVendorNamePtr, + _vbeInfo.oemProductNamePtr, + _vbeInfo.oemProductRevPtr, + vbeGetPmodeInterface() ? "Found" : "Missing"); + } else { + logWrite("VESA BIOS Extension 2.0 or better required!\n"); + platformShutdown(); + return; + } + + for (counter=0; ; counter++) { + if (_vbeInfo.videoModePtr[counter] == 0xFFFF) break; + vbeGetModeInfo(_vbeInfo.videoModePtr[counter]); + if (vbeIsDesiredMode()) { + logWrite("Mode %Xh - %4d x %4d x %2d - RGB %d:%d:%d - %s\n", + _vbeInfo.videoModePtr[counter], + _vbeModeInfo.xResolution, + _vbeModeInfo.yResolution, + _vbeModeInfo.bitsPerPixel, + _vbeModeInfo.redMaskSize, + _vbeModeInfo.greenMaskSize, + _vbeModeInfo.blueMaskSize, + ((_vbeModeInfo.memoryModel / 2) - 2) ? "DCOLOR" : "PACKED"); + } + } + + platformShutdown(); } diff --git a/client/src/platform/platform.h b/client/src/platform/platform.h index c66f117..4990035 100644 --- a/client/src/platform/platform.h +++ b/client/src/platform/platform.h @@ -18,8 +18,8 @@ #endif -void platformShutdown(void); -void platformStartup(int16_t width, int16_t height, int16_t depth); +void platformShutdown(void); +uint8_t platformStartup(int16_t width, int16_t height, int16_t depth); void videoModesShow(void); diff --git a/shared/array.h b/shared/array.h index 36a5aed..d1731ca 100644 --- a/shared/array.h +++ b/shared/array.h @@ -3,6 +3,7 @@ #include "os.h" + #include "thirdparty/stb_ds.h" diff --git a/shared/log.c b/shared/log.c new file mode 100644 index 0000000..c048693 --- /dev/null +++ b/shared/log.c @@ -0,0 +1,93 @@ +// ***TODO*** On DOS, open and close the log on every write so the file gets updated and we still have data on a crash. + + +#include "log.h" + + +static FILE *_log = NULL; +static uint8_t _ourHandle = 0; +static char *_logBuffer = NULL; +static uint16_t _logBufferSize = 0; +static logCallback _logCallback = NULL; + + +void logCallbackSet(logCallback callback) { + _logCallback = callback; +} + + +void logClose(void) { + if (_log && _ourHandle) { + fclose(_log); + _ourHandle = 0; + } + DEL(_logBuffer); +} + + +uint8_t logOpen(char *filename, uint8_t append) { + _log = fopen(filename, append ? "a" : "w"); + if (_log) { + _ourHandle = 1; + return 1; + } + return 0; +} + + +void logOpenByHandle(FILE *handle) { + _log = handle; +} + + +void logWrite(char *format, ...) { + va_list args = { 0 }; + va_list args2 = { 0 }; + uint16_t length = 0; + + if (_log) { + va_start(args, format); + va_copy(args2, args); + + // Attempt to write into current log buffer. + length = vsnprintf(_logBuffer, _logBufferSize, format, args); + + // Did it fit? + if (length >= _logBufferSize) { + // Nope. Resize buffer to fit plus a bit more. + if (_logBuffer) free(_logBuffer); + _logBufferSize = length + 32; + _logBuffer = (char *)malloc(_logBufferSize); + // Do it again. + vsnprintf(_logBuffer, _logBufferSize, format, args2); + } + +//#ifdef __linux__ + // Also output to stdout on Linux. + fprintf(stdout, "%s", _logBuffer); + fflush(stdout); +//#endif + + if (_logCallback) _logCallback(_logBuffer); + + fprintf(_log, "%s", _logBuffer); + fflush(_log); + + va_end(args2); + va_end(args); + } +} + + +void logWriteToFileOnly(char *format, ...) { + va_list args; + + va_start(args, format); + + if (_log) { + vfprintf(_log, format, args); + fflush(_log); + } + + va_end(args); +} diff --git a/shared/log.h b/shared/log.h new file mode 100644 index 0000000..fc0fd75 --- /dev/null +++ b/shared/log.h @@ -0,0 +1,19 @@ +#ifndef LOG_H +#define LOG_H + + +#include "os.h" + + +typedef void (*logCallback)(char *message); + + +void logCallbackSet(logCallback callback); +void logClose(void); +uint8_t logOpen(char *filename, uint8_t append); +void logOpenByHandle(FILE *handle); +void logWrite(char *format, ...); +void logWriteToFileOnly(char *format, ...); + + +#endif // LOG_H diff --git a/shared/macros.h b/shared/macros.h index 1d7d320..def3f88 100644 --- a/shared/macros.h +++ b/shared/macros.h @@ -12,6 +12,13 @@ #define DEL(v) {if(v) {free(v); v=NULL;}} +// Some helper defines. +#define DIVISIBLE_BY_EIGHT(x) ((((x) >> 3) << 3) == (x)) +#define HIGH_BYTE(b) ((uint8_t)(((b) & 0xFF00) >> 8)) +#define LOW_BYTE(b) ((uint8_t)((b) & 0x00FF)) + + +// Return codes. #define SUCCESS 0 #define FAIL 1 diff --git a/shared/memory.c b/shared/memory.c index b31569d..3ff5755 100644 --- a/shared/memory.c +++ b/shared/memory.c @@ -1 +1,51 @@ #include "memory.h" + + +FILE *_memoryLog = NULL; + + +#ifdef MEMORY_CHECK_ENABLED +void mwLogW(FILE *p); +#endif + + +FILE *memoryLogHandleGet(void) { + return _memoryLog; +} + + +#ifdef MEMORY_CHECK_ENABLED +void memoryOutput(int c) { + fputc(c, _memoryLog); + fflush(_memoryLog); + +#ifdef __linux__ + // Also output to stdout on Linux. + fputc(c, stdout); + fflush(stdout); +#endif +} +#endif + + +void memoryShutdown(void) { +#ifdef MEMORY_CHECK_ENABLED + unlink("memwatch.log"); // It just insists on creating this! + mwTerm(); +#endif +} + + +void memoryStartup(char *appName) { + char *logName = utilAppNameWithNewExtensionGet(appName, "log"); + + _memoryLog = fopen(logName, "w"); + +#ifdef MEMORY_CHECK_ENABLED + mwLogW(_memoryLog); + mwSetOutFunc(memoryOutput); + mwInit(); +#endif + + free(logName); +} diff --git a/shared/memory.h b/shared/memory.h index 97f5802..0f3f97d 100644 --- a/shared/memory.h +++ b/shared/memory.h @@ -4,10 +4,16 @@ #include "os.h" + #ifdef MEMORY_CHECK_ENABLED #define MEMWATCH #include "thirdparty/memwatch/memwatch.h" #endif +FILE *memoryLogHandleGet(void); +void memoryShutdown(void); +void memoryStartup(char *appName); + + #endif // MEMORY_H diff --git a/shared/thirdparty/stb_ds.h b/shared/thirdparty/stb_ds.h index e84c82d..ebd6161 100644 --- a/shared/thirdparty/stb_ds.h +++ b/shared/thirdparty/stb_ds.h @@ -4,11 +4,11 @@ dynamic arrays and hash tables for C (also works in C++). For a gentle introduction: - http://nothings.org/stb_ds + http://nothings.org/stb_ds To use this library, do this in *one* C or C++ file: - #define STB_DS_IMPLEMENTATION - #include "stb_ds.h" + #define STB_DS_IMPLEMENTATION + #include "stb_ds.h" TABLE OF CONTENTS @@ -25,39 +25,39 @@ COMPILE-TIME OPTIONS #define STBDS_NO_SHORT_NAMES - This flag needs to be set globally. + This flag needs to be set globally. - By default stb_ds exposes shorter function names that are not qualified - with the "stbds_" prefix. If these names conflict with the names in your - code, define this flag. + By default stb_ds exposes shorter function names that are not qualified + with the "stbds_" prefix. If these names conflict with the names in your + code, define this flag. #define STBDS_SIPHASH_2_4 - This flag only needs to be set in the file containing #define STB_DS_IMPLEMENTATION. + This flag only needs to be set in the file containing #define STB_DS_IMPLEMENTATION. - By default stb_ds.h hashes using a weaker variant of SipHash and a custom hash for - 4- and 8-byte keys. On 64-bit platforms, you can define the above flag to force - stb_ds.h to use specification-compliant SipHash-2-4 for all keys. Doing so makes - hash table insertion about 20% slower on 4- and 8-byte keys, 5% slower on - 64-byte keys, and 10% slower on 256-byte keys on my test computer. + By default stb_ds.h hashes using a weaker variant of SipHash and a custom hash for + 4- and 8-byte keys. On 64-bit platforms, you can define the above flag to force + stb_ds.h to use specification-compliant SipHash-2-4 for all keys. Doing so makes + hash table insertion about 20% slower on 4- and 8-byte keys, 5% slower on + 64-byte keys, and 10% slower on 256-byte keys on my test computer. #define STBDS_REALLOC(context,ptr,size) better_realloc #define STBDS_FREE(context,ptr) better_free - These defines only need to be set in the file containing #define STB_DS_IMPLEMENTATION. + These defines only need to be set in the file containing #define STB_DS_IMPLEMENTATION. - By default stb_ds uses stdlib realloc() and free() for memory management. You can - substitute your own functions instead by defining these symbols. You must either - define both, or neither. Note that at the moment, 'context' will always be NULL. - @TODO add an array/hash initialization function that takes a memory context pointer. + By default stb_ds uses stdlib realloc() and free() for memory management. You can + substitute your own functions instead by defining these symbols. You must either + define both, or neither. Note that at the moment, 'context' will always be NULL. + @TODO add an array/hash initialization function that takes a memory context pointer. #define STBDS_UNIT_TESTS - Defines a function stbds_unit_tests() that checks the functioning of the data structures. + Defines a function stbds_unit_tests() that checks the functioning of the data structures. Note that on older versions of gcc (e.g. 5.x.x) you may need to build with '-std=c++0x' - (or equivalentally '-std=c++11') when using anonymous structures as seen on the web - page or in STBDS_UNIT_TESTS. + (or equivalentally '-std=c++11') when using anonymous structures as seen on the web + page or in STBDS_UNIT_TESTS. LICENSE @@ -68,302 +68,302 @@ DOCUMENTATION Dynamic Arrays - Non-function interface: + Non-function interface: - Declare an empty dynamic array of type T - T* foo = NULL; + Declare an empty dynamic array of type T + T* foo = NULL; - Access the i'th item of a dynamic array 'foo' of type T, T* foo: - foo[i] + Access the i'th item of a dynamic array 'foo' of type T, T* foo: + foo[i] - Functions (actually macros) + Functions (actually macros) - arrfree: - void arrfree(T*); - Frees the array. + arrfree: + void arrfree(T*); + Frees the array. - arrlen: - ptrdiff_t arrlen(T*); - Returns the number of elements in the array. + arrlen: + ptrdiff_t arrlen(T*); + Returns the number of elements in the array. - arrlenu: - size_t arrlenu(T*); - Returns the number of elements in the array as an unsigned type. + arrlenu: + size_t arrlenu(T*); + Returns the number of elements in the array as an unsigned type. - arrpop: - T arrpop(T* a) - Removes the final element of the array and returns it. + arrpop: + T arrpop(T* a) + Removes the final element of the array and returns it. - arrput: - T arrput(T* a, T b); - Appends the item b to the end of array a. Returns b. + arrput: + T arrput(T* a, T b); + Appends the item b to the end of array a. Returns b. - arrins: - T arrins(T* a, int p, T b); - Inserts the item b into the middle of array a, into a[p], - moving the rest of the array over. Returns b. + arrins: + T arrins(T* a, int p, T b); + Inserts the item b into the middle of array a, into a[p], + moving the rest of the array over. Returns b. - arrinsn: - void arrinsn(T* a, int p, int n); - Inserts n uninitialized items into array a starting at a[p], - moving the rest of the array over. + arrinsn: + void arrinsn(T* a, int p, int n); + Inserts n uninitialized items into array a starting at a[p], + moving the rest of the array over. - arraddnptr: - T* arraddnptr(T* a, int n) - Appends n uninitialized items onto array at the end. - Returns a pointer to the first uninitialized item added. + arraddnptr: + T* arraddnptr(T* a, int n) + Appends n uninitialized items onto array at the end. + Returns a pointer to the first uninitialized item added. - arraddnindex: - size_t arraddnindex(T* a, int n) - Appends n uninitialized items onto array at the end. - Returns the index of the first uninitialized item added. + arraddnindex: + size_t arraddnindex(T* a, int n) + Appends n uninitialized items onto array at the end. + Returns the index of the first uninitialized item added. - arrdel: - void arrdel(T* a, int p); - Deletes the element at a[p], moving the rest of the array over. + arrdel: + void arrdel(T* a, int p); + Deletes the element at a[p], moving the rest of the array over. - arrdeln: - void arrdeln(T* a, int p, int n); - Deletes n elements starting at a[p], moving the rest of the array over. + arrdeln: + void arrdeln(T* a, int p, int n); + Deletes n elements starting at a[p], moving the rest of the array over. - arrdelswap: - void arrdelswap(T* a, int p); - Deletes the element at a[p], replacing it with the element from - the end of the array. O(1) performance. + arrdelswap: + void arrdelswap(T* a, int p); + Deletes the element at a[p], replacing it with the element from + the end of the array. O(1) performance. - arrsetlen: - void arrsetlen(T* a, int n); - Changes the length of the array to n. Allocates uninitialized - slots at the end if necessary. + arrsetlen: + void arrsetlen(T* a, int n); + Changes the length of the array to n. Allocates uninitialized + slots at the end if necessary. - arrsetcap: - size_t arrsetcap(T* a, int n); - Sets the length of allocated storage to at least n. It will not - change the length of the array. + arrsetcap: + size_t arrsetcap(T* a, int n); + Sets the length of allocated storage to at least n. It will not + change the length of the array. - arrcap: - size_t arrcap(T* a); - Returns the number of total elements the array can contain without - needing to be reallocated. + arrcap: + size_t arrcap(T* a); + Returns the number of total elements the array can contain without + needing to be reallocated. Hash maps & String hash maps - Given T is a structure type: struct { TK key; TV value; }. Note that some - functions do not require TV value and can have other fields. For string - hash maps, TK must be 'char *'. + Given T is a structure type: struct { TK key; TV value; }. Note that some + functions do not require TV value and can have other fields. For string + hash maps, TK must be 'char *'. - Special interface: + Special interface: - stbds_rand_seed: - void stbds_rand_seed(size_t seed); - For security against adversarially chosen data, you should seed the - library with a strong random number. Or at least seed it with time(). + stbds_rand_seed: + void stbds_rand_seed(size_t seed); + For security against adversarially chosen data, you should seed the + library with a strong random number. Or at least seed it with time(). - stbds_hash_string: - size_t stbds_hash_string(char *str, size_t seed); - Returns a hash value for a string. + stbds_hash_string: + size_t stbds_hash_string(char *str, size_t seed); + Returns a hash value for a string. - stbds_hash_bytes: - size_t stbds_hash_bytes(void *p, size_t len, size_t seed); - These functions hash an arbitrary number of bytes. The function - uses a custom hash for 4- and 8-byte data, and a weakened version - of SipHash for everything else. On 64-bit platforms you can get - specification-compliant SipHash-2-4 on all data by defining - STBDS_SIPHASH_2_4, at a significant cost in speed. + stbds_hash_bytes: + size_t stbds_hash_bytes(void *p, size_t len, size_t seed); + These functions hash an arbitrary number of bytes. The function + uses a custom hash for 4- and 8-byte data, and a weakened version + of SipHash for everything else. On 64-bit platforms you can get + specification-compliant SipHash-2-4 on all data by defining + STBDS_SIPHASH_2_4, at a significant cost in speed. - Non-function interface: + Non-function interface: - Declare an empty hash map of type T - T* foo = NULL; + Declare an empty hash map of type T + T* foo = NULL; - Access the i'th entry in a hash table T* foo: - foo[i] + Access the i'th entry in a hash table T* foo: + foo[i] - Function interface (actually macros): + Function interface (actually macros): - hmfree - shfree - void hmfree(T*); - void shfree(T*); - Frees the hashmap and sets the pointer to NULL. + hmfree + shfree + void hmfree(T*); + void shfree(T*); + Frees the hashmap and sets the pointer to NULL. - hmlen - shlen - ptrdiff_t hmlen(T*) - ptrdiff_t shlen(T*) - Returns the number of elements in the hashmap. + hmlen + shlen + ptrdiff_t hmlen(T*) + ptrdiff_t shlen(T*) + Returns the number of elements in the hashmap. - hmlenu - shlenu - size_t hmlenu(T*) - size_t shlenu(T*) - Returns the number of elements in the hashmap. + hmlenu + shlenu + size_t hmlenu(T*) + size_t shlenu(T*) + Returns the number of elements in the hashmap. - hmgeti - shgeti - hmgeti_ts - ptrdiff_t hmgeti(T*, TK key) - ptrdiff_t shgeti(T*, char* key) - ptrdiff_t hmgeti_ts(T*, TK key, ptrdiff_t tempvar) - Returns the index in the hashmap which has the key 'key', or -1 - if the key is not present. + hmgeti + shgeti + hmgeti_ts + ptrdiff_t hmgeti(T*, TK key) + ptrdiff_t shgeti(T*, char* key) + ptrdiff_t hmgeti_ts(T*, TK key, ptrdiff_t tempvar) + Returns the index in the hashmap which has the key 'key', or -1 + if the key is not present. - hmget - hmget_ts - shget - TV hmget(T*, TK key) - TV shget(T*, char* key) - TV hmget_ts(T*, TK key, ptrdiff_t tempvar) - Returns the value corresponding to 'key' in the hashmap. - The structure must have a 'value' field + hmget + hmget_ts + shget + TV hmget(T*, TK key) + TV shget(T*, char* key) + TV hmget_ts(T*, TK key, ptrdiff_t tempvar) + Returns the value corresponding to 'key' in the hashmap. + The structure must have a 'value' field - hmgets - shgets - T hmgets(T*, TK key) - T shgets(T*, char* key) - Returns the structure corresponding to 'key' in the hashmap. + hmgets + shgets + T hmgets(T*, TK key) + T shgets(T*, char* key) + Returns the structure corresponding to 'key' in the hashmap. - hmgetp - shgetp - hmgetp_ts - hmgetp_null - shgetp_null - T* hmgetp(T*, TK key) - T* shgetp(T*, char* key) - T* hmgetp_ts(T*, TK key, ptrdiff_t tempvar) - T* hmgetp_null(T*, TK key) - T* shgetp_null(T*, char *key) - Returns a pointer to the structure corresponding to 'key' in - the hashmap. Functions ending in "_null" return NULL if the key - is not present in the hashmap; the others return a pointer to a - structure holding the default value (but not the searched-for key). + hmgetp + shgetp + hmgetp_ts + hmgetp_null + shgetp_null + T* hmgetp(T*, TK key) + T* shgetp(T*, char* key) + T* hmgetp_ts(T*, TK key, ptrdiff_t tempvar) + T* hmgetp_null(T*, TK key) + T* shgetp_null(T*, char *key) + Returns a pointer to the structure corresponding to 'key' in + the hashmap. Functions ending in "_null" return NULL if the key + is not present in the hashmap; the others return a pointer to a + structure holding the default value (but not the searched-for key). - hmdefault - shdefault - TV hmdefault(T*, TV value) - TV shdefault(T*, TV value) - Sets the default value for the hashmap, the value which will be - returned by hmget/shget if the key is not present. + hmdefault + shdefault + TV hmdefault(T*, TV value) + TV shdefault(T*, TV value) + Sets the default value for the hashmap, the value which will be + returned by hmget/shget if the key is not present. - hmdefaults - shdefaults - TV hmdefaults(T*, T item) - TV shdefaults(T*, T item) - Sets the default struct for the hashmap, the contents which will be - returned by hmgets/shgets if the key is not present. + hmdefaults + shdefaults + TV hmdefaults(T*, T item) + TV shdefaults(T*, T item) + Sets the default struct for the hashmap, the contents which will be + returned by hmgets/shgets if the key is not present. - hmput - shput - TV hmput(T*, TK key, TV value) - TV shput(T*, char* key, TV value) - Inserts a pair into the hashmap. If the key is already - present in the hashmap, updates its value. + hmput + shput + TV hmput(T*, TK key, TV value) + TV shput(T*, char* key, TV value) + Inserts a pair into the hashmap. If the key is already + present in the hashmap, updates its value. - hmputs - shputs - T hmputs(T*, T item) - T shputs(T*, T item) - Inserts a struct with T.key into the hashmap. If the struct is already - present in the hashmap, updates it. + hmputs + shputs + T hmputs(T*, T item) + T shputs(T*, T item) + Inserts a struct with T.key into the hashmap. If the struct is already + present in the hashmap, updates it. - hmdel - shdel - int hmdel(T*, TK key) - int shdel(T*, char* key) - If 'key' is in the hashmap, deletes its entry and returns 1. - Otherwise returns 0. + hmdel + shdel + int hmdel(T*, TK key) + int shdel(T*, char* key) + If 'key' is in the hashmap, deletes its entry and returns 1. + Otherwise returns 0. - Function interface (actually macros) for strings only: + Function interface (actually macros) for strings only: - sh_new_strdup - void sh_new_strdup(T*); - Overwrites the existing pointer with a newly allocated - string hashmap which will automatically allocate and free - each string key using realloc/free + sh_new_strdup + void sh_new_strdup(T*); + Overwrites the existing pointer with a newly allocated + string hashmap which will automatically allocate and free + each string key using realloc/free - sh_new_arena - void sh_new_arena(T*); - Overwrites the existing pointer with a newly allocated - string hashmap which will automatically allocate each string - key to a string arena. Every string key ever used by this - hash table remains in the arena until the arena is freed. - Additionally, any key which is deleted and reinserted will - be allocated multiple times in the string arena. + sh_new_arena + void sh_new_arena(T*); + Overwrites the existing pointer with a newly allocated + string hashmap which will automatically allocate each string + key to a string arena. Every string key ever used by this + hash table remains in the arena until the arena is freed. + Additionally, any key which is deleted and reinserted will + be allocated multiple times in the string arena. NOTES * These data structures are realloc'd when they grow, and the macro - "functions" write to the provided pointer. This means: (a) the pointer - must be an lvalue, and (b) the pointer to the data structure is not - stable, and you must maintain it the same as you would a realloc'd - pointer. For example, if you pass a pointer to a dynamic array to a - function which updates it, the function must return back the new - pointer to the caller. This is the price of trying to do this in C. + "functions" write to the provided pointer. This means: (a) the pointer + must be an lvalue, and (b) the pointer to the data structure is not + stable, and you must maintain it the same as you would a realloc'd + pointer. For example, if you pass a pointer to a dynamic array to a + function which updates it, the function must return back the new + pointer to the caller. This is the price of trying to do this in C. * The following are the only functions that are thread-safe on a single data - structure, i.e. can be run in multiple threads simultaneously on the same - data structure - hmlen shlen - hmlenu shlenu - hmget_ts shget_ts - hmgeti_ts shgeti_ts - hmgets_ts shgets_ts + structure, i.e. can be run in multiple threads simultaneously on the same + data structure + hmlen shlen + hmlenu shlenu + hmget_ts shget_ts + hmgeti_ts shgeti_ts + hmgets_ts shgets_ts * You iterate over the contents of a dynamic array and a hashmap in exactly - the same way, using arrlen/hmlen/shlen: + the same way, using arrlen/hmlen/shlen: - for (i=0; i < arrlen(foo); ++i) - ... foo[i] ... + for (i=0; i < arrlen(foo); ++i) + ... foo[i] ... * All operations except arrins/arrdel are O(1) amortized, but individual - operations can be slow, so these data structures may not be suitable - for real time use. Dynamic arrays double in capacity as needed, so - elements are copied an average of once. Hash tables double/halve - their size as needed, with appropriate hysteresis to maintain O(1) - performance. + operations can be slow, so these data structures may not be suitable + for real time use. Dynamic arrays double in capacity as needed, so + elements are copied an average of once. Hash tables double/halve + their size as needed, with appropriate hysteresis to maintain O(1) + performance. NOTES - DYNAMIC ARRAY * If you know how long a dynamic array is going to be in advance, you can avoid - extra memory allocations by using arrsetlen to allocate it to that length in - advance and use foo[n] while filling it out, or arrsetcap to allocate the memory - for that length and use arrput/arrpush as normal. + extra memory allocations by using arrsetlen to allocate it to that length in + advance and use foo[n] while filling it out, or arrsetcap to allocate the memory + for that length and use arrput/arrpush as normal. * Unlike some other versions of the dynamic array, this version should - be safe to use with strict-aliasing optimizations. + be safe to use with strict-aliasing optimizations. NOTES - HASH MAP * For compilers other than GCC and clang (e.g. Visual Studio), for hmput/hmget/hmdel - and variants, the key must be an lvalue (so the macro can take the address of it). - Extensions are used that eliminate this requirement if you're using C99 and later - in GCC or clang, or if you're using C++ in GCC. But note that this can make your - code less portable. + and variants, the key must be an lvalue (so the macro can take the address of it). + Extensions are used that eliminate this requirement if you're using C99 and later + in GCC or clang, or if you're using C++ in GCC. But note that this can make your + code less portable. * To test for presence of a key in a hashmap, just do 'hmgeti(foo,key) >= 0'. * The iteration order of your data in the hashmap is determined solely by the - order of insertions and deletions. In particular, if you never delete, new - keys are always added at the end of the array. This will be consistent - across all platforms and versions of the library. However, you should not - attempt to serialize the internal hash table, as the hash is not consistent - between different platforms, and may change with future versions of the library. + order of insertions and deletions. In particular, if you never delete, new + keys are always added at the end of the array. This will be consistent + across all platforms and versions of the library. However, you should not + attempt to serialize the internal hash table, as the hash is not consistent + between different platforms, and may change with future versions of the library. * Use sh_new_arena() for string hashmaps that you never delete from. Initialize - with NULL if you're managing the memory for your strings, or your strings are - never freed (at least until the hashmap is freed). Otherwise, use sh_new_strdup(). - @TODO: make an arena variant that garbage collects the strings with a trivial - copy collector into a new arena whenever the table shrinks / rebuilds. Since - current arena recommendation is to only use arena if it never deletes, then - this can just replace current arena implementation. + with NULL if you're managing the memory for your strings, or your strings are + never freed (at least until the hashmap is freed). Otherwise, use sh_new_strdup(). + @TODO: make an arena variant that garbage collects the strings with a trivial + copy collector into a new arena whenever the table shrinks / rebuilds. Since + current arena recommendation is to only use arena if it never deletes, then + this can just replace current arena implementation. * If adversarial input is a serious concern and you're on a 64-bit platform, - enable STBDS_SIPHASH_2_4 (see the 'Compile-time options' section), and pass - a strong random number to stbds_rand_seed. + enable STBDS_SIPHASH_2_4 (see the 'Compile-time options' section), and pass + a strong random number to stbds_rand_seed. * The default value for the hash table is stored in foo[-1], so if you - use code like 'hmget(T,k)->value = 5' you can accidentally overwrite - the value stored by hmdefault if 'k' is not present. + use code like 'hmget(T,k)->value = 5' you can accidentally overwrite + the value stored by hmdefault if 'k' is not present. CREDITS @@ -373,15 +373,15 @@ CREDITS github:HeroicKatora -- arraddn() reworking Bugfixes: - Andy Durdin - Shane Liesegang - Vinh Truong - Andreas Molzer - github:hashitaku - github:srdjanstipic - Macoy Madson - Andreas Vennstrom - Tobias Mansfield-Williams + Andy Durdin + Shane Liesegang + Vinh Truong + Andreas Molzer + github:hashitaku + github:srdjanstipic + Macoy Madson + Andreas Vennstrom + Tobias Mansfield-Williams */ #ifdef STBDS_UNIT_TESTS @@ -556,44 +556,44 @@ extern void * stbds_shmode_func(size_t elemsize, int mode); #define stbds_arrins(a,i,v) (stbds_arrinsn((a),(i),1), (a)[i]=(v)) #define stbds_arrmaybegrow(a,n) ((!(a) || stbds_header(a)->length + (n) > stbds_header(a)->capacity) \ - ? (stbds_arrgrow(a,n,0),0) : 0) + ? (stbds_arrgrow(a,n,0),0) : 0) #define stbds_arrgrow(a,b,c) ((a) = stbds_arrgrowf_wrapper((a), sizeof *(a), (b), (c))) #define stbds_hmput(t, k, v) \ - ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) STBDS_ADDRESSOF((t)->key, (k)), sizeof (t)->key, 0), \ - (t)[stbds_temp((t)-1)].key = (k), \ - (t)[stbds_temp((t)-1)].value = (v)) + ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) STBDS_ADDRESSOF((t)->key, (k)), sizeof (t)->key, 0), \ + (t)[stbds_temp((t)-1)].key = (k), \ + (t)[stbds_temp((t)-1)].value = (v)) #define stbds_hmputs(t, s) \ - ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), &(s).key, sizeof (s).key, STBDS_HM_BINARY), \ - (t)[stbds_temp((t)-1)] = (s)) + ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), &(s).key, sizeof (s).key, STBDS_HM_BINARY), \ + (t)[stbds_temp((t)-1)] = (s)) #define stbds_hmgeti(t,k) \ - ((t) = stbds_hmget_key_wrapper((t), sizeof *(t), (void*) STBDS_ADDRESSOF((t)->key, (k)), sizeof (t)->key, STBDS_HM_BINARY), \ - stbds_temp((t)-1)) + ((t) = stbds_hmget_key_wrapper((t), sizeof *(t), (void*) STBDS_ADDRESSOF((t)->key, (k)), sizeof (t)->key, STBDS_HM_BINARY), \ + stbds_temp((t)-1)) #define stbds_hmgeti_ts(t,k,temp) \ - ((t) = stbds_hmget_key_ts_wrapper((t), sizeof *(t), (void*) STBDS_ADDRESSOF((t)->key, (k)), sizeof (t)->key, &(temp), STBDS_HM_BINARY), \ - (temp)) + ((t) = stbds_hmget_key_ts_wrapper((t), sizeof *(t), (void*) STBDS_ADDRESSOF((t)->key, (k)), sizeof (t)->key, &(temp), STBDS_HM_BINARY), \ + (temp)) #define stbds_hmgetp(t, k) \ - ((void) stbds_hmgeti(t,k), &(t)[stbds_temp((t)-1)]) + ((void) stbds_hmgeti(t,k), &(t)[stbds_temp((t)-1)]) #define stbds_hmgetp_ts(t, k, temp) \ - ((void) stbds_hmgeti_ts(t,k,temp), &(t)[temp]) + ((void) stbds_hmgeti_ts(t,k,temp), &(t)[temp]) #define stbds_hmdel(t,k) \ - (((t) = stbds_hmdel_key_wrapper((t),sizeof *(t), (void*) STBDS_ADDRESSOF((t)->key, (k)), sizeof (t)->key, STBDS_OFFSETOF((t),key), STBDS_HM_BINARY)),(t)?stbds_temp((t)-1):0) + (((t) = stbds_hmdel_key_wrapper((t),sizeof *(t), (void*) STBDS_ADDRESSOF((t)->key, (k)), sizeof (t)->key, STBDS_OFFSETOF((t),key), STBDS_HM_BINARY)),(t)?stbds_temp((t)-1):0) #define stbds_hmdefault(t, v) \ - ((t) = stbds_hmput_default_wrapper((t), sizeof *(t)), (t)[-1].value = (v)) + ((t) = stbds_hmput_default_wrapper((t), sizeof *(t)), (t)[-1].value = (v)) #define stbds_hmdefaults(t, s) \ - ((t) = stbds_hmput_default_wrapper((t), sizeof *(t)), (t)[-1] = (s)) + ((t) = stbds_hmput_default_wrapper((t), sizeof *(t)), (t)[-1] = (s)) #define stbds_hmfree(p) \ - ((void) ((p) != NULL ? stbds_hmfree_func((p)-1,sizeof*(p)),0 : 0),(p)=NULL) + ((void) ((p) != NULL ? stbds_hmfree_func((p)-1,sizeof*(p)),0 : 0),(p)=NULL) #define stbds_hmgets(t, k) (*stbds_hmgetp(t,k)) #define stbds_hmget(t, k) (stbds_hmgetp(t,k)->value) @@ -603,45 +603,45 @@ extern void * stbds_shmode_func(size_t elemsize, int mode); #define stbds_hmgetp_null(t,k) (stbds_hmgeti(t,k) == -1 ? NULL : &(t)[stbds_temp((t)-1)]) #define stbds_shput(t, k, v) \ - ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) (k), sizeof (t)->key, STBDS_HM_STRING), \ - (t)[stbds_temp((t)-1)].value = (v)) + ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) (k), sizeof (t)->key, STBDS_HM_STRING), \ + (t)[stbds_temp((t)-1)].value = (v)) #define stbds_shputi(t, k, v) \ - ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) (k), sizeof (t)->key, STBDS_HM_STRING), \ - (t)[stbds_temp((t)-1)].value = (v), stbds_temp((t)-1)) + ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) (k), sizeof (t)->key, STBDS_HM_STRING), \ + (t)[stbds_temp((t)-1)].value = (v), stbds_temp((t)-1)) #define stbds_shputs(t, s) \ - ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) (s).key, sizeof (s).key, STBDS_HM_STRING), \ - (t)[stbds_temp((t)-1)] = (s), \ - (t)[stbds_temp((t)-1)].key = stbds_temp_key((t)-1)) // above line overwrites whole structure, so must rewrite key here if it was allocated internally + ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) (s).key, sizeof (s).key, STBDS_HM_STRING), \ + (t)[stbds_temp((t)-1)] = (s), \ + (t)[stbds_temp((t)-1)].key = stbds_temp_key((t)-1)) // above line overwrites whole structure, so must rewrite key here if it was allocated internally #define stbds_pshput(t, p) \ - ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) (p)->key, sizeof (p)->key, STBDS_HM_PTR_TO_STRING), \ - (t)[stbds_temp((t)-1)] = (p)) + ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) (p)->key, sizeof (p)->key, STBDS_HM_PTR_TO_STRING), \ + (t)[stbds_temp((t)-1)] = (p)) #define stbds_shgeti(t,k) \ - ((t) = stbds_hmget_key_wrapper((t), sizeof *(t), (void*) (k), sizeof (t)->key, STBDS_HM_STRING), \ - stbds_temp((t)-1)) + ((t) = stbds_hmget_key_wrapper((t), sizeof *(t), (void*) (k), sizeof (t)->key, STBDS_HM_STRING), \ + stbds_temp((t)-1)) #define stbds_pshgeti(t,k) \ - ((t) = stbds_hmget_key_wrapper((t), sizeof *(t), (void*) (k), sizeof (*(t))->key, STBDS_HM_PTR_TO_STRING), \ - stbds_temp((t)-1)) + ((t) = stbds_hmget_key_wrapper((t), sizeof *(t), (void*) (k), sizeof (*(t))->key, STBDS_HM_PTR_TO_STRING), \ + stbds_temp((t)-1)) #define stbds_shgetp(t, k) \ - ((void) stbds_shgeti(t,k), &(t)[stbds_temp((t)-1)]) + ((void) stbds_shgeti(t,k), &(t)[stbds_temp((t)-1)]) #define stbds_pshget(t, k) \ - ((void) stbds_pshgeti(t,k), (t)[stbds_temp((t)-1)]) + ((void) stbds_pshgeti(t,k), (t)[stbds_temp((t)-1)]) #define stbds_shdel(t,k) \ - (((t) = stbds_hmdel_key_wrapper((t),sizeof *(t), (void*) (k), sizeof (t)->key, STBDS_OFFSETOF((t),key), STBDS_HM_STRING)),(t)?stbds_temp((t)-1):0) + (((t) = stbds_hmdel_key_wrapper((t),sizeof *(t), (void*) (k), sizeof (t)->key, STBDS_OFFSETOF((t),key), STBDS_HM_STRING)),(t)?stbds_temp((t)-1):0) #define stbds_pshdel(t,k) \ - (((t) = stbds_hmdel_key_wrapper((t),sizeof *(t), (void*) (k), sizeof (*(t))->key, STBDS_OFFSETOF(*(t),key), STBDS_HM_PTR_TO_STRING)),(t)?stbds_temp((t)-1):0) + (((t) = stbds_hmdel_key_wrapper((t),sizeof *(t), (void*) (k), sizeof (*(t))->key, STBDS_OFFSETOF(*(t),key), STBDS_HM_PTR_TO_STRING)),(t)?stbds_temp((t)-1):0) #define stbds_sh_new_arena(t) \ - ((t) = stbds_shmode_func_wrapper(t, sizeof *(t), STBDS_SH_ARENA)) + ((t) = stbds_shmode_func_wrapper(t, sizeof *(t), STBDS_SH_ARENA)) #define stbds_sh_new_strdup(t) \ - ((t) = stbds_shmode_func_wrapper(t, sizeof *(t), STBDS_SH_STRDUP)) + ((t) = stbds_shmode_func_wrapper(t, sizeof *(t), STBDS_SH_STRDUP)) #define stbds_shdefault(t, v) stbds_hmdefault(t,v) #define stbds_shdefaults(t, s) stbds_hmdefaults(t,s) @@ -768,16 +768,16 @@ void *stbds_arrgrowf(void *a, size_t elemsize, size_t addlen, size_t min_cap) // compute the minimum capacity needed if (min_len > min_cap) - min_cap = min_len; + min_cap = min_len; if (min_cap <= stbds_arrcap(a)) - return a; + return a; // increase needed capacity to guarantee O(1) amortized if (min_cap < 2 * stbds_arrcap(a)) - min_cap = 2 * stbds_arrcap(a); + min_cap = 2 * stbds_arrcap(a); else if (min_cap < 4) - min_cap = 4; + min_cap = 4; //if (num_prev < 65536) if (a) prev_allocs[num_prev++] = (int *) ((char *) a+1); //if (num_prev == 2201) @@ -786,11 +786,11 @@ void *stbds_arrgrowf(void *a, size_t elemsize, size_t addlen, size_t min_cap) //if (num_prev < 65536) prev_allocs[num_prev++] = (int *) (char *) b; b = (char *) b + sizeof(stbds_array_header); if (a == NULL) { - stbds_header(b)->length = 0; - stbds_header(b)->hash_table = 0; - stbds_header(b)->temp = 0; + stbds_header(b)->length = 0; + stbds_header(b)->hash_table = 0; + stbds_header(b)->temp = 0; } else { - STBDS_STATS(++stbds_array_grow); + STBDS_STATS(++stbds_array_grow); } stbds_header(b)->capacity = min_cap; @@ -875,8 +875,8 @@ static size_t stbds_log2(size_t slot_count) { size_t n=0; while (slot_count > 1) { - slot_count >>= 1; - ++n; + slot_count >>= 1; + ++n; } return n; } @@ -915,7 +915,7 @@ static stbds_hash_index *stbds_make_hash_index(size_t slot_count, stbds_hash_ind t->used_count_shrink_threshold = slot_count* 6/16; // if table is only 6/16th full, shrink #endif // Following statistics were measured on a Core i7-6700 @ 4.00Ghz, compiled with clang 7.0.1 -O2 - // Note that the larger tables have high variance as they were run fewer times + // Note that the larger tables have high variance as they were run fewer times // A1 A2 B1 C1 // 0.10ms : 0.10ms : 0.10ms : 0.11ms : 2,000 inserts creating 2K table // 0.96ms : 0.95ms : 0.97ms : 1.04ms : 20,000 inserts creating 20K table @@ -930,81 +930,81 @@ static stbds_hash_index *stbds_make_hash_index(size_t slot_count, stbds_hash_ind // 192.11ms : 194.39ms : 196.38ms : 195.73ms : 500,000 inserts & deletes in 200M table if (slot_count <= STBDS_BUCKET_LENGTH) - t->used_count_shrink_threshold = 0; + t->used_count_shrink_threshold = 0; // to avoid infinite loop, we need to guarantee that at least one slot is empty and will terminate probes STBDS_ASSERT(t->used_count_threshold + t->tombstone_count_threshold < t->slot_count); STBDS_STATS(++stbds_hash_alloc); if (ot) { - t->string = ot->string; - // reuse old seed so we can reuse old hashes so below "copy out old data" doesn't do any hashing - t->seed = ot->seed; + t->string = ot->string; + // reuse old seed so we can reuse old hashes so below "copy out old data" doesn't do any hashing + t->seed = ot->seed; } else { - size_t a,b,temp; - memset(&t->string, 0, sizeof(t->string)); - t->seed = stbds_hash_seed; - // LCG - // in 32-bit, a = 2147001325 b = 715136305 - // in 64-bit, a = 2862933555777941757 b = 3037000493 - stbds_load_32_or_64(a,temp, 2147001325, 0x27bb2ee6, 0x87b0b0fd); - stbds_load_32_or_64(b,temp, 715136305, 0, 0xb504f32d); - stbds_hash_seed = stbds_hash_seed * a + b; + size_t a,b,temp; + memset(&t->string, 0, sizeof(t->string)); + t->seed = stbds_hash_seed; + // LCG + // in 32-bit, a = 2147001325 b = 715136305 + // in 64-bit, a = 2862933555777941757 b = 3037000493 + stbds_load_32_or_64(a,temp, 2147001325, 0x27bb2ee6, 0x87b0b0fd); + stbds_load_32_or_64(b,temp, 715136305, 0, 0xb504f32d); + stbds_hash_seed = stbds_hash_seed * a + b; } { - size_t i,j; - for (i=0; i < slot_count >> STBDS_BUCKET_SHIFT; ++i) { - stbds_hash_bucket *b = &t->storage[i]; - for (j=0; j < STBDS_BUCKET_LENGTH; ++j) - b->hash[j] = STBDS_HASH_EMPTY; - for (j=0; j < STBDS_BUCKET_LENGTH; ++j) - b->index[j] = STBDS_INDEX_EMPTY; - } + size_t i,j; + for (i=0; i < slot_count >> STBDS_BUCKET_SHIFT; ++i) { + stbds_hash_bucket *b = &t->storage[i]; + for (j=0; j < STBDS_BUCKET_LENGTH; ++j) + b->hash[j] = STBDS_HASH_EMPTY; + for (j=0; j < STBDS_BUCKET_LENGTH; ++j) + b->index[j] = STBDS_INDEX_EMPTY; + } } // copy out the old data, if any if (ot) { - size_t i,j; - t->used_count = ot->used_count; - for (i=0; i < ot->slot_count >> STBDS_BUCKET_SHIFT; ++i) { - stbds_hash_bucket *ob = &ot->storage[i]; - for (j=0; j < STBDS_BUCKET_LENGTH; ++j) { - if (STBDS_INDEX_IN_USE(ob->index[j])) { - size_t hash = ob->hash[j]; - size_t pos = stbds_probe_position(hash, t->slot_count, t->slot_count_log2); - size_t step = STBDS_BUCKET_LENGTH; - STBDS_STATS(++stbds_rehash_items); - for (;;) { - size_t limit,z; - stbds_hash_bucket *bucket; - bucket = &t->storage[pos >> STBDS_BUCKET_SHIFT]; - STBDS_STATS(++stbds_rehash_probes); + size_t i,j; + t->used_count = ot->used_count; + for (i=0; i < ot->slot_count >> STBDS_BUCKET_SHIFT; ++i) { + stbds_hash_bucket *ob = &ot->storage[i]; + for (j=0; j < STBDS_BUCKET_LENGTH; ++j) { + if (STBDS_INDEX_IN_USE(ob->index[j])) { + size_t hash = ob->hash[j]; + size_t pos = stbds_probe_position(hash, t->slot_count, t->slot_count_log2); + size_t step = STBDS_BUCKET_LENGTH; + STBDS_STATS(++stbds_rehash_items); + for (;;) { + size_t limit,z; + stbds_hash_bucket *bucket; + bucket = &t->storage[pos >> STBDS_BUCKET_SHIFT]; + STBDS_STATS(++stbds_rehash_probes); - for (z=pos & STBDS_BUCKET_MASK; z < STBDS_BUCKET_LENGTH; ++z) { - if (bucket->hash[z] == 0) { - bucket->hash[z] = hash; - bucket->index[z] = ob->index[j]; - goto done; - } - } + for (z=pos & STBDS_BUCKET_MASK; z < STBDS_BUCKET_LENGTH; ++z) { + if (bucket->hash[z] == 0) { + bucket->hash[z] = hash; + bucket->index[z] = ob->index[j]; + goto done; + } + } - limit = pos & STBDS_BUCKET_MASK; - for (z = 0; z < limit; ++z) { - if (bucket->hash[z] == 0) { - bucket->hash[z] = hash; - bucket->index[z] = ob->index[j]; - goto done; - } - } + limit = pos & STBDS_BUCKET_MASK; + for (z = 0; z < limit; ++z) { + if (bucket->hash[z] == 0) { + bucket->hash[z] = hash; + bucket->index[z] = ob->index[j]; + goto done; + } + } - pos += step; // quadratic probing - step += STBDS_BUCKET_LENGTH; - pos &= (t->slot_count-1); - } - } - done: - ; - } - } + pos += step; // quadratic probing + step += STBDS_BUCKET_LENGTH; + pos &= (t->slot_count-1); + } + } + done: + ; + } + } } return t; @@ -1017,7 +1017,7 @@ size_t stbds_hash_string(char *str, size_t seed) { size_t hash = seed; while (*str) - hash = STBDS_ROTATE_LEFT(hash, 9) + (unsigned char) *str++; + hash = STBDS_ROTATE_LEFT(hash, 9) + (unsigned char) *str++; // Thomas Wang 64-to-32 bit mix function, hopefully also works in 32 bits hash ^= seed; @@ -1071,40 +1071,40 @@ static size_t stbds_siphash_bytes(void *p, size_t len, size_t seed) #endif #define STBDS_SIPROUND() \ - do { \ - v0 += v1; v1 = STBDS_ROTATE_LEFT(v1, 13); v1 ^= v0; v0 = STBDS_ROTATE_LEFT(v0,STBDS_SIZE_T_BITS/2); \ - v2 += v3; v3 = STBDS_ROTATE_LEFT(v3, 16); v3 ^= v2; \ - v2 += v1; v1 = STBDS_ROTATE_LEFT(v1, 17); v1 ^= v2; v2 = STBDS_ROTATE_LEFT(v2,STBDS_SIZE_T_BITS/2); \ - v0 += v3; v3 = STBDS_ROTATE_LEFT(v3, 21); v3 ^= v0; \ - } while (0) + do { \ + v0 += v1; v1 = STBDS_ROTATE_LEFT(v1, 13); v1 ^= v0; v0 = STBDS_ROTATE_LEFT(v0,STBDS_SIZE_T_BITS/2); \ + v2 += v3; v3 = STBDS_ROTATE_LEFT(v3, 16); v3 ^= v2; \ + v2 += v1; v1 = STBDS_ROTATE_LEFT(v1, 17); v1 ^= v2; v2 = STBDS_ROTATE_LEFT(v2,STBDS_SIZE_T_BITS/2); \ + v0 += v3; v3 = STBDS_ROTATE_LEFT(v3, 21); v3 ^= v0; \ + } while (0) for (i=0; i+sizeof(size_t) <= len; i += sizeof(size_t), d += sizeof(size_t)) { - data = d[0] | (d[1] << 8) | (d[2] << 16) | (d[3] << 24); - data |= (size_t) (d[4] | (d[5] << 8) | (d[6] << 16) | (d[7] << 24)) << 16 << 16; // discarded if size_t == 4 + data = d[0] | (d[1] << 8) | (d[2] << 16) | (d[3] << 24); + data |= (size_t) (d[4] | (d[5] << 8) | (d[6] << 16) | (d[7] << 24)) << 16 << 16; // discarded if size_t == 4 - v3 ^= data; - for (j=0; j < STBDS_SIPHASH_C_ROUNDS; ++j) - STBDS_SIPROUND(); - v0 ^= data; + v3 ^= data; + for (j=0; j < STBDS_SIPHASH_C_ROUNDS; ++j) + STBDS_SIPROUND(); + v0 ^= data; } data = len << (STBDS_SIZE_T_BITS-8); switch (len - i) { - case 7: data |= ((size_t) d[6] << 24) << 24; // fall through - case 6: data |= ((size_t) d[5] << 20) << 20; // fall through - case 5: data |= ((size_t) d[4] << 16) << 16; // fall through - case 4: data |= (d[3] << 24); // fall through - case 3: data |= (d[2] << 16); // fall through - case 2: data |= (d[1] << 8); // fall through - case 1: data |= d[0]; // fall through - case 0: break; + case 7: data |= ((size_t) d[6] << 24) << 24; // fall through + case 6: data |= ((size_t) d[5] << 20) << 20; // fall through + case 5: data |= ((size_t) d[4] << 16) << 16; // fall through + case 4: data |= (d[3] << 24); // fall through + case 3: data |= (d[2] << 16); // fall through + case 2: data |= (d[1] << 8); // fall through + case 1: data |= d[0]; // fall through + case 0: break; } v3 ^= data; for (j=0; j < STBDS_SIPHASH_C_ROUNDS; ++j) - STBDS_SIPROUND(); + STBDS_SIPROUND(); v0 ^= data; v2 ^= 0xff; for (j=0; j < STBDS_SIPHASH_D_ROUNDS; ++j) - STBDS_SIPROUND(); + STBDS_SIPROUND(); #ifdef STBDS_SIPHASH_2_4 return v0^v1^v2^v3; @@ -1121,77 +1121,77 @@ size_t stbds_hash_bytes(void *p, size_t len, size_t seed) unsigned char *d = (unsigned char *) p; if (len == 4) { - unsigned int hash = d[0] | (d[1] << 8) | (d[2] << 16) | (d[3] << 24); - #if 0 - // HASH32-A Bob Jenkin's hash function w/o large constants - hash ^= seed; - hash -= (hash<<6); - hash ^= (hash>>17); - hash -= (hash<<9); - hash ^= seed; - hash ^= (hash<<4); - hash -= (hash<<3); - hash ^= (hash<<10); - hash ^= (hash>>15); - #elif 1 - // HASH32-BB Bob Jenkin's presumably-accidental version of Thomas Wang hash with rotates turned into shifts. - // Note that converting these back to rotates makes it run a lot slower, presumably due to collisions, so I'm - // not really sure what's going on. - hash ^= seed; - hash = (hash ^ 61) ^ (hash >> 16); - hash = hash + (hash << 3); - hash = hash ^ (hash >> 4); - hash = hash * 0x27d4eb2d; - hash ^= seed; - hash = hash ^ (hash >> 15); - #else // HASH32-C - Murmur3 - hash ^= seed; - hash *= 0xcc9e2d51; - hash = (hash << 17) | (hash >> 15); - hash *= 0x1b873593; - hash ^= seed; - hash = (hash << 19) | (hash >> 13); - hash = hash*5 + 0xe6546b64; - hash ^= hash >> 16; - hash *= 0x85ebca6b; - hash ^= seed; - hash ^= hash >> 13; - hash *= 0xc2b2ae35; - hash ^= hash >> 16; - #endif - // Following statistics were measured on a Core i7-6700 @ 4.00Ghz, compiled with clang 7.0.1 -O2 - // Note that the larger tables have high variance as they were run fewer times - // HASH32-A // HASH32-BB // HASH32-C - // 0.10ms // 0.10ms // 0.10ms : 2,000 inserts creating 2K table - // 0.96ms // 0.95ms // 0.99ms : 20,000 inserts creating 20K table - // 14.69ms // 14.43ms // 14.97ms : 200,000 inserts creating 200K table - // 199.99ms // 195.36ms // 202.05ms : 2,000,000 inserts creating 2M table - // 2234.84ms // 2187.74ms // 2240.38ms : 20,000,000 inserts creating 20M table - // 55.68ms // 53.72ms // 57.31ms : 500,000 inserts & deletes in 2K table - // 63.43ms // 61.99ms // 65.73ms : 500,000 inserts & deletes in 20K table - // 80.04ms // 77.96ms // 81.83ms : 500,000 inserts & deletes in 200K table - // 100.42ms // 97.40ms // 102.39ms : 500,000 inserts & deletes in 2M table - // 119.71ms // 120.59ms // 121.63ms : 500,000 inserts & deletes in 20M table - // 185.28ms // 195.15ms // 187.74ms : 500,000 inserts & deletes in 200M table - // 15.58ms // 14.79ms // 15.52ms : 200,000 inserts creating 200K table with varying key spacing + unsigned int hash = d[0] | (d[1] << 8) | (d[2] << 16) | (d[3] << 24); + #if 0 + // HASH32-A Bob Jenkin's hash function w/o large constants + hash ^= seed; + hash -= (hash<<6); + hash ^= (hash>>17); + hash -= (hash<<9); + hash ^= seed; + hash ^= (hash<<4); + hash -= (hash<<3); + hash ^= (hash<<10); + hash ^= (hash>>15); + #elif 1 + // HASH32-BB Bob Jenkin's presumably-accidental version of Thomas Wang hash with rotates turned into shifts. + // Note that converting these back to rotates makes it run a lot slower, presumably due to collisions, so I'm + // not really sure what's going on. + hash ^= seed; + hash = (hash ^ 61) ^ (hash >> 16); + hash = hash + (hash << 3); + hash = hash ^ (hash >> 4); + hash = hash * 0x27d4eb2d; + hash ^= seed; + hash = hash ^ (hash >> 15); + #else // HASH32-C - Murmur3 + hash ^= seed; + hash *= 0xcc9e2d51; + hash = (hash << 17) | (hash >> 15); + hash *= 0x1b873593; + hash ^= seed; + hash = (hash << 19) | (hash >> 13); + hash = hash*5 + 0xe6546b64; + hash ^= hash >> 16; + hash *= 0x85ebca6b; + hash ^= seed; + hash ^= hash >> 13; + hash *= 0xc2b2ae35; + hash ^= hash >> 16; + #endif + // Following statistics were measured on a Core i7-6700 @ 4.00Ghz, compiled with clang 7.0.1 -O2 + // Note that the larger tables have high variance as they were run fewer times + // HASH32-A // HASH32-BB // HASH32-C + // 0.10ms // 0.10ms // 0.10ms : 2,000 inserts creating 2K table + // 0.96ms // 0.95ms // 0.99ms : 20,000 inserts creating 20K table + // 14.69ms // 14.43ms // 14.97ms : 200,000 inserts creating 200K table + // 199.99ms // 195.36ms // 202.05ms : 2,000,000 inserts creating 2M table + // 2234.84ms // 2187.74ms // 2240.38ms : 20,000,000 inserts creating 20M table + // 55.68ms // 53.72ms // 57.31ms : 500,000 inserts & deletes in 2K table + // 63.43ms // 61.99ms // 65.73ms : 500,000 inserts & deletes in 20K table + // 80.04ms // 77.96ms // 81.83ms : 500,000 inserts & deletes in 200K table + // 100.42ms // 97.40ms // 102.39ms : 500,000 inserts & deletes in 2M table + // 119.71ms // 120.59ms // 121.63ms : 500,000 inserts & deletes in 20M table + // 185.28ms // 195.15ms // 187.74ms : 500,000 inserts & deletes in 200M table + // 15.58ms // 14.79ms // 15.52ms : 200,000 inserts creating 200K table with varying key spacing - return (((size_t) hash << 16 << 16) | hash) ^ seed; + return (((size_t) hash << 16 << 16) | hash) ^ seed; } else if (len == 8 && sizeof(size_t) == 8) { - size_t hash = d[0] | (d[1] << 8) | (d[2] << 16) | (d[3] << 24); - hash |= (size_t) (d[4] | (d[5] << 8) | (d[6] << 16) | (d[7] << 24)) << 16 << 16; // avoid warning if size_t == 4 - hash ^= seed; - hash = (~hash) + (hash << 21); - hash ^= STBDS_ROTATE_RIGHT(hash,24); - hash *= 265; - hash ^= STBDS_ROTATE_RIGHT(hash,14); - hash ^= seed; - hash *= 21; - hash ^= STBDS_ROTATE_RIGHT(hash,28); - hash += (hash << 31); - hash = (~hash) + (hash << 18); - return hash; + size_t hash = d[0] | (d[1] << 8) | (d[2] << 16) | (d[3] << 24); + hash |= (size_t) (d[4] | (d[5] << 8) | (d[6] << 16) | (d[7] << 24)) << 16 << 16; // avoid warning if size_t == 4 + hash ^= seed; + hash = (~hash) + (hash << 21); + hash ^= STBDS_ROTATE_RIGHT(hash,24); + hash *= 265; + hash ^= STBDS_ROTATE_RIGHT(hash,14); + hash ^= seed; + hash *= 21; + hash ^= STBDS_ROTATE_RIGHT(hash,28); + hash += (hash << 31); + hash = (~hash) + (hash << 18); + return hash; } else { - return stbds_siphash_bytes(p,len,seed); + return stbds_siphash_bytes(p,len,seed); } #endif } @@ -1203,9 +1203,9 @@ size_t stbds_hash_bytes(void *p, size_t len, size_t seed) static int stbds_is_key_equal(void *a, size_t elemsize, void *key, size_t keysize, size_t keyoffset, int mode, size_t i) { if (mode >= STBDS_HM_STRING) - return 0==strcmp((char *) key, * (char **) ((char *) a + elemsize*i + keyoffset)); + return 0==strcmp((char *) key, * (char **) ((char *) a + elemsize*i + keyoffset)); else - return 0==memcmp(key, (char *) a + elemsize*i + keyoffset, keysize); + return 0==memcmp(key, (char *) a + elemsize*i + keyoffset, keysize); } #define STBDS_HASH_TO_ARR(x,elemsize) ((char*) (x) - (elemsize)) @@ -1217,13 +1217,13 @@ void stbds_hmfree_func(void *a, size_t elemsize) { if (a == NULL) return; if (stbds_hash_table(a) != NULL) { - if (stbds_hash_table(a)->string.mode == STBDS_SH_STRDUP) { - size_t i; - // skip 0th element, which is default - for (i=1; i < stbds_header(a)->length; ++i) - STBDS_FREE(NULL, *(char**) ((char *) a + elemsize*i)); - } - stbds_strreset(&stbds_hash_table(a)->string); + if (stbds_hash_table(a)->string.mode == STBDS_SH_STRDUP) { + size_t i; + // skip 0th element, which is default + for (i=1; i < stbds_header(a)->length; ++i) + STBDS_FREE(NULL, *(char**) ((char *) a + elemsize*i)); + } + stbds_strreset(&stbds_hash_table(a)->string); } STBDS_FREE(NULL, stbds_header(a)->hash_table); STBDS_FREE(NULL, stbds_header(a)); @@ -1244,36 +1244,36 @@ static ptrdiff_t stbds_hm_find_slot(void *a, size_t elemsize, void *key, size_t pos = stbds_probe_position(hash, table->slot_count, table->slot_count_log2); for (;;) { - STBDS_STATS(++stbds_hash_probes); - bucket = &table->storage[pos >> STBDS_BUCKET_SHIFT]; + STBDS_STATS(++stbds_hash_probes); + bucket = &table->storage[pos >> STBDS_BUCKET_SHIFT]; - // start searching from pos to end of bucket, this should help performance on small hash tables that fit in cache - for (i=pos & STBDS_BUCKET_MASK; i < STBDS_BUCKET_LENGTH; ++i) { - if (bucket->hash[i] == hash) { - if (stbds_is_key_equal(a, elemsize, key, keysize, keyoffset, mode, bucket->index[i])) { - return (pos & ~STBDS_BUCKET_MASK)+i; - } - } else if (bucket->hash[i] == STBDS_HASH_EMPTY) { - return -1; - } - } + // start searching from pos to end of bucket, this should help performance on small hash tables that fit in cache + for (i=pos & STBDS_BUCKET_MASK; i < STBDS_BUCKET_LENGTH; ++i) { + if (bucket->hash[i] == hash) { + if (stbds_is_key_equal(a, elemsize, key, keysize, keyoffset, mode, bucket->index[i])) { + return (pos & ~STBDS_BUCKET_MASK)+i; + } + } else if (bucket->hash[i] == STBDS_HASH_EMPTY) { + return -1; + } + } - // search from beginning of bucket to pos - limit = pos & STBDS_BUCKET_MASK; - for (i = 0; i < limit; ++i) { - if (bucket->hash[i] == hash) { - if (stbds_is_key_equal(a, elemsize, key, keysize, keyoffset, mode, bucket->index[i])) { - return (pos & ~STBDS_BUCKET_MASK)+i; - } - } else if (bucket->hash[i] == STBDS_HASH_EMPTY) { - return -1; - } - } + // search from beginning of bucket to pos + limit = pos & STBDS_BUCKET_MASK; + for (i = 0; i < limit; ++i) { + if (bucket->hash[i] == hash) { + if (stbds_is_key_equal(a, elemsize, key, keysize, keyoffset, mode, bucket->index[i])) { + return (pos & ~STBDS_BUCKET_MASK)+i; + } + } else if (bucket->hash[i] == STBDS_HASH_EMPTY) { + return -1; + } + } - // quadratic probing - pos += step; - step += STBDS_BUCKET_LENGTH; - pos &= (table->slot_count-1); + // quadratic probing + pos += step; + step += STBDS_BUCKET_LENGTH; + pos &= (table->slot_count-1); } /* NOTREACHED */ } @@ -1282,30 +1282,30 @@ void * stbds_hmget_key_ts(void *a, size_t elemsize, void *key, size_t keysize, p { size_t keyoffset = 0; if (a == NULL) { - // make it non-empty so we can return a temp - a = stbds_arrgrowf(0, elemsize, 0, 1); - stbds_header(a)->length += 1; - memset(a, 0, elemsize); - *temp = STBDS_INDEX_EMPTY; - // adjust a to point after the default element - return STBDS_ARR_TO_HASH(a,elemsize); + // make it non-empty so we can return a temp + a = stbds_arrgrowf(0, elemsize, 0, 1); + stbds_header(a)->length += 1; + memset(a, 0, elemsize); + *temp = STBDS_INDEX_EMPTY; + // adjust a to point after the default element + return STBDS_ARR_TO_HASH(a,elemsize); } else { - stbds_hash_index *table; - void *raw_a = STBDS_HASH_TO_ARR(a,elemsize); - // adjust a to point to the default element - table = (stbds_hash_index *) stbds_header(raw_a)->hash_table; - if (table == 0) { - *temp = -1; - } else { - ptrdiff_t slot = stbds_hm_find_slot(a, elemsize, key, keysize, keyoffset, mode); - if (slot < 0) { - *temp = STBDS_INDEX_EMPTY; - } else { - stbds_hash_bucket *b = &table->storage[slot >> STBDS_BUCKET_SHIFT]; - *temp = b->index[slot & STBDS_BUCKET_MASK]; - } - } - return a; + stbds_hash_index *table; + void *raw_a = STBDS_HASH_TO_ARR(a,elemsize); + // adjust a to point to the default element + table = (stbds_hash_index *) stbds_header(raw_a)->hash_table; + if (table == 0) { + *temp = -1; + } else { + ptrdiff_t slot = stbds_hm_find_slot(a, elemsize, key, keysize, keyoffset, mode); + if (slot < 0) { + *temp = STBDS_INDEX_EMPTY; + } else { + stbds_hash_bucket *b = &table->storage[slot >> STBDS_BUCKET_SHIFT]; + *temp = b->index[slot & STBDS_BUCKET_MASK]; + } + } + return a; } } @@ -1324,10 +1324,10 @@ void * stbds_hmput_default(void *a, size_t elemsize) // a has a hash table but no entries, because of shmode <- grow // a has entries <- do nothing if (a == NULL || stbds_header(STBDS_HASH_TO_ARR(a,elemsize))->length == 0) { - a = stbds_arrgrowf(a ? STBDS_HASH_TO_ARR(a,elemsize) : NULL, elemsize, 0, 1); - stbds_header(a)->length += 1; - memset(a, 0, elemsize); - a=STBDS_ARR_TO_HASH(a,elemsize); + a = stbds_arrgrowf(a ? STBDS_HASH_TO_ARR(a,elemsize) : NULL, elemsize, 0, 1); + stbds_header(a)->length += 1; + memset(a, 0, elemsize); + a=STBDS_ARR_TO_HASH(a,elemsize); } return a; } @@ -1341,11 +1341,11 @@ void *stbds_hmput_key(void *a, size_t elemsize, void *key, size_t keysize, int m stbds_hash_index *table; if (a == NULL) { - a = stbds_arrgrowf(0, elemsize, 0, 1); - memset(a, 0, elemsize); - stbds_header(a)->length += 1; - // adjust a to point AFTER the default element - a = STBDS_ARR_TO_HASH(a,elemsize); + a = stbds_arrgrowf(0, elemsize, 0, 1); + memset(a, 0, elemsize); + stbds_header(a)->length += 1; + // adjust a to point AFTER the default element + a = STBDS_ARR_TO_HASH(a,elemsize); } // adjust a to point to the default element @@ -1355,106 +1355,106 @@ void *stbds_hmput_key(void *a, size_t elemsize, void *key, size_t keysize, int m table = (stbds_hash_index *) stbds_header(a)->hash_table; if (table == NULL || table->used_count >= table->used_count_threshold) { - stbds_hash_index *nt; - size_t slot_count; + stbds_hash_index *nt; + size_t slot_count; - slot_count = (table == NULL) ? STBDS_BUCKET_LENGTH : table->slot_count*2; - nt = stbds_make_hash_index(slot_count, table); - if (table) - STBDS_FREE(NULL, table); - else - nt->string.mode = mode >= STBDS_HM_STRING ? STBDS_SH_DEFAULT : 0; - stbds_header(a)->hash_table = table = nt; - STBDS_STATS(++stbds_hash_grow); + slot_count = (table == NULL) ? STBDS_BUCKET_LENGTH : table->slot_count*2; + nt = stbds_make_hash_index(slot_count, table); + if (table) + STBDS_FREE(NULL, table); + else + nt->string.mode = mode >= STBDS_HM_STRING ? STBDS_SH_DEFAULT : 0; + stbds_header(a)->hash_table = table = nt; + STBDS_STATS(++stbds_hash_grow); } // we iterate hash table explicitly because we want to track if we saw a tombstone { - size_t hash = mode >= STBDS_HM_STRING ? stbds_hash_string((char*)key,table->seed) : stbds_hash_bytes(key, keysize,table->seed); - size_t step = STBDS_BUCKET_LENGTH; - size_t pos; - ptrdiff_t tombstone = -1; - stbds_hash_bucket *bucket; + size_t hash = mode >= STBDS_HM_STRING ? stbds_hash_string((char*)key,table->seed) : stbds_hash_bytes(key, keysize,table->seed); + size_t step = STBDS_BUCKET_LENGTH; + size_t pos; + ptrdiff_t tombstone = -1; + stbds_hash_bucket *bucket; - // stored hash values are forbidden from being 0, so we can detect empty slots to early out quickly - if (hash < 2) hash += 2; + // stored hash values are forbidden from being 0, so we can detect empty slots to early out quickly + if (hash < 2) hash += 2; - pos = stbds_probe_position(hash, table->slot_count, table->slot_count_log2); + pos = stbds_probe_position(hash, table->slot_count, table->slot_count_log2); - for (;;) { - size_t limit, i; - STBDS_STATS(++stbds_hash_probes); - bucket = &table->storage[pos >> STBDS_BUCKET_SHIFT]; + for (;;) { + size_t limit, i; + STBDS_STATS(++stbds_hash_probes); + bucket = &table->storage[pos >> STBDS_BUCKET_SHIFT]; - // start searching from pos to end of bucket - for (i=pos & STBDS_BUCKET_MASK; i < STBDS_BUCKET_LENGTH; ++i) { - if (bucket->hash[i] == hash) { - if (stbds_is_key_equal(raw_a, elemsize, key, keysize, keyoffset, mode, bucket->index[i])) { - stbds_temp(a) = bucket->index[i]; - if (mode >= STBDS_HM_STRING) - stbds_temp_key(a) = * (char **) ((char *) raw_a + elemsize*bucket->index[i] + keyoffset); - return STBDS_ARR_TO_HASH(a,elemsize); - } - } else if (bucket->hash[i] == 0) { - pos = (pos & ~STBDS_BUCKET_MASK) + i; - goto found_empty_slot; - } else if (tombstone < 0) { - if (bucket->index[i] == STBDS_INDEX_DELETED) - tombstone = (ptrdiff_t) ((pos & ~STBDS_BUCKET_MASK) + i); - } - } + // start searching from pos to end of bucket + for (i=pos & STBDS_BUCKET_MASK; i < STBDS_BUCKET_LENGTH; ++i) { + if (bucket->hash[i] == hash) { + if (stbds_is_key_equal(raw_a, elemsize, key, keysize, keyoffset, mode, bucket->index[i])) { + stbds_temp(a) = bucket->index[i]; + if (mode >= STBDS_HM_STRING) + stbds_temp_key(a) = * (char **) ((char *) raw_a + elemsize*bucket->index[i] + keyoffset); + return STBDS_ARR_TO_HASH(a,elemsize); + } + } else if (bucket->hash[i] == 0) { + pos = (pos & ~STBDS_BUCKET_MASK) + i; + goto found_empty_slot; + } else if (tombstone < 0) { + if (bucket->index[i] == STBDS_INDEX_DELETED) + tombstone = (ptrdiff_t) ((pos & ~STBDS_BUCKET_MASK) + i); + } + } - // search from beginning of bucket to pos - limit = pos & STBDS_BUCKET_MASK; - for (i = 0; i < limit; ++i) { - if (bucket->hash[i] == hash) { - if (stbds_is_key_equal(raw_a, elemsize, key, keysize, keyoffset, mode, bucket->index[i])) { - stbds_temp(a) = bucket->index[i]; - return STBDS_ARR_TO_HASH(a,elemsize); - } - } else if (bucket->hash[i] == 0) { - pos = (pos & ~STBDS_BUCKET_MASK) + i; - goto found_empty_slot; - } else if (tombstone < 0) { - if (bucket->index[i] == STBDS_INDEX_DELETED) - tombstone = (ptrdiff_t) ((pos & ~STBDS_BUCKET_MASK) + i); - } - } + // search from beginning of bucket to pos + limit = pos & STBDS_BUCKET_MASK; + for (i = 0; i < limit; ++i) { + if (bucket->hash[i] == hash) { + if (stbds_is_key_equal(raw_a, elemsize, key, keysize, keyoffset, mode, bucket->index[i])) { + stbds_temp(a) = bucket->index[i]; + return STBDS_ARR_TO_HASH(a,elemsize); + } + } else if (bucket->hash[i] == 0) { + pos = (pos & ~STBDS_BUCKET_MASK) + i; + goto found_empty_slot; + } else if (tombstone < 0) { + if (bucket->index[i] == STBDS_INDEX_DELETED) + tombstone = (ptrdiff_t) ((pos & ~STBDS_BUCKET_MASK) + i); + } + } - // quadratic probing - pos += step; - step += STBDS_BUCKET_LENGTH; - pos &= (table->slot_count-1); - } + // quadratic probing + pos += step; + step += STBDS_BUCKET_LENGTH; + pos &= (table->slot_count-1); + } found_empty_slot: - if (tombstone >= 0) { - pos = tombstone; - --table->tombstone_count; - } - ++table->used_count; + if (tombstone >= 0) { + pos = tombstone; + --table->tombstone_count; + } + ++table->used_count; - { - ptrdiff_t i = (ptrdiff_t) stbds_arrlen(a); - // we want to do stbds_arraddn(1), but we can't use the macros since we don't have something of the right type - if ((size_t) i+1 > stbds_arrcap(a)) - *(void **) &a = stbds_arrgrowf(a, elemsize, 1, 0); - raw_a = STBDS_ARR_TO_HASH(a,elemsize); + { + ptrdiff_t i = (ptrdiff_t) stbds_arrlen(a); + // we want to do stbds_arraddn(1), but we can't use the macros since we don't have something of the right type + if ((size_t) i+1 > stbds_arrcap(a)) + *(void **) &a = stbds_arrgrowf(a, elemsize, 1, 0); + raw_a = STBDS_ARR_TO_HASH(a,elemsize); - STBDS_ASSERT((size_t) i+1 <= stbds_arrcap(a)); - stbds_header(a)->length = i+1; - bucket = &table->storage[pos >> STBDS_BUCKET_SHIFT]; - bucket->hash[pos & STBDS_BUCKET_MASK] = hash; - bucket->index[pos & STBDS_BUCKET_MASK] = i-1; - stbds_temp(a) = i-1; + STBDS_ASSERT((size_t) i+1 <= stbds_arrcap(a)); + stbds_header(a)->length = i+1; + bucket = &table->storage[pos >> STBDS_BUCKET_SHIFT]; + bucket->hash[pos & STBDS_BUCKET_MASK] = hash; + bucket->index[pos & STBDS_BUCKET_MASK] = i-1; + stbds_temp(a) = i-1; - switch (table->string.mode) { - case STBDS_SH_STRDUP: stbds_temp_key(a) = *(char **) ((char *) a + elemsize*i) = stbds_strdup((char*) key); break; - case STBDS_SH_ARENA: stbds_temp_key(a) = *(char **) ((char *) a + elemsize*i) = stbds_stralloc(&table->string, (char*)key); break; - case STBDS_SH_DEFAULT: stbds_temp_key(a) = *(char **) ((char *) a + elemsize*i) = (char *) key; break; - default: memcpy((char *) a + elemsize*i, key, keysize); break; - } - } - return STBDS_ARR_TO_HASH(a,elemsize); + switch (table->string.mode) { + case STBDS_SH_STRDUP: stbds_temp_key(a) = *(char **) ((char *) a + elemsize*i) = stbds_strdup((char*) key); break; + case STBDS_SH_ARENA: stbds_temp_key(a) = *(char **) ((char *) a + elemsize*i) = stbds_stralloc(&table->string, (char*)key); break; + case STBDS_SH_DEFAULT: stbds_temp_key(a) = *(char **) ((char *) a + elemsize*i) = (char *) key; break; + default: memcpy((char *) a + elemsize*i, key, keysize); break; + } + } + return STBDS_ARR_TO_HASH(a,elemsize); } } @@ -1472,67 +1472,67 @@ void * stbds_shmode_func(size_t elemsize, int mode) void * stbds_hmdel_key(void *a, size_t elemsize, void *key, size_t keysize, size_t keyoffset, int mode) { if (a == NULL) { - return 0; + return 0; } else { - stbds_hash_index *table; - void *raw_a = STBDS_HASH_TO_ARR(a,elemsize); - table = (stbds_hash_index *) stbds_header(raw_a)->hash_table; - stbds_temp(raw_a) = 0; - if (table == 0) { - return a; - } else { - ptrdiff_t slot; - slot = stbds_hm_find_slot(a, elemsize, key, keysize, keyoffset, mode); - if (slot < 0) - return a; - else { - stbds_hash_bucket *b = &table->storage[slot >> STBDS_BUCKET_SHIFT]; - int i = slot & STBDS_BUCKET_MASK; - ptrdiff_t old_index = b->index[i]; - ptrdiff_t final_index = (ptrdiff_t) stbds_arrlen(raw_a)-1-1; // minus one for the raw_a vs a, and minus one for 'last' - STBDS_ASSERT(slot < (ptrdiff_t) table->slot_count); - --table->used_count; - ++table->tombstone_count; - stbds_temp(raw_a) = 1; - STBDS_ASSERT(table->used_count >= 0); - //STBDS_ASSERT(table->tombstone_count < table->slot_count/4); - b->hash[i] = STBDS_HASH_DELETED; - b->index[i] = STBDS_INDEX_DELETED; + stbds_hash_index *table; + void *raw_a = STBDS_HASH_TO_ARR(a,elemsize); + table = (stbds_hash_index *) stbds_header(raw_a)->hash_table; + stbds_temp(raw_a) = 0; + if (table == 0) { + return a; + } else { + ptrdiff_t slot; + slot = stbds_hm_find_slot(a, elemsize, key, keysize, keyoffset, mode); + if (slot < 0) + return a; + else { + stbds_hash_bucket *b = &table->storage[slot >> STBDS_BUCKET_SHIFT]; + int i = slot & STBDS_BUCKET_MASK; + ptrdiff_t old_index = b->index[i]; + ptrdiff_t final_index = (ptrdiff_t) stbds_arrlen(raw_a)-1-1; // minus one for the raw_a vs a, and minus one for 'last' + STBDS_ASSERT(slot < (ptrdiff_t) table->slot_count); + --table->used_count; + ++table->tombstone_count; + stbds_temp(raw_a) = 1; + STBDS_ASSERT(table->used_count >= 0); + //STBDS_ASSERT(table->tombstone_count < table->slot_count/4); + b->hash[i] = STBDS_HASH_DELETED; + b->index[i] = STBDS_INDEX_DELETED; - if (mode == STBDS_HM_STRING && table->string.mode == STBDS_SH_STRDUP) - STBDS_FREE(NULL, *(char**) ((char *) a+elemsize*old_index)); + if (mode == STBDS_HM_STRING && table->string.mode == STBDS_SH_STRDUP) + STBDS_FREE(NULL, *(char**) ((char *) a+elemsize*old_index)); - // if indices are the same, memcpy is a no-op, but back-pointer-fixup will fail, so skip - if (old_index != final_index) { - // swap delete - memmove((char*) a + elemsize*old_index, (char*) a + elemsize*final_index, elemsize); + // if indices are the same, memcpy is a no-op, but back-pointer-fixup will fail, so skip + if (old_index != final_index) { + // swap delete + memmove((char*) a + elemsize*old_index, (char*) a + elemsize*final_index, elemsize); - // now find the slot for the last element - if (mode == STBDS_HM_STRING) - slot = stbds_hm_find_slot(a, elemsize, *(char**) ((char *) a+elemsize*old_index + keyoffset), keysize, keyoffset, mode); - else - slot = stbds_hm_find_slot(a, elemsize, (char* ) a+elemsize*old_index + keyoffset, keysize, keyoffset, mode); - STBDS_ASSERT(slot >= 0); - b = &table->storage[slot >> STBDS_BUCKET_SHIFT]; - i = slot & STBDS_BUCKET_MASK; - STBDS_ASSERT(b->index[i] == final_index); - b->index[i] = old_index; - } - stbds_header(raw_a)->length -= 1; + // now find the slot for the last element + if (mode == STBDS_HM_STRING) + slot = stbds_hm_find_slot(a, elemsize, *(char**) ((char *) a+elemsize*old_index + keyoffset), keysize, keyoffset, mode); + else + slot = stbds_hm_find_slot(a, elemsize, (char* ) a+elemsize*old_index + keyoffset, keysize, keyoffset, mode); + STBDS_ASSERT(slot >= 0); + b = &table->storage[slot >> STBDS_BUCKET_SHIFT]; + i = slot & STBDS_BUCKET_MASK; + STBDS_ASSERT(b->index[i] == final_index); + b->index[i] = old_index; + } + stbds_header(raw_a)->length -= 1; - if (table->used_count < table->used_count_shrink_threshold && table->slot_count > STBDS_BUCKET_LENGTH) { - stbds_header(raw_a)->hash_table = stbds_make_hash_index(table->slot_count>>1, table); - STBDS_FREE(NULL, table); - STBDS_STATS(++stbds_hash_shrink); - } else if (table->tombstone_count > table->tombstone_count_threshold) { - stbds_header(raw_a)->hash_table = stbds_make_hash_index(table->slot_count , table); - STBDS_FREE(NULL, table); - STBDS_STATS(++stbds_hash_rebuild); - } + if (table->used_count < table->used_count_shrink_threshold && table->slot_count > STBDS_BUCKET_LENGTH) { + stbds_header(raw_a)->hash_table = stbds_make_hash_index(table->slot_count>>1, table); + STBDS_FREE(NULL, table); + STBDS_STATS(++stbds_hash_shrink); + } else if (table->tombstone_count > table->tombstone_count_threshold) { + stbds_header(raw_a)->hash_table = stbds_make_hash_index(table->slot_count , table); + STBDS_FREE(NULL, table); + STBDS_STATS(++stbds_hash_rebuild); + } - return a; - } - } + return a; + } + } } /* NOTREACHED */ } @@ -1559,40 +1559,40 @@ char *stbds_stralloc(stbds_string_arena *a, char *str) char *p; size_t len = strlen(str)+1; if (len > a->remaining) { - // compute the next blocksize - size_t blocksize = a->block; + // compute the next blocksize + size_t blocksize = a->block; - // size is 512, 512, 1024, 1024, 2048, 2048, 4096, 4096, etc., so that - // there are log(SIZE) allocations to free when we destroy the table - blocksize = (size_t) (STBDS_STRING_ARENA_BLOCKSIZE_MIN) << (blocksize>>1); + // size is 512, 512, 1024, 1024, 2048, 2048, 4096, 4096, etc., so that + // there are log(SIZE) allocations to free when we destroy the table + blocksize = (size_t) (STBDS_STRING_ARENA_BLOCKSIZE_MIN) << (blocksize>>1); - // if size is under 1M, advance to next blocktype - if (blocksize < (size_t)(STBDS_STRING_ARENA_BLOCKSIZE_MAX)) - ++a->block; + // if size is under 1M, advance to next blocktype + if (blocksize < (size_t)(STBDS_STRING_ARENA_BLOCKSIZE_MAX)) + ++a->block; - if (len > blocksize) { - // if string is larger than blocksize, then just allocate the full size. - // note that we still advance string_block so block size will continue - // increasing, so e.g. if somebody only calls this with 1000-long strings, - // eventually the arena will start doubling and handling those as well - stbds_string_block *sb = (stbds_string_block *) STBDS_REALLOC(NULL, 0, sizeof(*sb)-8 + len); - memmove(sb->storage, str, len); - if (a->storage) { - // insert it after the first element, so that we don't waste the space there - sb->next = a->storage->next; - a->storage->next = sb; - } else { - sb->next = 0; - a->storage = sb; - a->remaining = 0; // this is redundant, but good for clarity - } - return sb->storage; - } else { - stbds_string_block *sb = (stbds_string_block *) STBDS_REALLOC(NULL, 0, sizeof(*sb)-8 + blocksize); - sb->next = a->storage; - a->storage = sb; - a->remaining = blocksize; - } + if (len > blocksize) { + // if string is larger than blocksize, then just allocate the full size. + // note that we still advance string_block so block size will continue + // increasing, so e.g. if somebody only calls this with 1000-long strings, + // eventually the arena will start doubling and handling those as well + stbds_string_block *sb = (stbds_string_block *) STBDS_REALLOC(NULL, 0, sizeof(*sb)-8 + len); + memmove(sb->storage, str, len); + if (a->storage) { + // insert it after the first element, so that we don't waste the space there + sb->next = a->storage->next; + a->storage->next = sb; + } else { + sb->next = 0; + a->storage = sb; + a->remaining = 0; // this is redundant, but good for clarity + } + return sb->storage; + } else { + stbds_string_block *sb = (stbds_string_block *) STBDS_REALLOC(NULL, 0, sizeof(*sb)-8 + blocksize); + sb->next = a->storage; + a->storage = sb; + a->remaining = blocksize; + } } STBDS_ASSERT(len <= a->remaining); @@ -1607,9 +1607,9 @@ void stbds_strreset(stbds_string_arena *a) stbds_string_block *x,*y; x = a->storage; while (x) { - y = x->next; - STBDS_FREE(NULL, x); - x = y; + y = x->next; + STBDS_FREE(NULL, x); + x = y; } memset(a, 0, sizeof(*a)); } @@ -1667,27 +1667,27 @@ void stbds_unit_tests(void) STBDS_ASSERT(arrlen(arr)==0); for (i=0; i < 20000; i += 50) { - for (j=0; j < i; ++j) - arrpush(arr,j); - arrfree(arr); + for (j=0; j < i; ++j) + arrpush(arr,j); + arrfree(arr); } for (i=0; i < 4; ++i) { - arrpush(arr,1); arrpush(arr,2); arrpush(arr,3); arrpush(arr,4); - arrdel(arr,i); - arrfree(arr); - arrpush(arr,1); arrpush(arr,2); arrpush(arr,3); arrpush(arr,4); - arrdelswap(arr,i); - arrfree(arr); + arrpush(arr,1); arrpush(arr,2); arrpush(arr,3); arrpush(arr,4); + arrdel(arr,i); + arrfree(arr); + arrpush(arr,1); arrpush(arr,2); arrpush(arr,3); arrpush(arr,4); + arrdelswap(arr,i); + arrfree(arr); } for (i=0; i < 5; ++i) { - arrpush(arr,1); arrpush(arr,2); arrpush(arr,3); arrpush(arr,4); - stbds_arrins(arr,i,5); - STBDS_ASSERT(arr[i] == 5); - if (i < 4) - STBDS_ASSERT(arr[4] == 4); - arrfree(arr); + arrpush(arr,1); arrpush(arr,2); arrpush(arr,3); arrpush(arr,4); + stbds_arrins(arr,i,5); + STBDS_ASSERT(arr[i] == 5); + if (i < 4) + STBDS_ASSERT(arr[4] == 4); + arrfree(arr); } i = 1; @@ -1696,30 +1696,30 @@ void stbds_unit_tests(void) STBDS_ASSERT(hmgeti(intmap, i) == -1); STBDS_ASSERT(hmget (intmap, i) == -2); for (i=0; i < testsize; i+=2) - hmput(intmap, i, i*5); + hmput(intmap, i, i*5); for (i=0; i < testsize; i+=1) { - if (i & 1) STBDS_ASSERT(hmget(intmap, i) == -2 ); - else STBDS_ASSERT(hmget(intmap, i) == i*5); - if (i & 1) STBDS_ASSERT(hmget_ts(intmap, i, temp) == -2 ); - else STBDS_ASSERT(hmget_ts(intmap, i, temp) == i*5); + if (i & 1) STBDS_ASSERT(hmget(intmap, i) == -2 ); + else STBDS_ASSERT(hmget(intmap, i) == i*5); + if (i & 1) STBDS_ASSERT(hmget_ts(intmap, i, temp) == -2 ); + else STBDS_ASSERT(hmget_ts(intmap, i, temp) == i*5); } for (i=0; i < testsize; i+=2) - hmput(intmap, i, i*3); + hmput(intmap, i, i*3); for (i=0; i < testsize; i+=1) - if (i & 1) STBDS_ASSERT(hmget(intmap, i) == -2 ); - else STBDS_ASSERT(hmget(intmap, i) == i*3); + if (i & 1) STBDS_ASSERT(hmget(intmap, i) == -2 ); + else STBDS_ASSERT(hmget(intmap, i) == i*3); for (i=2; i < testsize; i+=4) - hmdel(intmap, i); // delete half the entries + hmdel(intmap, i); // delete half the entries for (i=0; i < testsize; i+=1) - if (i & 3) STBDS_ASSERT(hmget(intmap, i) == -2 ); - else STBDS_ASSERT(hmget(intmap, i) == i*3); + if (i & 3) STBDS_ASSERT(hmget(intmap, i) == -2 ); + else STBDS_ASSERT(hmget(intmap, i) == i*3); for (i=0; i < testsize; i+=1) - hmdel(intmap, i); // delete the rest of the entries + hmdel(intmap, i); // delete the rest of the entries for (i=0; i < testsize; i+=1) - STBDS_ASSERT(hmget(intmap, i) == -2 ); + STBDS_ASSERT(hmget(intmap, i) == -2 ); hmfree(intmap); for (i=0; i < testsize; i+=2) - hmput(intmap, i, i*3); + hmput(intmap, i, i*3); hmfree(intmap); #if defined(__clang__) || defined(__GNUC__) @@ -1735,117 +1735,117 @@ void stbds_unit_tests(void) #endif for (i=0; i < testsize; ++i) - stralloc(&sa, strkey(i)); + stralloc(&sa, strkey(i)); strreset(&sa); { - s.key = "a", s.value = 1; - shputs(strmap, s); - STBDS_ASSERT(*strmap[0].key == 'a'); - STBDS_ASSERT(strmap[0].key == s.key); - STBDS_ASSERT(strmap[0].value == s.value); - shfree(strmap); + s.key = "a", s.value = 1; + shputs(strmap, s); + STBDS_ASSERT(*strmap[0].key == 'a'); + STBDS_ASSERT(strmap[0].key == s.key); + STBDS_ASSERT(strmap[0].value == s.value); + shfree(strmap); } { - s.key = "a", s.value = 1; - sh_new_strdup(strmap); - shputs(strmap, s); - STBDS_ASSERT(*strmap[0].key == 'a'); - STBDS_ASSERT(strmap[0].key != s.key); - STBDS_ASSERT(strmap[0].value == s.value); - shfree(strmap); + s.key = "a", s.value = 1; + sh_new_strdup(strmap); + shputs(strmap, s); + STBDS_ASSERT(*strmap[0].key == 'a'); + STBDS_ASSERT(strmap[0].key != s.key); + STBDS_ASSERT(strmap[0].value == s.value); + shfree(strmap); } { - s.key = "a", s.value = 1; - sh_new_arena(strmap); - shputs(strmap, s); - STBDS_ASSERT(*strmap[0].key == 'a'); - STBDS_ASSERT(strmap[0].key != s.key); - STBDS_ASSERT(strmap[0].value == s.value); - shfree(strmap); + s.key = "a", s.value = 1; + sh_new_arena(strmap); + shputs(strmap, s); + STBDS_ASSERT(*strmap[0].key == 'a'); + STBDS_ASSERT(strmap[0].key != s.key); + STBDS_ASSERT(strmap[0].value == s.value); + shfree(strmap); } for (j=0; j < 2; ++j) { - STBDS_ASSERT(shgeti(strmap,"foo") == -1); - if (j == 0) - sh_new_strdup(strmap); - else - sh_new_arena(strmap); - STBDS_ASSERT(shgeti(strmap,"foo") == -1); - shdefault(strmap, -2); - STBDS_ASSERT(shgeti(strmap,"foo") == -1); - for (i=0; i < testsize; i+=2) - shput(strmap, strkey(i), i*3); - for (i=0; i < testsize; i+=1) - if (i & 1) STBDS_ASSERT(shget(strmap, strkey(i)) == -2 ); - else STBDS_ASSERT(shget(strmap, strkey(i)) == i*3); - for (i=2; i < testsize; i+=4) - shdel(strmap, strkey(i)); // delete half the entries - for (i=0; i < testsize; i+=1) - if (i & 3) STBDS_ASSERT(shget(strmap, strkey(i)) == -2 ); - else STBDS_ASSERT(shget(strmap, strkey(i)) == i*3); - for (i=0; i < testsize; i+=1) - shdel(strmap, strkey(i)); // delete the rest of the entries - for (i=0; i < testsize; i+=1) - STBDS_ASSERT(shget(strmap, strkey(i)) == -2 ); - shfree(strmap); + STBDS_ASSERT(shgeti(strmap,"foo") == -1); + if (j == 0) + sh_new_strdup(strmap); + else + sh_new_arena(strmap); + STBDS_ASSERT(shgeti(strmap,"foo") == -1); + shdefault(strmap, -2); + STBDS_ASSERT(shgeti(strmap,"foo") == -1); + for (i=0; i < testsize; i+=2) + shput(strmap, strkey(i), i*3); + for (i=0; i < testsize; i+=1) + if (i & 1) STBDS_ASSERT(shget(strmap, strkey(i)) == -2 ); + else STBDS_ASSERT(shget(strmap, strkey(i)) == i*3); + for (i=2; i < testsize; i+=4) + shdel(strmap, strkey(i)); // delete half the entries + for (i=0; i < testsize; i+=1) + if (i & 3) STBDS_ASSERT(shget(strmap, strkey(i)) == -2 ); + else STBDS_ASSERT(shget(strmap, strkey(i)) == i*3); + for (i=0; i < testsize; i+=1) + shdel(strmap, strkey(i)); // delete the rest of the entries + for (i=0; i < testsize; i+=1) + STBDS_ASSERT(shget(strmap, strkey(i)) == -2 ); + shfree(strmap); } { - struct { char *key; char value; } *hash = NULL; - char name[4] = "jen"; - shput(hash, "bob" , 'h'); - shput(hash, "sally" , 'e'); - shput(hash, "fred" , 'l'); - shput(hash, "jen" , 'x'); - shput(hash, "doug" , 'o'); + struct { char *key; char value; } *hash = NULL; + char name[4] = "jen"; + shput(hash, "bob" , 'h'); + shput(hash, "sally" , 'e'); + shput(hash, "fred" , 'l'); + shput(hash, "jen" , 'x'); + shput(hash, "doug" , 'o'); - shput(hash, name , 'l'); - shfree(hash); + shput(hash, name , 'l'); + shfree(hash); } for (i=0; i < testsize; i += 2) { - stbds_struct s = { i,i*2,i*3,i*4 }; - hmput(map, s, i*5); + stbds_struct s = { i,i*2,i*3,i*4 }; + hmput(map, s, i*5); } for (i=0; i < testsize; i += 1) { - stbds_struct s = { i,i*2,i*3 ,i*4 }; - stbds_struct t = { i,i*2,i*3+1,i*4 }; - if (i & 1) STBDS_ASSERT(hmget(map, s) == 0); - else STBDS_ASSERT(hmget(map, s) == i*5); - if (i & 1) STBDS_ASSERT(hmget_ts(map, s, temp) == 0); - else STBDS_ASSERT(hmget_ts(map, s, temp) == i*5); - //STBDS_ASSERT(hmget(map, t.key) == 0); + stbds_struct s = { i,i*2,i*3 ,i*4 }; + stbds_struct t = { i,i*2,i*3+1,i*4 }; + if (i & 1) STBDS_ASSERT(hmget(map, s) == 0); + else STBDS_ASSERT(hmget(map, s) == i*5); + if (i & 1) STBDS_ASSERT(hmget_ts(map, s, temp) == 0); + else STBDS_ASSERT(hmget_ts(map, s, temp) == i*5); + //STBDS_ASSERT(hmget(map, t.key) == 0); } for (i=0; i < testsize; i += 2) { - stbds_struct s = { i,i*2,i*3,i*4 }; - hmputs(map2, s); + stbds_struct s = { i,i*2,i*3,i*4 }; + hmputs(map2, s); } hmfree(map); for (i=0; i < testsize; i += 1) { - stbds_struct s = { i,i*2,i*3,i*4 }; - stbds_struct t = { i,i*2,i*3+1,i*4 }; - if (i & 1) STBDS_ASSERT(hmgets(map2, s.key).d == 0); - else STBDS_ASSERT(hmgets(map2, s.key).d == i*4); - //STBDS_ASSERT(hmgetp(map2, t.key) == 0); + stbds_struct s = { i,i*2,i*3,i*4 }; + stbds_struct t = { i,i*2,i*3+1,i*4 }; + if (i & 1) STBDS_ASSERT(hmgets(map2, s.key).d == 0); + else STBDS_ASSERT(hmgets(map2, s.key).d == i*4); + //STBDS_ASSERT(hmgetp(map2, t.key) == 0); } hmfree(map2); for (i=0; i < testsize; i += 2) { - stbds_struct2 s = { { i,i*2 }, i*3,i*4, i*5 }; - hmputs(map3, s); + stbds_struct2 s = { { i,i*2 }, i*3,i*4, i*5 }; + hmputs(map3, s); } for (i=0; i < testsize; i += 1) { - stbds_struct2 s = { { i,i*2}, i*3, i*4, i*5 }; - stbds_struct2 t = { { i,i*2}, i*3+1, i*4, i*5 }; - if (i & 1) STBDS_ASSERT(hmgets(map3, s.key).d == 0); - else STBDS_ASSERT(hmgets(map3, s.key).d == i*5); - //STBDS_ASSERT(hmgetp(map3, t.key) == 0); + stbds_struct2 s = { { i,i*2}, i*3, i*4, i*5 }; + stbds_struct2 t = { { i,i*2}, i*3+1, i*4, i*5 }; + if (i & 1) STBDS_ASSERT(hmgets(map3, s.key).d == 0); + else STBDS_ASSERT(hmgets(map3, s.key).d == i*5); + //STBDS_ASSERT(hmgetp(map3, t.key) == 0); } #endif } diff --git a/shared/util.c b/shared/util.c new file mode 100644 index 0000000..7c5b615 --- /dev/null +++ b/shared/util.c @@ -0,0 +1,181 @@ +#include "array.h" + +#include "util.h" + + +char __scratch[SCRATCH_SIZE]; + + +char *utilAppNameWithNewExtensionGet(char *appName, char *extension) { + char *c = NULL; + char *newName = NULL; + int16_t x = strlen(appName); + uint16_t len = 2 + strlen(extension); // 2 = dot in extension and 0 terminator. + + // Find last portion of filename. + while (x > 0) { + if (appName[x] == '/' || appName[x] == '\\') break; + x--; + len++; + } + + // We use this + length of new extension for new string length. + newName = (char *)malloc(len); + if (newName) { + if (strlen(appName) - x < len) { + // Replace extension + strncpy(newName, &appName[x + 1], len - 1); + c = strstr(newName, "."); + if (c) *c = 0; + strncat(newName, ".", len - 1); + strncat(newName, extension, len - 1); + } + } + + return newName; +} + + +void utilBitsPrint(uint8_t byte) { + int i = 0; + + for (i = 7; 0 <= i; i--) { + printf("%c", (byte & (1 << i)) ? '1' : '0'); + } +} + + +char *utilCreateString(char *format, ...) { + va_list args; + char *string; + + va_start(args, format); + string = utilCreateStringVArgs(format, args); + va_end(args); + + return string; +} + + +__attribute__((__format__(__printf__, 1, 0))) +char *utilCreateStringVArgs(char *format, va_list args) { + va_list argsCopy; + int32_t size = 0; + char *buffer = NULL; + + va_copy(argsCopy, args); + size = vsnprintf(NULL, 0, format, argsCopy) + 1; + va_end(argsCopy); + buffer = calloc(1, (size_t)size); + if (buffer) { + vsnprintf(buffer, (size_t)size, format, args); + } + + return buffer; +} + + +void utilDie(const char *why, ...) { + va_list args; + char msg[2048]; + + va_start(args, why); + vsprintf(msg, why, args); + va_end(args); + + logWrite("DIE: %s", msg); + exit(1); +} + + +uint8_t utilFileExists(char *filename) { + FILE *f = fopen(filename, "rb"); + + if (f) { + fclose(f); + return SUCCESS; + } + + return FAIL; +} + + +uint8_t utilFromFileReadByte(FILE *f, uint8_t *result) { + unsigned char c; + + // Get next byte. + c = fgetc(f); + // End of file? + if (feof(f)) return FAIL; + // Add to result. + *result = c; + return SUCCESS; +} + + +uint8_t utilFromFileReadString(FILE *f, char **result) { + unsigned char c; + uint16_t x = 0; + + // Something already in 'result'? + DEL(*result); + + while (1) { + // Get next byte. + c = fgetc(f); + // End of file? + if (feof(f)) return FAIL; + // Add to result. + __scratch[x++] = c; + __scratch[x] = 0; + // End of string? + if (c == 0) { + *result = strdup(__scratch); + return SUCCESS; + } + } +} + + +void utilStringToLower(char *string) { + uint16_t i; + + for (i=0; i 0) { + buffer[bufferPos++] = *head++; + buffer[bufferPos] = 0; + } + arrput(lines, strdup(buffer)); + if (isLf) head++; // jump the line feed + while (*head!=0 && *head==' ') head++; // clear the leading space + lastSpace = pos = bufferPos = 0; + } else { + if (head[pos] == ' ') lastSpace = pos; + pos++; + } + } + arrput(lines, strdup(head)); + free(buffer); + } + + return lines; +} diff --git a/shared/util.h b/shared/util.h new file mode 100644 index 0000000..93da7dd --- /dev/null +++ b/shared/util.h @@ -0,0 +1,26 @@ +#ifndef UTIL_H +#define UTIL_H + + +#include "os.h" + + +#define SCRATCH_SIZE 4096 + + +extern char __scratch[SCRATCH_SIZE]; + + +char *utilAppNameWithNewExtensionGet(char *appName, char *extension); +void utilBitsPrint(uint8_t byte); +char *utilCreateString(char *format, ...); +char *utilCreateStringVArgs(char *format, va_list args); +void utilDie(const char *why, ...); +uint8_t utilFileExists(char *filename); +uint8_t utilFromFileReadByte(FILE *f, uint8_t *result); +uint8_t utilFromFileReadString(FILE *f, char **result); +void utilStringToLower(char *string); +char **utilWrapText(char *text, uint16_t width); + + +#endif // UTIL_H