diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 77a1f40fa..2de66a5af 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -54,7 +54,7 @@ set(SRB2_CORE_SOURCES string.c tables.c v_video.c - w_wad.c + w_wad.cpp y_inter.c z_zone.cpp ) diff --git a/src/console.h b/src/console.h index a16ef47b6..3292bc6e3 100644 --- a/src/console.h +++ b/src/console.h @@ -13,6 +13,10 @@ #ifndef __CONSOLE__ #define __CONSOLE__ +#ifdef __cplusplus +extern "C" { +#endif + #include "d_event.h" #include "command.h" #include "i_threads.h" @@ -63,4 +67,8 @@ boolean CON_Ready(void); void CON_LogMessage(const char *msg); +#ifdef __cplusplus +} // extern "C" +#endif + #endif diff --git a/src/dedicated/i_system.c b/src/dedicated/i_system.c index f6450134c..ddc17d6a5 100644 --- a/src/dedicated/i_system.c +++ b/src/dedicated/i_system.c @@ -1384,6 +1384,7 @@ static void I_Fork(void) INT32 I_StartupSystem(void) { + W_Startup(); I_StartupConsole(); #ifdef NEWSIGNALHANDLER // This is useful when debugging. It lets GDB attach to diff --git a/src/dehacked.h b/src/dehacked.h index eb22c5f6a..0db20ee56 100644 --- a/src/dehacked.h +++ b/src/dehacked.h @@ -13,6 +13,10 @@ #ifndef __DEHACKED_H__ #define __DEHACKED_H__ +#ifdef __cplusplus +extern "C" { +#endif + #include "m_fixed.h" // for get_number #include "info.h" @@ -71,4 +75,9 @@ typedef struct } MYFILE; #define myfeof(a) (a->data + a->size <= a->curpos) char *myfgets(char *buf, size_t bufsize, MYFILE *f); + +#ifdef __cplusplus +} // extern "C" +#endif + #endif diff --git a/src/doomdef.h b/src/doomdef.h index 23768d0f4..445b6951f 100644 --- a/src/doomdef.h +++ b/src/doomdef.h @@ -516,22 +516,6 @@ extern boolean capslock; // i_system.c, replace getchar() once the keyboard has been appropriated INT32 I_GetKey(void); -/* http://www.cse.yorku.ca/~oz/hash.html */ -static inline -UINT32 quickncasehash (const char *p, size_t n) -{ - size_t i = 0; - UINT32 x = 5381; - - while (i < n && p[i]) - { - x = (x * 33) ^ tolower(p[i]); - i++; - } - - return x; -} - #ifndef __cplusplus #ifndef min // Double-Check with WATTCP-32's cdefs.h #define min(x, y) (((x) < (y)) ? (x) : (y)) diff --git a/src/hardware/hw_cache.cpp b/src/hardware/hw_cache.c similarity index 92% rename from src/hardware/hw_cache.cpp rename to src/hardware/hw_cache.c index 09f4f1a7b..f7cfceecb 100644 --- a/src/hardware/hw_cache.cpp +++ b/src/hardware/hw_cache.c @@ -15,8 +15,6 @@ #ifdef HWRENDER -#include - #include "hw_main.h" #include "hw_glob.h" #include "hw_gl.h" @@ -100,7 +98,7 @@ static void HWR_DrawColumnInCache(const column_t *patchcol, UINT8 *block, GLMipm position = 0; } - position = std::max(((position * scale_y) + (FRACUNIT/2)) >> FRACBITS, 0); + position = max(((position * scale_y) + (FRACUNIT/2)) >> FRACBITS, 0); if (position + count >= pblockheight) count = pblockheight - position; @@ -164,7 +162,7 @@ static void HWR_DrawPatchInCache(GLMipmap_t *mipmap, fixed_t xfrac, xfracstep; fixed_t yfracstep, scale_y; const column_t *patchcol; - UINT8 *block = static_cast(mipmap->data); + UINT8 *block = (UINT8 *)(mipmap->data); INT32 bpp; INT32 blockmodulo; RGBA_t *palette; @@ -214,7 +212,7 @@ static void HWR_DrawTexturePatchInCache(GLMipmap_t *mipmap, fixed_t xfrac, xfracstep; fixed_t yfracstep, scale_y; const column_t *patchcol; - UINT8 *block = static_cast(mipmap->data); + UINT8 *block = (UINT8 *)(mipmap->data); INT32 bpp; INT32 blockmodulo; INT32 width, height; @@ -290,7 +288,7 @@ static UINT8 *MakeBlock(GLMipmap_t *glMipmap) INT32 blocksize = (glMipmap->width * glMipmap->height); bpp = format2bpp(glMipmap->format); - block = static_cast(Z_Malloc(blocksize*bpp, PU_HWRCACHE, &(glMipmap->data))); + block = (UINT8 *)(Z_Malloc(blocksize*bpp, PU_HWRCACHE, &(glMipmap->data))); switch (bpp) { @@ -327,7 +325,7 @@ static void HWR_GenerateTexture(INT32 texnum, GLMapTexture_t *gltex, boolean noe texture = textures[texnum]; gltex->mipmap.flags = TF_CHROMAKEYED | TF_WRAPXY; - gltex->mipmap.format = static_cast(textureformat); + gltex->mipmap.format = (GLTextureFormat_t)(textureformat); // hack the Legacy skies.. if (UNLIKELY(memcmp(texture->name, "SKY", 3) == 0 && @@ -346,7 +344,7 @@ static void HWR_GenerateTexture(INT32 texnum, GLMapTexture_t *gltex, boolean noe colormap += COLORMAP_REMAPOFFSET; #endif - gltex->mipmap.colormap = static_cast(Z_Calloc(sizeof(*gltex->mipmap.colormap), PU_HWRPATCHCOLMIPMAP, NULL)); + gltex->mipmap.colormap = (GLColormap_t *)(Z_Calloc(sizeof(*gltex->mipmap.colormap), PU_HWRPATCHCOLMIPMAP, NULL)); gltex->mipmap.colormap->source = colormap; memcpy(gltex->mipmap.colormap->data, colormap, 256 * sizeof(UINT8)); @@ -379,7 +377,7 @@ static void HWR_GenerateTexture(INT32 texnum, GLMapTexture_t *gltex, boolean noe // Composite the columns together. for (i = 0, patch = texture->patches; i < texture->patchcount; i++, patch++) { - pdata = static_cast(W_CacheLumpNumPwad(patch->wad, patch->lump, PU_LEVEL)); + pdata = (UINT8 *)(W_CacheLumpNumPwad(patch->wad, patch->lump, PU_LEVEL)); realpatch = (softwarepatch_t *)pdata; HWR_DrawTexturePatchInCache(&gltex->mipmap, blockwidth, blockheight, texture, patch, realpatch); @@ -415,7 +413,7 @@ void HWR_MakePatch(const patch_t *patch, GLPatch_t *glPatch, GLMipmap_t *glMipma glMipmap->flags = 0; // setup the texture info - glMipmap->format = static_cast(patchformat); + glMipmap->format = (GLTextureFormat_t)(patchformat); glPatch->max_s = (float)patch->width / (float)glMipmap->width; glPatch->max_t = (float)patch->height / (float)glMipmap->height; @@ -457,7 +455,7 @@ static void HWR_FreeTextureData(patch_t *patch) if (!patch || !patch->hardware) return; - glPatch = static_cast(patch->hardware); + glPatch = (GLPatch_t *)(patch->hardware); if (vid.glstate == VID_GL_LIBRARY_LOADED) GL_DeleteTexture(glPatch->mipmap); @@ -473,7 +471,7 @@ void HWR_FreeTexture(patch_t *patch) if (patch->hardware) { - GLPatch_t *glPatch = static_cast(patch->hardware); + GLPatch_t *glPatch = (GLPatch_t *)(patch->hardware); HWR_FreeTextureColormaps(patch); @@ -499,7 +497,7 @@ static void HWR_FreeTextureColormaps(patch_t *patch) if (!patch) return; - pat = static_cast(patch->hardware); + pat = (GLPatch_t *)(patch->hardware); if (!pat) return; @@ -614,24 +612,20 @@ void HWR_FreeMapTextures(void) gl_numtextures = 0; } +#define NAME flatcheck +#define KEY_TY UINT32 +#define HASH_FN vt_hash_integer +#define CMPR_FN vt_cmpr_integer +#include "../verstable.h" + static void HWR_PrecacheLevelFlats(void) { // lookup for flats that may´ve been already loaded - // too lazy to make a hashtable or smth so unordered set will do :carbuncle: - std::unordered_set flatpresent; + flatcheck flatpresent; + flatcheck_init(&flatpresent); size_t i, j; INT32 k; - auto load_flat = [&](lumpnum_t lump, boolean noencoremap) - { - // flat already loaded? - if (flatpresent.count(lump) == 0) - { - HWR_GetFlat(lump, noencoremap); - flatpresent.insert(lump); - } - }; - // special case for encore #ifdef GLENCORE if (encoremap) @@ -649,14 +643,23 @@ static void HWR_PrecacheLevelFlats(void) const levelflat_t *levelflat = &levelflats[pic]; - load_flat(levelflat->lumpnum, R_NoEncore(sec, ceiling)); + if (flatcheck_is_end(flatcheck_get(&flatpresent, levelflat->lumpnum))) + { + HWR_GetFlat(levelflat->lumpnum, R_NoEncore(sec, ceiling)); + flatcheck_insert(&flatpresent, levelflat->lumpnum); + } if (levelflat->speed) // is it an animated flat ? { // start from 0, occasionally basenumlump != flat->lumpnum for (k = 0; k < levelflat->numpics; k++) { - load_flat(levelflat->baselumpnum + k, R_NoEncore(sec, ceiling)); + lumpnum_t lump = levelflat->baselumpnum + k; + if (flatcheck_is_end(flatcheck_get(&flatpresent, lump))) + { + HWR_GetFlat(lump, R_NoEncore(sec, ceiling)); + flatcheck_insert(&flatpresent, lump); + } } } } @@ -671,18 +674,29 @@ static void HWR_PrecacheLevelFlats(void) { const levelflat_t *levelflat = &levelflats[i]; - load_flat(levelflat->lumpnum, false); + if (flatcheck_is_end(flatcheck_get(&flatpresent, levelflat->lumpnum))) + { + HWR_GetFlat(levelflat->lumpnum, false); + flatcheck_insert(&flatpresent, levelflat->lumpnum); + } if (levelflat->speed) // is it an animated flat ? { // start from 0, occasionally basenumlump != flat->lumpnum for (k = 0; k < levelflat->numpics; k++) { - load_flat(levelflat->baselumpnum + k, false); + lumpnum_t lump = levelflat->baselumpnum + k; + if (flatcheck_is_end(flatcheck_get(&flatpresent, lump))) + { + HWR_GetFlat(lump, false); + flatcheck_insert(&flatpresent, lump); + } } } } } + + flatcheck_cleanup(&flatpresent); } static void HWR_PrecacheLevelTextures(void) @@ -692,7 +706,7 @@ static void HWR_PrecacheLevelTextures(void) size_t i, j, f; INT32 h; - texturepresent = static_cast(calloc(gl_numtextures, sizeof(*texturepresent))); + texturepresent = (char *)(calloc(gl_numtextures, sizeof(*texturepresent))); if (texturepresent == NULL) I_Error("%s: Out of memory looking up textures", "HWR_PrecacheLevel"); @@ -867,7 +881,7 @@ void HWR_LoadMapTextures(size_t pnumtextures) HWR_FreeMapTextures(); gl_numtextures = pnumtextures; - gl_textures = static_cast(calloc(gl_numtextures, sizeof(*gl_textures))); // *2 - 1 for encore-remapped texture and another for noencore texture (unused when not in encore), see: GLMapTextureSet_t + gl_textures = (GLMapTextureSet_t *)(calloc(gl_numtextures, sizeof(*gl_textures))); // *2 - 1 for encore-remapped texture and another for noencore texture (unused when not in encore), see: GLMapTextureSet_t if (gl_textures == NULL) I_Error("HWR_LoadMapTextures: ran out of memory for OpenGL textures. Sad!"); @@ -964,7 +978,7 @@ static void HWR_CacheFlat(GLMipmap_t *glMipmap, lumpnum_t flatlumpnum) W_ReadLump(flatlumpnum, Z_Malloc(size, PU_HWRCACHE, &glMipmap->data)); #ifdef GLENCORE - flat = static_cast(glMipmap->data); + flat = (UINT8 *)(glMipmap->data); for (steppy = 0; steppy < size; steppy++) { if (flat[steppy] == HWR_PATCHES_CHROMAKEY_COLORINDEX) @@ -1008,7 +1022,7 @@ void HWR_GetFlat(lumpnum_t flatlumpnum, boolean noencoremap) if (!noencoremap && encoremap) colormap += COLORMAP_REMAPOFFSET; #endif - glMipmap->colormap = static_cast(Z_Calloc(sizeof(*glMipmap->colormap), PU_HWRPATCHCOLMIPMAP, NULL)); + glMipmap->colormap = (GLColormap_t *)(Z_Calloc(sizeof(*glMipmap->colormap), PU_HWRPATCHCOLMIPMAP, NULL)); glMipmap->colormap->source = colormap; memcpy(glMipmap->colormap->data, colormap, 256 * sizeof(UINT8)); } @@ -1032,7 +1046,7 @@ void HWR_GetFlat(lumpnum_t flatlumpnum, boolean noencoremap) // --------------------+ static void HWR_LoadPatchMipmap(patch_t *patch, GLMipmap_t *glMipmap) { - GLPatch_t *glPatch = static_cast(patch->hardware); + GLPatch_t *glPatch = (GLPatch_t *)(patch->hardware); if (!glMipmap->downloaded) { @@ -1053,7 +1067,7 @@ static void HWR_LoadPatchMipmap(patch_t *patch, GLMipmap_t *glMipmap) // ----------------------+ static void HWR_UpdatePatchMipmap(patch_t *patch, GLMipmap_t *glMipmap) { - GLPatch_t *glPatch = static_cast(patch->hardware); + GLPatch_t *glPatch = (GLPatch_t *)(patch->hardware); HWR_MakePatch(patch, glPatch, glMipmap, true); // If hardware does not have the texture, then call GL_SetTexture to upload it @@ -1089,7 +1103,7 @@ void HWR_GetMappedPatch(patch_t *patch, const UINT8 *colormap) if (!patch->hardware) Patch_CreateGL(patch); - glPatch = static_cast(patch->hardware); + glPatch = (GLPatch_t *)(patch->hardware); // Blatant hack for encore colormapping aside... if (colormap == colormaps || colormap == NULL || colormap == (const UINT8*)(COLORMAP_REMAPOFFSET)) @@ -1125,12 +1139,12 @@ void HWR_GetMappedPatch(patch_t *patch, const UINT8 *colormap) // (it have a liste of mipmap) // this malloc is cleared in HWR_FreeColormapCache // (...) unfortunately z_malloc fragment alot the memory :(so malloc is better - newMipmap = static_cast(calloc(1, sizeof (*newMipmap))); + newMipmap = (GLMipmap_t *)(calloc(1, sizeof (*newMipmap))); if (newMipmap == NULL) I_Error("%s: Out of memory", "HWR_GetMappedPatch"); glMipmap->nextcolormap = newMipmap; - newMipmap->colormap = static_cast(Z_Calloc(sizeof(*newMipmap->colormap), PU_HWRPATCHCOLMIPMAP, NULL)); + newMipmap->colormap = (GLColormap_t *)(Z_Calloc(sizeof(*newMipmap->colormap), PU_HWRPATCHCOLMIPMAP, NULL)); newMipmap->colormap->source = colormap; memcpy(newMipmap->colormap->data, colormap, 256 * sizeof(UINT8)); @@ -1153,7 +1167,7 @@ patch_t *HWR_GetCachedGLPatchPwad(UINT16 wadnum, UINT16 lumpnum) { void *ptr = Z_Calloc(sizeof(patch_t), PU_PATCH, &lumpcache[lumpnum]); Patch_Create(NULL, 0, ptr); - Patch_AllocateHardwarePatch(static_cast(ptr)); + Patch_AllocateHardwarePatch((patch_t *)(ptr)); } return (patch_t *)(lumpcache[lumpnum]); @@ -1170,7 +1184,7 @@ static void HWR_DrawFadeMaskInCache(GLMipmap_t *mipmap, INT32 pblockwidth, INT32 { INT32 i,j; fixed_t posx, posy, stepx, stepy; - UINT8 *block = static_cast(mipmap->data); // places the data directly into here + UINT8 *block = (UINT8 *)(mipmap->data); // places the data directly into here UINT8 *flat; UINT8 *dest, *src, texel; RGBA_t col; @@ -1326,7 +1340,7 @@ void HWR_SetPalette(RGBA_t *palette) static void HWR_SetPaletteLookup(RGBA_t *palette) { int r, g, b; - UINT8 *lut = static_cast(Z_Malloc( + UINT8 *lut = (UINT8 *)(Z_Malloc( HWR_PALETTE_LUT_SIZE*HWR_PALETTE_LUT_SIZE*HWR_PALETTE_LUT_SIZE*sizeof(UINT8), PU_STATIC, NULL)); #define STEP_SIZE (256/HWR_PALETTE_LUT_SIZE) @@ -1371,7 +1385,7 @@ void HWR_SetMapPalette(void) if (palsize < 768) // 256 * 3 I_Error("HWR_SetMapPalette: A programmer assumed palette lumps are at least 768 bytes long, but apparently this was a wrong assumption!\n"); - RGB_data = static_cast(W_CacheLumpNum(lumpnum, PU_CACHE)); + RGB_data = (UINT8 *)(W_CacheLumpNum(lumpnum, PU_CACHE)); // we got the RGB palette now, but we need it in RGBA format. for (i = 0; i < 256; i++) { @@ -1406,7 +1420,7 @@ static UINT32 HWR_CreateLightTable(UINT8 *lighttable) { UINT32 i, id; RGBA_t *palette = HWR_GetTexturePalette(); - RGBA_t *hw_lighttable = static_cast(Z_Malloc(256 * 32 * sizeof(RGBA_t), PU_STATIC, NULL)); + RGBA_t *hw_lighttable = (RGBA_t *)(Z_Malloc(256 * 32 * sizeof(RGBA_t), PU_STATIC, NULL)); // To make the palette index -> RGBA mapping easier for the shader, // the hardware lighttable is composed of RGBA colors instead of palette indices. diff --git a/src/hardware/hw_data.h b/src/hardware/hw_data.h index bb174b205..93ccfd4ac 100644 --- a/src/hardware/hw_data.h +++ b/src/hardware/hw_data.h @@ -54,7 +54,7 @@ struct GLMipmap_s UINT16 width, height; UINT32 downloaded; // The GPU has this texture. - struct GLMipmap_s *nextcolormap; + struct GLMipmap_s *nextcolormap; struct GLColormap_s *colormap; }; typedef struct GLMipmap_s GLMipmap_t; diff --git a/src/lzf.h b/src/lzf.h index 85daf255e..995e4ebd6 100644 --- a/src/lzf.h +++ b/src/lzf.h @@ -13,6 +13,10 @@ #define LZF_H #include +#ifdef __cplusplus +extern "C" { +#endif + /*********************************************************************** ** ** lzf -- an extremely fast/free compression/decompression-method @@ -71,5 +75,9 @@ size_t lzf_decompress (const void *const in_data, size_t in_len, void *out_data, size_t out_len); +#ifdef __cplusplus +} // extern "C" +#endif + #endif diff --git a/src/m_misc.c b/src/m_misc.c index ee17e2098..dca00950d 100644 --- a/src/m_misc.c +++ b/src/m_misc.c @@ -1956,6 +1956,22 @@ UINT32 FNV1a_Hash(const char *message, size_t size) return hash; } +// Behaves like the old quickncasehash +// Stops either if it encounters null terminator +// or reaches size +UINT32 FNV1a_QuickCaseHash(const char *message, size_t size) +{ + UINT32 hash = FNV1A_OFFSET_BASIS; + + for (size_t i = 0; i < size && message[i]; i++) + { + hash ^= tolower(message[i]); + hash *= FNV1A_PRIME; + } + + return hash; +} + UINT32 FNV1a_HashString(const char *message) { UINT32 hash = FNV1A_OFFSET_BASIS; diff --git a/src/m_misc.h b/src/m_misc.h index ab7c8986f..813e8abf1 100644 --- a/src/m_misc.h +++ b/src/m_misc.h @@ -123,6 +123,7 @@ FUNCMATH UINT8 M_CountBits(UINT32 num, UINT8 size); // Hashes some message using FNV-1a UINT32 FNV1a_Hash(const char *message, size_t size); +UINT32 FNV1a_QuickCaseHash(const char *message, size_t size); UINT32 FNV1a_HashString(const char *message); UINT32 FNV1a_HashLowercaseString(const char *message); diff --git a/src/md5.h b/src/md5.h index f09d865ab..85384c479 100644 --- a/src/md5.h +++ b/src/md5.h @@ -16,6 +16,10 @@ #ifndef _MD5_H #define _MD5_H 1 +#ifdef __cplusplus +extern "C" { +#endif + #include #if defined (HAVE_LIMITS_H) || (defined (_LIBC) && _LIBC) || defined (_WIN32) @@ -52,4 +56,8 @@ int md5_stream(FILE *stream, void *resblock) THROWFUNC; digest. */ extern void *md5_buffer(const char *buffer, size_t len, void *resblock) THROWFUNC; +#ifdef __cplusplus +} // extern "C" +#endif + #endif /* md5.h */ diff --git a/src/r_data.c b/src/r_data.c index 911413bb4..f0a075217 100644 --- a/src/r_data.c +++ b/src/r_data.c @@ -622,7 +622,7 @@ Rloadtextures (INT32 i, INT32 w) // Set texture properties. memcpy(texture->name, W_CheckNameForNumPwad(wadnum, lumpnum), sizeof(texture->name)); - texture->hash = quickncasehash(texture->name, 8); + texture->hash = FNV1a_QuickCaseHash(texture->name, 8); texture->width = SHORT(patchlump.width); texture->height = SHORT(patchlump.height); @@ -1066,7 +1066,7 @@ static texture_t *R_ParseTexture(boolean actuallyLoadTexture) // Allocate memory for a zero-patch texture. Obviously, we'll be adding patches momentarily. resultTexture = (texture_t *)Z_Calloc(sizeof(texture_t), PU_STATIC, NULL); memcpy(resultTexture->name, newTextureName, 8); - resultTexture->hash = quickncasehash(newTextureName, 8); + resultTexture->hash = FNV1a_QuickCaseHash(newTextureName, 8); resultTexture->width = newTextureWidth; resultTexture->height = newTextureHeight; resultTexture->type = TEXTURETYPE_COMPOSITE; @@ -1755,7 +1755,7 @@ INT32 R_CheckTextureNumForName(const char *name) if (name[0] == '-') return 0; - hash = quickncasehash(name, 8); + hash = FNV1a_QuickCaseHash(name, 8); for (i = 0; i < tidcachelen; i++) if (tidcache[i].hash == hash && !strncasecmp(tidcache[i].name, name, 8)) diff --git a/src/s_sound.c b/src/s_sound.c index f2ee1dc03..621f8a8a1 100644 --- a/src/s_sound.c +++ b/src/s_sound.c @@ -1301,7 +1301,7 @@ static boolean ReadMusicDefFields(UINT16 wadnum, int line, char *stoken, musicde STRBUFCPY(def->name, value); strlwr(def->name); - def->hash = quickncasehash (def->name, 6); + def->hash = FNV1a_QuickCaseHash(def->name, 6); } (*defp) = def; @@ -1483,7 +1483,7 @@ void S_InitMusicDefs(void) // musicdef_t *S_FindMusicCredit(const char *musname) { - UINT32 hash = quickncasehash(musname, 6); + UINT32 hash = FNV1a_QuickCaseHash(musname, 6); musicdef_t *def; for (INT32 i = 0; i < nummusicdefs; ++i) diff --git a/src/sdl/i_system.cpp b/src/sdl/i_system.cpp index 5310cc855..407e5c3a0 100644 --- a/src/sdl/i_system.cpp +++ b/src/sdl/i_system.cpp @@ -1886,6 +1886,9 @@ INT32 I_StartupSystem(void) SDL_version SDLlinked; SDL_VERSION(&SDLcompiled) SDL_GetVersion(&SDLlinked); + + W_Startup(); + I_StartupConsole(); #ifdef NEWSIGNALHANDLER // This is useful when debugging. It lets GDB attach to diff --git a/src/verstable.h b/src/verstable.h new file mode 100644 index 000000000..b3fe13f91 --- /dev/null +++ b/src/verstable.h @@ -0,0 +1,2119 @@ +/*------------------------------------------------- VERSTABLE v2.2.1 --------------------------------------------------- + +Verstable is a C99-compatible, open-addressing hash table using quadratic probing and the following additions: + +* All keys that hash (i.e. "belong") to the same bucket (their "home bucket") are linked together by an 11-bit integer + specifying the quadratic displacement, relative to that bucket, of the next key in the chain. + +* If a chain of keys exists for a given bucket, then it always begins at that bucket. To maintain this policy, a 1-bit + flag is used to mark whether the key occupying a bucket belongs there. When inserting a new key, if the bucket it + belongs to is occupied by a key that does not belong there, then the occupying key is evicted and the new key takes + the bucket. + +* A 4-bit fragment of each key's hash code is also stored. + +* The aforementioned metadata associated with each bucket (the 4-bit hash fragment, the 1-bit flag, and the 11-bit link + to the next key in the chain) are stored together in a uint16_t array rather than in the bucket alongside the key and + (optionally) the value. + +One way to conceptualize this scheme is as a chained hash table in which overflowing keys are stored not in separate +memory allocations but in otherwise unused buckets. In this regard, it shares similarities with Malte Skarupke's Bytell +hash table (https://www.youtube.com/watch?v=M2fKMP47slQ) and traditional "coalesced hashing". + +Advantages of this scheme include: + +* Fast lookups impervious to load factor: If the table contains any key belonging to the lookup key's home bucket, then + that bucket contains the first in a traversable chain of all keys belonging to it. Hence, only the home bucket and + other buckets containing keys belonging to it are ever probed. Moreover, the stored hash fragments allow skipping most + non-matching keys in the chain without accessing the actual buckets array or calling the (potentially expensive) key + comparison function. + +* Fast insertions: Insertions are faster than they are in other schemes that move keys around (e.g. Robin Hood) because + they only move, at most, one existing key. + +* Fast, tombstone-free deletions: Deletions, which usually require tombstones in quadratic-probing hash tables, are + tombstone-free and only move, at most, one existing key. + +* Fast iteration: The separate metadata array allows keys in sparsely populated tables to be found without incurring the + frequent cache misses that would result from traversing the buckets array. + +Usage example: + + +---------------------------------------------------------+----------------------------------------------------------+ + | Using the generic macro API (C11 and later): | Using the prefixed functions API (C99 and later): | + +---------------------------------------------------------+----------------------------------------------------------+ + | #include | #include | + | | | + | // Instantiating a set template. | // Instantiating a set template. | + | #define NAME int_set | #define NAME int_set | + | #define KEY_TY int | #define KEY_TY int | + | #include "verstable.h" | #define HASH_FN vt_hash_integer | + | | #define CMPR_FN vt_cmpr_integer | + | // Instantiating a map template. | #include "verstable.h" | + | #define NAME int_int_map | | + | #define KEY_TY int | // Instantiating a map template. | + | #define VAL_TY int | #define NAME int_int_map | + | #include "verstable.h" | #define KEY_TY int | + | | #define VAL_TY int | + | int main( void ) | #define HASH_FN vt_hash_integer | + | { | #define CMPR_FN vt_cmpr_integer | + | // Set. | #include "verstable.h" | + | | | + | int_set our_set; | int main( void ) | + | vt_init( &our_set ); | { | + | | // Set. | + | // Inserting keys. | | + | for( int i = 0; i < 10; ++i ) | int_set our_set; | + | { | int_set_init( &our_set ); | + | int_set_itr itr = vt_insert( &our_set, i ); | | + | if( vt_is_end( itr ) ) | // Inserting keys. | + | { | for( int i = 0; i < 10; ++i ) | + | // Out of memory, so abort. | { | + | vt_cleanup( &our_set ); | int_set_itr itr = | + | return 1; | int_set_insert( &our_set, i ); | + | } | if( int_set_is_end( itr ) ) | + | } | { | + | | // Out of memory, so abort. | + | // Erasing keys. | int_set_cleanup( &our_set ); | + | for( int i = 0; i < 10; i += 3 ) | return 1; | + | vt_erase( &our_set, i ); | } | + | | } | + | // Retrieving keys. | | + | for( int i = 0; i < 10; ++i ) | // Erasing keys. | + | { | for( int i = 0; i < 10; i += 3 ) | + | int_set_itr itr = vt_get( &our_set, i ); | int_set_erase( &our_set, i ); | + | if( !vt_is_end( itr ) ) | | + | printf( "%d ", itr.data->key ); | // Retrieving keys. | + | } | for( int i = 0; i < 10; ++i ) | + | // Printed: 1 2 4 5 7 8 | { | + | | int_set_itr itr = int_set_get( &our_set, i ); | + | // Iteration. | if( !int_set_is_end( itr ) ) | + | for( | printf( "%d ", itr.data->key ); | + | int_set_itr itr = vt_first( &our_set ); | } | + | !vt_is_end( itr ); | // Printed: 1 2 4 5 7 8 | + | itr = vt_next( itr ) | | + | ) | // Iteration. | + | printf( "%d ", itr.data->key ); | for( | + | // Printed: 2 4 7 1 5 8 | int_set_itr itr = | + | | int_set_first( &our_set ); | + | vt_cleanup( &our_set ); | !int_set_is_end( itr ); | + | | itr = int_set_next( itr ) | + | // Map. | ) | + | | printf( "%d ", itr.data->key ); | + | int_int_map our_map; | // Printed: 2 4 7 1 5 8 | + | vt_init( &our_map ); | | + | | int_set_cleanup( &our_set ); | + | // Inserting keys and values. | | + | for( int i = 0; i < 10; ++i ) | // Map. | + | { | | + | int_int_map_itr itr = | int_int_map our_map; | + | vt_insert( &our_map, i, i + 1 ); | int_int_map_init( &our_map ); | + | if( vt_is_end( itr ) ) | | + | { | // Inserting keys and values. | + | // Out of memory, so abort. | for( int i = 0; i < 10; ++i ) | + | vt_cleanup( &our_map ); | { | + | return 1; | int_int_map_itr itr = | + | } | int_int_map_insert( &our_map, i, i + 1 ); | + | } | if( int_int_map_is_end( itr ) ) | + | | { | + | // Erasing keys and values. | // Out of memory, so abort. | + | for( int i = 0; i < 10; i += 3 ) | int_int_map_cleanup( &our_map ); | + | vt_erase( &our_map, i ); | return 1; | + | | } | + | // Retrieving keys and values. | } | + | for( int i = 0; i < 10; ++i ) | | + | { | // Erasing keys and values. | + | int_int_map_itr itr = vt_get( &our_map, i ); | for( int i = 0; i < 10; i += 3 ) | + | if( !vt_is_end( itr ) ) | int_int_map_erase( &our_map, i ); | + | printf( | | + | "%d:%d ", | // Retrieving keys and values. | + | itr.data->key, | for( int i = 0; i < 10; ++i ) | + | itr.data->val | { | + | ); | int_int_map_itr itr = | + | } | int_int_map_get( &our_map, i ); | + | // Printed: 1:2 2:3 4:5 5:6 7:8 8:9 | if( !int_int_map_is_end( itr ) ) | + | | printf( | + | // Iteration. | "%d:%d ", | + | for( | itr.data->key, | + | int_int_map_itr itr = vt_first( &our_map ); | itr.data->val | + | !vt_is_end( itr ); | ); | + | itr = vt_next( itr ) | } | + | ) | // Printed: 1:2 2:3 4:5 5:6 7:8 8:9 | + | printf( | | + | "%d:%d ", | // Iteration. | + | itr.data->key, | for( | + | itr.data->val | int_int_map_itr itr = | + | ); | int_int_map_first( &our_map ); | + | // Printed: 2:3 4:5 7:8 1:2 5:6 8:9 | !int_int_map_is_end( itr ); | + | | itr = int_int_map_next( itr ) | + | vt_cleanup( &our_map ); | ) | + | } | printf( | + | | "%d:%d ", | + | | itr.data->key, | + | | itr.data->val | + | | ); | + | | // Printed: 2:3 4:5 7:8 1:2 5:6 8:9 | + | | | + | | int_int_map_cleanup( &our_map ); | + | | } | + | | | + +---------------------------------------------------------+----------------------------------------------------------+ + +API: + + Instantiating a hash table template: + + Create a new hash table type in the following manner: + + #define NAME + #define KEY_TY + #include "verstable.h" + + The NAME macro specifies the name of hash table type that the library will declare, the prefix for the functions + associated with it, and the prefix for the associated iterator type. + + The KEY_TY macro specifies the key type. + + In C99, it is also always necessary to define HASH_FN and CMPR_FN (see below) before including the header. + + The following macros may also be defined before including the header: + + #define KEY_GET_CONST + + Forces the key getter to take a const pointer even if the input key is not marked const + + #define VAL_TY + + The type of the value associated with each key. + If this macro is defined, the hash table acts as a map associating keys with values. + Otherwise, it acts as a set containing only keys. + + #define HASH_FN + + The name of the existing function used to hash each key. + The function should have the signature uint64_t ( KEY_TY key ) and return a 64-bit hash code. + For best performance, the hash function should provide a high level of entropy across all bits. + There are two default hash functions: vt_hash_integer for all integer types up to 64 bits in size, and + vt_hash_string for NULL-terminated strings (i.e. char *). + When KEY_TY is one of such types and the compiler is in C11 mode or later, HASH_FN may be left undefined, in + which case the appropriate default function is inferred from KEY_TY. + Otherwise, HASH_FN must be defined. + + #define CMPR_FN + + The name of the existing function used to compare two keys. + The function should have the signature bool ( KEY_TY key_1, KEY_TY key_2 ) and return true if the two keys are + equal. + There are two default comparison functions: vt_cmpr_integer for all integer types up to 64 bits in size, and + vt_cmpr_string for NULL-terminated strings (i.e. char *). + As with the default hash functions, in C11 or later the appropriate default comparison function is inferred if + KEY_TY is one of such types and CMPR_FN is left undefined. + Otherwise, CMPR_FN must be defined. + + #define MAX_LOAD + + The floating-point load factor at which the hash table automatically doubles the size of its internal buckets + array. + The default is 0.9, i.e. 90%. + + #define KEY_DTOR_FN + + The name of the existing destructor function, with the signature void ( KEY_TY key ), called on a key when it is + erased from the table or replaced by a newly inserted key. + The API functions that may call the key destructor are NAME_insert, NAME_erase, NAME_erase_itr, NAME_clear, + and NAME_cleanup. + + #define VAL_DTOR_FN + + The name of the existing destructor function, with the signature void ( VAL_TY val ), called on a value when it + is erased from the table or replaced by a newly inserted value. + The API functions that may call the value destructor are NAME_insert, NAME_erase, NAME_erase_itr, NAME_clear, + and NAME_cleanup. + + #define CTX_TY + + The type of the hash table type's ctx (context) member. + This member only exists if CTX_TY was defined. + It is intended to be used in conjunction with MALLOC_FN and FREE_FN (see below). + + #define MALLOC_FN + + The name of the existing function used to allocate memory. + If CTX_TY was defined, the signature should be void *( size_t size, CTX_TY *ctx ), where size is the number of + bytes to allocate and ctx points to the table's ctx member. + Otherwise, the signature should be void *( size_t size ). + The default wraps stdlib.h's malloc. + + #define FREE_FN + + The name of the existing function used to free memory. + If CTX_TY was defined, the signature should be void ( void *ptr, size_t size, CTX_TY *ctx ), where ptr points to + the memory to free, size is the number of bytes that were allocated, and ctx points to the table's ctx member. + Otherwise, the signature should be void ( void *ptr, size_t size ). + The default wraps stdlib.h's free. + + #define HEADER_MODE + #define IMPLEMENTATION_MODE + + By default, all hash table functions are defined as static inline functions, the intent being that a given hash + table template should be instantiated once per translation unit; for best performance, this is the recommended + way to use the library. + However, it is also possible to separate the struct definitions and function declarations from the function + definitions such that one implementation can be shared across all translation units (as in a traditional header + and source file pair). + In that case, instantiate a template wherever it is needed by defining HEADER_MODE, along with only NAME, + KEY_TY, and (optionally) VAL_TY, CTX_TY, and header guards, and including the library, e.g.: + + #ifndef INT_INT_MAP_H + #define INT_INT_MAP_H + #define NAME int_int_map + #define KEY_TY int + #define VAL_TY int + #define HEADER_MODE + #include "verstable.h" + #endif + + In one source file, define IMPLEMENTATION_MODE, along with NAME, KEY_TY, and any of the aforementioned optional + macros, and include the library, e.g.: + + #define NAME int_int_map + #define KEY_TY int + #define VAL_TY int + #define HASH_FN vt_hash_integer // C99. + #define CMPR_FN vt_cmpr_integer // C99. + #define MAX_LOAD 0.8 + #define IMPLEMENTATION_MODE + #include "verstable.h" + + Including the library automatically undefines all the aforementioned macros after they have been used to instantiate + the template. + + Functions: + + The functions associated with a hash table type are all prefixed with the name the user supplied via the NAME macro. + In C11 and later, the generic "vt_"-prefixed macros may be used to automatically select the correct version of the + specified function based on the arguments. + + void NAME_init( NAME *table ) + void NAME_init( NAME *table, CTX_TY ctx ) + // C11 generic macro: vt_init. + + Initializes the table for use. + If CTX_TY was defined, ctx sets the table's ctx member. + + bool NAME_init_clone( NAME *table, NAME *source ) + bool NAME_init_clone( NAME *table, NAME *source, CTX_TY ctx ) + // C11 generic macro: vt_init_clone. + + Initializes the table as a shallow copy of the specified source table. + If CTX_TY was defined, ctx sets the table's ctx member. + Returns false in the case of memory allocation failure. + + size_t NAME_size( NAME *table ) // C11 generic macro: vt_size. + + Returns the number of keys currently in the table. + + size_t NAME_bucket_count( NAME *table ) // C11 generic macro: vt_bucket_count. + + Returns the table's current bucket count. + + NAME_itr NAME_insert( NAME *table, KEY_TY key ) + NAME_itr NAME_insert( NAME *table, KEY_TY key, VAL_TY val ) + // C11 generic macro: vt_insert. + + Inserts the specified key (and value, if VAL_TY was defined) into the hash table. + If the same key already exists, then the new key (and value) replaces the existing key (and value). + Returns an iterator to the new key, or an end iterator in the case of memory allocation failure. + + NAME_itr NAME_get_or_insert( NAME *table, KEY_TY key ) + NAME_itr NAME_get_or_insert( NAME *table, KEY_TY key, VAL_TY val ) + // C11 generic macro: vt_get_or_insert. + + Inserts the specified key (and value, if VAL_TY was defined) if it does not already exist in the table. + Returns an iterator to the new key if it was inserted, or an iterator to the existing key, or an end iterator if + the key did not exist but the new key could not be inserted because of memory allocation failure. + Determine whether the key was inserted by comparing the table's size before and after the call. + + NAME_itr NAME_get( NAME *table, KEY_TY key ) // C11 generic macro: vt_get. + + Returns a iterator to the specified key, or an end iterator if no such key exists. + + bool NAME_erase( NAME *table, KEY_TY key ) // C11 generic macro: vt_erase. + + Erases the specified key (and associated value, if VAL_TY was defined), if it exists. + Returns true if a key was erased. + + NAME_itr NAME_erase_itr( NAME *table, NAME_itr itr ) // C11 generic macro: vt_erase_itr. + + Erases the key (and associated value, if VAL_TY was defined) pointed to by the specified iterator. + Returns an iterator to the next key in the table, or an end iterator if the erased key was the last one. + + bool NAME_reserve( NAME *table, size_t size ) // C11 generic macro: vt_reserve. + + Ensures that the bucket count is large enough to support the specified key count (i.e. size) without rehashing. + Returns false if unsuccessful due to memory allocation failure. + + bool NAME_shrink( NAME *table ) // C11 generic macro: vt_shrink. + + Shrinks the bucket count to best accommodate the current size. + Returns false if unsuccessful due to memory allocation failure. + + NAME_itr NAME_first( NAME *table ) // C11 generic macro: vt_first. + + Returns an iterator to the first key in the table, or an end iterator if the table is empty. + + bool NAME_is_end( NAME_itr itr ) // C11 generic macro: vt_is_end. + + Returns true if the iterator is an end iterator. + + NAME_itr NAME_next( NAME_itr itr ) // C11 generic macro: vt_next. + + Returns an iterator to the key after the one pointed to by the specified iterator, or an end iterator if the + specified iterator points to the last key in the table. + + void NAME_clear( NAME *table ) // C11 generic macro: vt_clear. + + Erases all keys (and values, if VAL_TY was defined) in the table. + + void NAME_cleanup( NAME *table ) // C11 generic macro: vt_cleanup. + + Erases all keys (and values, if VAL_TY was defined) in the table, frees all memory associated with it, and + initializes it for reuse. + + Iterators: + + Access the key (and value, if VAL_TY was defined) that an iterator points to using the NAME_itr struct's data + member: + + itr.data->key + itr.data->val + + Functions that may insert new keys (NAME_insert and NAME_get_or_insert), erase keys (NAME_erase and NAME_erase_itr), + or reallocate the internal bucket array (NAME_reserve and NAME_shrink) invalidate all existing iterators. + To delete keys during iteration and resume iterating, use the return value of NAME_erase_itr. + +SRB2Kart - Saturn: + added KEY_GET_CONST to force the getters to take a const pointer even if the type of key to be inserted is not const + +Version history: + + 06/05/2025 2.2.1: Fixed incorrect signature of NAME_is_end in documentation. + 18/04/2025 2.2.0: Added const qualifier to the table parameter of NAME_size, NAME_bucket_count, NAME_get, and + NAME_first and to the source parameter of NAME_init_clone. + Added default support for const char * strings. + Added support for -Wextra. + Replaced FNV-1a with Wyhash as the default string hash function. + 18/06/2024 2.1.1: Fixed a bug affecting iteration on big-endian platforms under MSVC. + 27/05/2024 2.1.0: Replaced the Murmur3 mixer with the fast-hash mixer as the default integer hash function. + Fixed a bug that could theoretically cause a crash on rehash (triggerable in testing using + NAME_shrink with a maximum load factor significantly higher than 1.0). + 06/02/2024 2.0.0: Improved custom allocator support by introducing the CTX_TY option and allowing user-supplied free + functions to receive the allocation size. + Improved documentation. + Introduced various optimizations, including storing the buckets-array size mask instead of the + bucket count, eliminating empty-table checks, combining the buckets memory and metadata memory into + one allocation, and adding branch prediction macros. + Fixed a bug that caused a key to be used after destruction during erasure. + 12/12/2023 1.0.0: Initial release. + +License (MIT): + + Copyright (c) 2023-2025 Jackson L. Allan + + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated + documentation files (the "Software"), to deal in the Software without restriction, including without limitation the + rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit + persons to whom the Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +*/ + +/*--------------------------------------------------------------------------------------------------------------------*/ +/* Common header section */ +/*--------------------------------------------------------------------------------------------------------------------*/ + +#ifdef __GNUC__ + #if (__GNUC__ > 3) || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1) // >= GCC 3.1 + #define FORCE_INLINE __attribute__((always_inline)) + #else + #define FORCE_INLINE + #endif +#else + #define FORCE_INLINE +#endif + +#ifndef VERSTABLE_H +#define VERSTABLE_H + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Waggregate-return" + +#include +#include +#include +#include +#include +#include + +// Two-way concatenation macro. +#define VT_CAT_( a, b ) a##b +#define VT_CAT( a, b ) VT_CAT_( a, b ) + +// Branch optimization macros. +#ifdef __GNUC__ +#define VT_LIKELY( expression ) __builtin_expect( (bool)( expression ), true ) +#define VT_UNLIKELY( expression ) __builtin_expect( (bool)( expression ), false ) +#else +#define VT_LIKELY( expression ) ( expression ) +#define VT_UNLIKELY( expression ) ( expression ) +#endif + +// Masks for manipulating and extracting data from a bucket's uint16_t metadatum. +#define VT_EMPTY 0x0000 +#define VT_HASH_FRAG_MASK 0xF000 // 0b1111000000000000. +#define VT_IN_HOME_BUCKET_MASK 0x0800 // 0b0000100000000000. +#define VT_DISPLACEMENT_MASK 0x07FF // 0b0000011111111111, also denotes the displacement limit. Set to VT_LOAD to 1.0 + // to test proper handling of encroachment on the displacement limit during + // inserts. + +// Extracts a hash fragment from a uint64_t hash code. +// We take the highest four bits so that keys that map (via modulo) to the same bucket have distinct hash fragments. +static inline FORCE_INLINE uint16_t vt_hashfrag( uint64_t hash ) +{ + return ( hash >> 48 ) & VT_HASH_FRAG_MASK; +} + +// Standard quadratic probing formula that guarantees that all buckets are visited when the bucket count is a power of +// two (at least in theory, because the displacement limit could terminate the search early when the bucket count is +// high). +static inline FORCE_INLINE size_t vt_quadratic( uint16_t displacement ) +{ + return ( (size_t)displacement * displacement + displacement ) / 2; +} + +#define VT_MIN_NONZERO_BUCKET_COUNT 8 // Must be a power of two. + +// Function to find the left-most non-zero uint16_t in a uint64_t. +// This function is used when we scan four buckets at a time while iterating and relies on compiler intrinsics wherever +// possible. + +#if defined( __GNUC__ ) && ULLONG_MAX == 0xFFFFFFFFFFFFFFFF + +static inline FORCE_INLINE int vt_first_nonzero_uint16( uint64_t val ) +{ + const uint16_t endian_checker = 0x0001; + if( *(const char *)&endian_checker ) // Little-endian (the compiler will optimize away the check at -O1 and above). + return __builtin_ctzll( val ) / 16; + + return __builtin_clzll( val ) / 16; +} + +#elif defined( _MSC_VER ) && ( defined( _M_X64 ) || defined( _M_ARM64 ) ) + +#include +#pragma intrinsic(_BitScanForward64) +#pragma intrinsic(_BitScanReverse64) + +static inline FORCE_INLINE int vt_first_nonzero_uint16( uint64_t val ) +{ + unsigned long result; + + const uint16_t endian_checker = 0x0001; + if( *(const char *)&endian_checker ) + _BitScanForward64( &result, val ); + else + { + _BitScanReverse64( &result, val ); + result = 63 - result; + } + + return result / 16; +} + +#else + +static inline FORCE_INLINE int vt_first_nonzero_uint16( uint64_t val ) +{ + int result = 0; + + uint32_t half; + memcpy( &half, &val, sizeof( uint32_t ) ); + if( !half ) + result += 2; + + uint16_t quarter; + memcpy( &quarter, (char *)&val + result * sizeof( uint16_t ), sizeof( uint16_t ) ); + if( !quarter ) + result += 1; + + return result; +} + +#endif + +// When the bucket count is zero, setting the metadata pointer to point to a VT_EMPTY placeholder, rather than NULL, +// allows us to avoid checking for a zero bucket count during insertion and lookup. +static const uint16_t vt_empty_placeholder_metadatum = VT_EMPTY; + +// Default hash and comparison functions. + +// Fast-hash, as described by https://jonkagstrom.com/bit-mixer-construction and +// https://code.google.com/archive/p/fast-hash. +// In testing, this hash function provided slightly better performance than the Murmur3 mixer. +static inline FORCE_INLINE uint64_t vt_hash_integer( uint64_t key ) +{ + key ^= key >> 23; + key *= 0x2127599bf4325c37ull; + key ^= key >> 47; + return key; +} + +// For hashing strings, we use the public-domain Wyhash +// (https://github.com/wangyi-fudan/wyhash) with the following modifications: +// * We use a fixed seed and secret (the defaults suggested in the Wyhash repository). +// * We do not handle endianness, so the result will differ depending on the platform. +// * We omit the code optimized for 32-bit platforms. + +static inline FORCE_INLINE void vt_wymum( uint64_t *a, uint64_t *b ) +{ +#if defined( __SIZEOF_INT128__ ) + __uint128_t r = *a; + r *= *b; + *a = (uint64_t)r; + *b = (uint64_t)( r >> 64 ); +#elif defined( _MSC_VER ) && defined( _M_X64 ) + *a = _umul128( *a, *b, b ); +#else + uint64_t ha = *a >> 32; + uint64_t hb = *b >> 32; + uint64_t la = (uint32_t)*a; + uint64_t lb = (uint32_t)*b; + uint64_t rh = ha * hb; + uint64_t rm0 = ha * lb; + uint64_t rm1 = hb * la; + uint64_t rl = la * lb; + uint64_t t = rl + ( rm0 << 32 ); + uint64_t c = t < rl; + uint64_t lo = t + ( rm1 << 32 ); + c += lo < t; + uint64_t hi = rh + ( rm0 >> 32 ) + ( rm1 >> 32 ) + c; + *a = lo; + *b = hi; +#endif +} + +static inline FORCE_INLINE uint64_t vt_wymix( uint64_t a, uint64_t b ) +{ + vt_wymum( &a, &b ); + return a ^ b; +} + +static inline FORCE_INLINE uint64_t vt_wyr8( const unsigned char *p ) +{ + uint64_t v; + memcpy( &v, p, 8 ); + return v; +} + +static inline FORCE_INLINE uint64_t vt_wyr4( const unsigned char *p ) +{ + uint32_t v; + memcpy( &v, p, 4 ); + return v; +} + +static inline FORCE_INLINE uint64_t vt_wyr3( const unsigned char *p, size_t k ) +{ + return ( ( (uint64_t)p[ 0 ] ) << 16 ) | ( ( (uint64_t)p[ k >> 1 ] ) << 8 ) | p[ k - 1 ]; +} + +static inline FORCE_INLINE size_t vt_wyhash( const void *key, size_t len ) +{ + const unsigned char *p = (const unsigned char *)key; + uint64_t seed = 0xca813bf4c7abf0a9ull; + uint64_t a; + uint64_t b; + if( VT_LIKELY( len <= 16 ) ) + { + if( VT_LIKELY( len >= 4 ) ) + { + a = ( vt_wyr4( p ) << 32 ) | vt_wyr4( p + ( ( len >> 3 ) << 2 ) ); + b = ( vt_wyr4( p + len - 4 ) << 32 ) | vt_wyr4( p + len - 4 - ( ( len >> 3 ) << 2 ) ); + } + else if( VT_LIKELY( len > 0 ) ) + { + a = vt_wyr3( p, len ); + b = 0; + } + else + { + a = 0; + b = 0; + } + } + else + { + size_t i = len; + if( VT_UNLIKELY( i >= 48 ) ) + { + uint64_t see1 = seed; + uint64_t see2 = seed; + do{ + seed = vt_wymix( vt_wyr8( p ) ^ 0x8bb84b93962eacc9ull, vt_wyr8( p + 8 ) ^ seed ); + see1 = vt_wymix( vt_wyr8( p + 16 ) ^ 0x4b33a62ed433d4a3ull, vt_wyr8( p + 24 ) ^ see1 ); + see2 = vt_wymix( vt_wyr8( p + 32 ) ^ 0x4d5a2da51de1aa47ull, vt_wyr8( p + 40 ) ^ see2 ); + p += 48; + i -= 48; + } + while( VT_LIKELY( i >= 48 ) ); + seed ^= see1 ^ see2; + } + + while( VT_UNLIKELY( i > 16 ) ) + { + seed = vt_wymix( vt_wyr8( p ) ^ 0x8bb84b93962eacc9ull, vt_wyr8( p + 8 ) ^ seed ); + i -= 16; + p += 16; + } + + a = vt_wyr8( p + i - 16 ); + b = vt_wyr8( p + i - 8 ); + } + + a ^= 0x8bb84b93962eacc9ull; + b ^= seed; + vt_wymum( &a, &b ); + return (size_t)vt_wymix( a ^ 0x2d358dccaa6c78a5ull ^ len, b ^ 0x8bb84b93962eacc9ull ); +} + +static inline FORCE_INLINE uint64_t vt_hash_string( const char *key ) +{ + return vt_wyhash( key, strlen( key ) ); +} + +static inline FORCE_INLINE bool vt_cmpr_integer( uint64_t key_1, uint64_t key_2 ) +{ + return key_1 == key_2; +} + +static inline FORCE_INLINE bool vt_cmpr_string( const char *key_1, const char *key_2 ) +{ + return strcmp( key_1, key_2 ) == 0; +} + +// Default allocation and free functions. + +static inline FORCE_INLINE void *vt_malloc( size_t size ) +{ + return malloc( size ); +} + +static inline FORCE_INLINE void vt_free( void *ptr, size_t size ) +{ + (void)size; + free( ptr ); +} + +static inline FORCE_INLINE void *vt_malloc_with_ctx( size_t size, void *ctx ) +{ + (void)ctx; + return malloc( size ); +} + +static inline FORCE_INLINE void vt_free_with_ctx( void *ptr, size_t size, void *ctx ) +{ + (void)size; + (void)ctx; + free( ptr ); +} + +// The rest of the common header section pertains to the C11 generic macro API. +// This interface is based on the extendible-_Generic mechanism documented in detail at +// https://github.com/JacksonAllan/CC/blob/main/articles/Better_C_Generics_Part_1_The_Extendible_Generic.md. +// In summary, instantiating a template also defines wrappers for the template's types and functions with names in the +// pattern of vt_table_NNNN and vt_init_NNNN, where NNNN is an automatically generated integer unique to the template +// instance in the current translation unit. +// These wrappers plug into _Generic-based API macros, which use preprocessor magic to automatically generate _Generic +// slots for every existing template instance. +#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L && !defined( VT_NO_C11_GENERIC_API ) + +// Octal counter that supports up to 511 hash table templates. +#define VT_TEMPLATE_COUNT_D1 0 // Digit 1, i.e. least significant digit. +#define VT_TEMPLATE_COUNT_D2 0 +#define VT_TEMPLATE_COUNT_D3 0 + +// Four-way concatenation macro. +#define VT_CAT_4_( a, b, c, d ) a##b##c##d +#define VT_CAT_4( a, b, c, d ) VT_CAT_4_( a, b, c, d ) + +// Provides the current value of the counter as a three-digit octal number preceded by 0. +#define VT_TEMPLATE_COUNT VT_CAT_4( 0, VT_TEMPLATE_COUNT_D3, VT_TEMPLATE_COUNT_D2, VT_TEMPLATE_COUNT_D1 ) + +// _Generic-slot generation macros. + +#define VT_GENERIC_SLOT( ty, fn, n ) , VT_CAT( ty, n ): VT_CAT( fn, n ) +#define VT_R1_0( ty, fn, d3, d2 ) +#define VT_R1_1( ty, fn, d3, d2 ) VT_GENERIC_SLOT( ty, fn, VT_CAT_4( 0, d3, d2, 0 ) ) +#define VT_R1_2( ty, fn, d3, d2 ) VT_GENERIC_SLOT( ty, fn, VT_CAT_4( 0, d3, d2, 1 ) ) VT_R1_1( ty, fn, d3, d2 ) +#define VT_R1_3( ty, fn, d3, d2 ) VT_GENERIC_SLOT( ty, fn, VT_CAT_4( 0, d3, d2, 2 ) ) VT_R1_2( ty, fn, d3, d2 ) +#define VT_R1_4( ty, fn, d3, d2 ) VT_GENERIC_SLOT( ty, fn, VT_CAT_4( 0, d3, d2, 3 ) ) VT_R1_3( ty, fn, d3, d2 ) +#define VT_R1_5( ty, fn, d3, d2 ) VT_GENERIC_SLOT( ty, fn, VT_CAT_4( 0, d3, d2, 4 ) ) VT_R1_4( ty, fn, d3, d2 ) +#define VT_R1_6( ty, fn, d3, d2 ) VT_GENERIC_SLOT( ty, fn, VT_CAT_4( 0, d3, d2, 5 ) ) VT_R1_5( ty, fn, d3, d2 ) +#define VT_R1_7( ty, fn, d3, d2 ) VT_GENERIC_SLOT( ty, fn, VT_CAT_4( 0, d3, d2, 6 ) ) VT_R1_6( ty, fn, d3, d2 ) +#define VT_R1_8( ty, fn, d3, d2 ) VT_GENERIC_SLOT( ty, fn, VT_CAT_4( 0, d3, d2, 7 ) ) VT_R1_7( ty, fn, d3, d2 ) +#define VT_R2_0( ty, fn, d3 ) +#define VT_R2_1( ty, fn, d3 ) VT_R1_8( ty, fn, d3, 0 ) +#define VT_R2_2( ty, fn, d3 ) VT_R1_8( ty, fn, d3, 1 ) VT_R2_1( ty, fn, d3 ) +#define VT_R2_3( ty, fn, d3 ) VT_R1_8( ty, fn, d3, 2 ) VT_R2_2( ty, fn, d3 ) +#define VT_R2_4( ty, fn, d3 ) VT_R1_8( ty, fn, d3, 3 ) VT_R2_3( ty, fn, d3 ) +#define VT_R2_5( ty, fn, d3 ) VT_R1_8( ty, fn, d3, 4 ) VT_R2_4( ty, fn, d3 ) +#define VT_R2_6( ty, fn, d3 ) VT_R1_8( ty, fn, d3, 5 ) VT_R2_5( ty, fn, d3 ) +#define VT_R2_7( ty, fn, d3 ) VT_R1_8( ty, fn, d3, 6 ) VT_R2_6( ty, fn, d3 ) +#define VT_R2_8( ty, fn, d3 ) VT_R1_8( ty, fn, d3, 7 ) VT_R2_7( ty, fn, d3 ) +#define VT_R3_0( ty, fn ) +#define VT_R3_1( ty, fn ) VT_R2_8( ty, fn, 0 ) +#define VT_R3_2( ty, fn ) VT_R2_8( ty, fn, 1 ) VT_R3_1( ty, fn ) +#define VT_R3_3( ty, fn ) VT_R2_8( ty, fn, 2 ) VT_R3_2( ty, fn ) +#define VT_R3_4( ty, fn ) VT_R2_8( ty, fn, 3 ) VT_R3_3( ty, fn ) +#define VT_R3_5( ty, fn ) VT_R2_8( ty, fn, 4 ) VT_R3_4( ty, fn ) +#define VT_R3_6( ty, fn ) VT_R2_8( ty, fn, 5 ) VT_R3_5( ty, fn ) +#define VT_R3_7( ty, fn ) VT_R2_8( ty, fn, 6 ) VT_R3_6( ty, fn ) + +#define VT_GENERIC_SLOTS( ty, fn ) \ +VT_CAT( VT_R1_, VT_TEMPLATE_COUNT_D1 )( ty, fn, VT_TEMPLATE_COUNT_D3, VT_TEMPLATE_COUNT_D2 ) \ +VT_CAT( VT_R2_, VT_TEMPLATE_COUNT_D2 )( ty, fn, VT_TEMPLATE_COUNT_D3 ) \ +VT_CAT( VT_R3_, VT_TEMPLATE_COUNT_D3 )( ty, fn ) \ + +// Actual generic API macros. + +// vt_init must be handled as a special case because it could take one or two arguments, depending on whether CTX_TY +// was defined. +#define VT_ARG_3( _1, _2, _3, ... ) _3 +#define vt_init( ... ) VT_ARG_3( __VA_ARGS__, vt_init_with_ctx, vt_init_without_ctx, )( __VA_ARGS__ ) +#define vt_init_without_ctx( table ) _Generic( *( table ) VT_GENERIC_SLOTS( vt_table_, vt_init_ ) )( table ) +#define vt_init_with_ctx( table, ... ) _Generic( *( table ) \ + VT_GENERIC_SLOTS( vt_table_, vt_init_ ) \ +)( table, __VA_ARGS__ ) \ + +#define vt_init_clone( table, ... ) _Generic( *( table ) \ + VT_GENERIC_SLOTS( vt_table_, vt_init_clone_ ) \ +)( table, __VA_ARGS__ ) \ + +#define vt_size( table )_Generic( *( table ) VT_GENERIC_SLOTS( vt_table_, vt_size_ ) )( table ) + +#define vt_bucket_count( table ) _Generic( *( table ) VT_GENERIC_SLOTS( vt_table_, vt_bucket_count_ ) )( table ) + +#define vt_is_end( itr ) _Generic( itr VT_GENERIC_SLOTS( vt_table_itr_, vt_is_end_ ) )( itr ) + +#define vt_insert( table, ... ) _Generic( *( table ) VT_GENERIC_SLOTS( vt_table_, vt_insert_ ) )( table, __VA_ARGS__ ) + +#define vt_get_or_insert( table, ... ) _Generic( *( table ) \ + VT_GENERIC_SLOTS( vt_table_, vt_get_or_insert_ ) \ +)( table, __VA_ARGS__ ) \ + +#define vt_get( table, ... ) _Generic( *( table ) VT_GENERIC_SLOTS( vt_table_, vt_get_ ) )( table, __VA_ARGS__ ) + +#define vt_erase( table, ... ) _Generic( *( table ) VT_GENERIC_SLOTS( vt_table_, vt_erase_ ) )( table, __VA_ARGS__ ) + +#define vt_next( itr ) _Generic( itr VT_GENERIC_SLOTS( vt_table_itr_, vt_next_ ) )( itr ) + +#define vt_erase_itr( table, ... ) _Generic( *( table ) \ + VT_GENERIC_SLOTS( vt_table_, vt_erase_itr_ ) \ +)( table, __VA_ARGS__ ) \ + +#define vt_reserve( table, ... ) _Generic( *( table ) VT_GENERIC_SLOTS( vt_table_, vt_reserve_ ) )( table, __VA_ARGS__ ) + +#define vt_shrink( table ) _Generic( *( table ) VT_GENERIC_SLOTS( vt_table_, vt_shrink_ ) )( table ) + +#define vt_first( table ) _Generic( *( table ) VT_GENERIC_SLOTS( vt_table_, vt_first_ ) )( table ) + +#define vt_clear( table ) _Generic( *( table ) VT_GENERIC_SLOTS( vt_table_, vt_clear_ ) )( table ) + +#define vt_cleanup( table ) _Generic( *( table ) VT_GENERIC_SLOTS( vt_table_, vt_cleanup_ ) )( table ) + +#endif + +#endif + +/*--------------------------------------------------------------------------------------------------------------------*/ +/* Prefixed structs */ +/*--------------------------------------------------------------------------------------------------------------------*/ + +#ifndef IMPLEMENTATION_MODE + +typedef struct +{ + KEY_TY key; + #ifdef VAL_TY + VAL_TY val; + #endif +} VT_CAT( NAME, _bucket ); + +typedef struct +{ + VT_CAT( NAME, _bucket ) *data; + uint16_t *metadatum; + uint16_t *metadata_end; // Iterators carry an internal end pointer so that NAME_is_end does not need the table to be + // passed in as an argument. + // This also allows for the zero-bucket-count check to occur once in NAME_first, rather than + // repeatedly in NAME_is_end. + size_t home_bucket; // SIZE_MAX if home bucket is unknown. +} VT_CAT( NAME, _itr ); + +typedef struct +{ + size_t key_count; + size_t buckets_mask; // Rather than storing the bucket count directly, we store the bit mask used to reduce a hash + // code or displacement-derived bucket index to the buckets array, i.e. the bucket count minus + // one. + // Consequently, a zero bucket count (i.e. when .metadata points to the placeholder) constitutes + // a special case, represented by all bits unset (i.e. zero). + VT_CAT( NAME, _bucket ) *buckets; + uint16_t *metadata; // As described above, each metadatum consists of a 4-bit hash-code fragment (X), a 1-bit flag + // indicating whether the key in this bucket begins a chain associated with the bucket (Y), and + // an 11-bit value indicating the quadratic displacement of the next key in the chain (Z): + // XXXXYZZZZZZZZZZZ. + #ifdef CTX_TY + CTX_TY ctx; + #endif +} NAME; + +#endif + +/*--------------------------------------------------------------------------------------------------------------------*/ +/* Function prototypes */ +/*--------------------------------------------------------------------------------------------------------------------*/ + +#if defined( HEADER_MODE ) || defined( IMPLEMENTATION_MODE ) +#define VT_API_FN_QUALIFIERS +#else +#define VT_API_FN_QUALIFIERS static inline FORCE_INLINE +#endif + +#ifndef IMPLEMENTATION_MODE + +VT_API_FN_QUALIFIERS void VT_CAT( NAME, _init )( + NAME * + #ifdef CTX_TY + , CTX_TY + #endif +); + +VT_API_FN_QUALIFIERS bool VT_CAT( NAME, _init_clone )( + NAME *, + const NAME * + #ifdef CTX_TY + , CTX_TY + #endif +); + +VT_API_FN_QUALIFIERS size_t VT_CAT( NAME, _size )( const NAME * ); + +VT_API_FN_QUALIFIERS size_t VT_CAT( NAME, _bucket_count )( const NAME * ); + +VT_API_FN_QUALIFIERS bool VT_CAT( NAME, _is_end )( VT_CAT( NAME, _itr ) ); + +VT_API_FN_QUALIFIERS VT_CAT( NAME, _itr ) VT_CAT( NAME, _insert )( + NAME *, + KEY_TY + #ifdef VAL_TY + , VAL_TY + #endif +); + +VT_API_FN_QUALIFIERS VT_CAT( NAME, _itr ) VT_CAT( NAME, _get_or_insert )( + NAME *, + KEY_TY + #ifdef VAL_TY + , VAL_TY + #endif +); + +VT_API_FN_QUALIFIERS VT_CAT( NAME, _itr ) VT_CAT( NAME, _get )( + const NAME *table, +#ifdef KEY_GET_CONST + const KEY_TY key +#else + KEY_TY key +#endif +); + +VT_API_FN_QUALIFIERS bool VT_CAT( NAME, _erase )( NAME *, KEY_TY ); + +VT_API_FN_QUALIFIERS VT_CAT( NAME, _itr ) VT_CAT( NAME, _next )( VT_CAT( NAME, _itr ) ); + +VT_API_FN_QUALIFIERS bool VT_CAT( NAME, _reserve )( NAME *, size_t ); + +VT_API_FN_QUALIFIERS bool VT_CAT( NAME, _shrink )( NAME * ); + +VT_API_FN_QUALIFIERS VT_CAT( NAME, _itr ) VT_CAT( NAME, _first )( const NAME * ); + +VT_API_FN_QUALIFIERS void VT_CAT( NAME, _clear )( NAME * ); + +VT_API_FN_QUALIFIERS void VT_CAT( NAME, _cleanup )( NAME * ); + +// Not an API function, but must be prototyped anyway because it is called by the inline NAME_erase_itr below. +VT_API_FN_QUALIFIERS bool VT_CAT( NAME, _erase_itr_raw ) ( NAME *, VT_CAT( NAME, _itr ) ); + +// Erases the key pointed to by itr and returns an iterator to the next key in the table. +// This function must be inlined to ensure that the compiler optimizes away the NAME_fast_forward call if the returned +// iterator is discarded. +#ifdef __GNUC__ +static inline FORCE_INLINE +#elif defined( _MSC_VER ) +static __forceinline +#else +static inline +#endif +VT_CAT( NAME, _itr ) VT_CAT( NAME, _erase_itr )( NAME *table, VT_CAT( NAME, _itr ) itr ) +{ + if( VT_CAT( NAME, _erase_itr_raw )( table, itr ) ) + return VT_CAT( NAME, _next )( itr ); + + return itr; +} + +#endif + +/*--------------------------------------------------------------------------------------------------------------------*/ +/* Function implementations */ +/*--------------------------------------------------------------------------------------------------------------------*/ + +#ifndef HEADER_MODE + +// Default settings. + +#ifndef MAX_LOAD +#define MAX_LOAD 0.9 +#endif + +#ifndef MALLOC_FN +#ifdef CTX_TY +#define MALLOC_FN vt_malloc_with_ctx +#else +#define MALLOC_FN vt_malloc +#endif +#endif + +#ifndef FREE_FN +#ifdef CTX_TY +#define FREE_FN vt_free_with_ctx +#else +#define FREE_FN vt_free +#endif +#endif + +#ifndef HASH_FN +#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L +#ifdef _MSC_VER // In MSVC, the compound literal in the _Generic triggers a warning about unused local variables at /W4. +#define HASH_FN \ +_Pragma( "warning( push )" ) \ +_Pragma( "warning( disable: 4189 )" ) \ +_Generic( ( KEY_TY ){ 0 }, char *: vt_hash_string, const char*: vt_hash_string, default: vt_hash_integer ) \ +_Pragma( "warning( pop )" ) +#else +#define HASH_FN _Generic( ( KEY_TY ){ 0 }, \ + char *: vt_hash_string, \ + const char*: vt_hash_string, \ + default: vt_hash_integer \ +) +#endif +#else +#error Hash function inference is only available in C11 and later. In C99, you need to define HASH_FN manually to \ +vt_hash_integer, vt_hash_string, or your own custom function with the signature uint64_t ( KEY_TY ). +#endif +#endif + +#ifndef CMPR_FN +#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L +#ifdef _MSC_VER +#define CMPR_FN \ +_Pragma( "warning( push )" ) \ +_Pragma( "warning( disable: 4189 )" ) \ +_Generic( ( KEY_TY ){ 0 }, char *: vt_cmpr_string, const char*: vt_cmpr_string, default: vt_cmpr_integer ) \ +_Pragma( "warning( pop )" ) +#else +#define CMPR_FN _Generic( ( KEY_TY ){ 0 }, \ + char *: vt_cmpr_string, \ + const char*: vt_cmpr_string, \ + default: vt_cmpr_integer \ +) +#endif +#else +#error Comparison function inference is only available in C11 and later. In C99, you need to define CMPR_FN manually \ +to vt_cmpr_integer, vt_cmpr_string, or your own custom function with the signature bool ( KEY_TY, KEY_TY ). +#endif +#endif + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wcast-qual" + +VT_API_FN_QUALIFIERS void VT_CAT( NAME, _init )( + NAME *table + #ifdef CTX_TY + , CTX_TY ctx + #endif +) +{ + table->key_count = 0; + table->buckets_mask = 0x0000000000000000ull; + table->buckets = NULL; + table->metadata = (uint16_t *)&vt_empty_placeholder_metadatum; + #ifdef CTX_TY + table->ctx = ctx; + #endif +} + +#pragma GCC diagnostic pop + +// For efficiency, especially in the case of a small table, the buckets array and metadata share the same dynamic memory +// allocation: +// +-----------------------------+-----+----------------+--------+ +// | Buckets | Pad | Metadata | Excess | +// +-----------------------------+-----+----------------+--------+ +// Any allocated metadata array requires four excess elements to ensure that iteration functions, which read four +// metadata at a time, never read beyond the end of it. +// This function returns the offset of the beginning of the metadata, i.e. the size of the buckets array plus the +// (usually zero) padding. +// It assumes that the bucket count is not zero. +static inline FORCE_INLINE size_t VT_CAT( NAME, _metadata_offset )( NAME *table ) +{ + // Use sizeof, rather than alignof, for C99 compatibility. + return ( ( ( table->buckets_mask + 1 ) * sizeof( VT_CAT( NAME, _bucket ) ) + sizeof( uint16_t ) - 1 ) / + sizeof( uint16_t ) ) * sizeof( uint16_t ); +} + +// Returns the total allocation size, including the buckets array, padding, metadata, and excess metadata. +// As above, this function assumes that the bucket count is not zero. +static inline FORCE_INLINE size_t VT_CAT( NAME, _total_alloc_size )( NAME *table ) +{ + return VT_CAT( NAME, _metadata_offset )( table ) + ( table->buckets_mask + 1 + 4 ) * sizeof( uint16_t ); +} + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wcast-qual" + +VT_API_FN_QUALIFIERS bool VT_CAT( NAME, _init_clone )( + NAME *table, + const NAME *source + #ifdef CTX_TY + , CTX_TY ctx + #endif +) +{ + table->key_count = source->key_count; + table->buckets_mask = source->buckets_mask; + #ifdef CTX_TY + table->ctx = ctx; + #endif + + if( !source->buckets_mask ) + { + table->metadata = (uint16_t *)&vt_empty_placeholder_metadatum; + table->buckets = NULL; + return true; + } + + void *allocation = MALLOC_FN( + VT_CAT( NAME, _total_alloc_size )( table ) + #ifdef CTX_TY + , &table->ctx + #endif + ); + + if( VT_UNLIKELY( !allocation ) ) + return false; + + table->buckets = (VT_CAT( NAME, _bucket ) *)allocation; + table->metadata = (uint16_t *)( (unsigned char *)allocation + VT_CAT( NAME, _metadata_offset )( table ) ); + memcpy( allocation, source->buckets, VT_CAT( NAME, _total_alloc_size )( table ) ); + + return true; +} + +#pragma GCC diagnostic pop + +VT_API_FN_QUALIFIERS size_t VT_CAT( NAME, _size )( const NAME *table ) +{ + return table->key_count; +} + +VT_API_FN_QUALIFIERS size_t VT_CAT( NAME, _bucket_count )( const NAME *table ) +{ + // If the bucket count is zero, buckets_mask will be zero, not the bucket count minus one. + // We account for this special case by adding (bool)buckets_mask rather than one. + return table->buckets_mask + (bool)table->buckets_mask; +} + +VT_API_FN_QUALIFIERS bool VT_CAT( NAME, _is_end )( VT_CAT( NAME, _itr ) itr ) +{ + return itr.metadatum == itr.metadata_end; +} + +// Finds the earliest empty bucket in which a key belonging to home_bucket can be placed, assuming that home_bucket +// is already occupied. +// The reason to begin the search at home_bucket, rather than the end of the existing chain, is that keys deleted from +// other chains might have freed up buckets that could fall in this chain before the final key. +// Returns true if an empty bucket within the range of the displacement limit was found, in which case the final two +// pointer arguments contain the index of the empty bucket and its quadratic displacement from home_bucket. +static inline FORCE_INLINE bool VT_CAT( NAME, _find_first_empty )( + NAME *table, + size_t home_bucket, + size_t *empty, + uint16_t *displacement +) +{ + *displacement = 1; + size_t linear_dispacement = 1; + + while( true ) + { + *empty = ( home_bucket + linear_dispacement ) & table->buckets_mask; + if( table->metadata[ *empty ] == VT_EMPTY ) + return true; + + if( VT_UNLIKELY( ++*displacement == VT_DISPLACEMENT_MASK ) ) + return false; + + linear_dispacement += *displacement; + } +} + +// Finds the key in the chain beginning in home_bucket after which to link a new key with displacement_to_empty +// quadratic displacement and returns the index of the bucket containing that key. +// Although the new key could simply be linked to the end of the chain, keeping the chain ordered by displacement +// theoretically improves cache locality during lookups. +static inline FORCE_INLINE size_t VT_CAT( NAME, _find_insert_location_in_chain )( + NAME *table, + size_t home_bucket, + uint16_t displacement_to_empty +) +{ + size_t candidate = home_bucket; + while( true ) + { + uint16_t displacement = table->metadata[ candidate ] & VT_DISPLACEMENT_MASK; + + if( displacement > displacement_to_empty ) + return candidate; + + candidate = ( home_bucket + vt_quadratic( displacement ) ) & table->buckets_mask; + } +} + +// Frees up a bucket occupied by a key not belonging there so that a new key belonging there can be placed there as the +// beginning of a new chain. +// This requires: +// * Finding the previous key in the chain to which the occupying key belongs by rehashing it and then traversing the +// chain. +// * Disconnecting the key from the chain. +// * Finding the appropriate empty bucket to which to move the key. +// * Moving the key (and value) data to the empty bucket. +// * Re-linking the key to the chain. +// Returns true if the eviction succeeded, or false if no empty bucket to which to evict the occupying key could be +// found within the displacement limit. +static inline FORCE_INLINE bool VT_CAT( NAME, _evict )( NAME *table, size_t bucket ) +{ + // Find the previous key in chain. + size_t home_bucket = HASH_FN( table->buckets[ bucket ].key ) & table->buckets_mask; + size_t prev = home_bucket; + while( true ) + { + size_t next = ( home_bucket + vt_quadratic( table->metadata[ prev ] & VT_DISPLACEMENT_MASK ) ) & + table->buckets_mask; + + if( next == bucket ) + break; + + prev = next; + } + + // Disconnect the key from chain. + table->metadata[ prev ] = ( table->metadata[ prev ] & ~VT_DISPLACEMENT_MASK ) | ( table->metadata[ bucket ] & + VT_DISPLACEMENT_MASK ); + + // Find the empty bucket to which to move the key. + size_t empty; + uint16_t displacement; + if( VT_UNLIKELY( !VT_CAT( NAME, _find_first_empty )( table, home_bucket, &empty, &displacement ) ) ) + return false; + + // Find the key in the chain after which to link the moved key. + prev = VT_CAT( NAME, _find_insert_location_in_chain )( table, home_bucket, displacement ); + + // Move the key (and value) data. + table->buckets[ empty ] = table->buckets[ bucket ]; + + // Re-link the key to the chain from its new bucket. + table->metadata[ empty ] = ( table->metadata[ bucket ] & VT_HASH_FRAG_MASK ) | ( table->metadata[ prev ] & + VT_DISPLACEMENT_MASK ); + table->metadata[ prev ] = ( table->metadata[ prev ] & ~VT_DISPLACEMENT_MASK ) | displacement; + + return true; +} + +// Returns an end iterator, i.e. any iterator for which .metadatum == .metadata_end. +// This function just cleans up the library code in functions that return an end iterator as a failure indicator. +static inline FORCE_INLINE VT_CAT( NAME, _itr ) VT_CAT( NAME, _end_itr )( void ) +{ + VT_CAT( NAME, _itr ) itr = { NULL, NULL, NULL, 0 }; + return itr; +} + +// Inserts a key, optionally replacing the existing key if it already exists. +// There are two main cases that must be handled: +// * If the key's home bucket is empty or occupied by a key that does not belong there, then the key is inserted there, +// evicting the occupying key if there is one. +// * Otherwise, the chain of keys beginning at the home bucket is (if unique is false) traversed in search of a matching +// key. +// If none is found, then the new key is inserted at the earliest available bucket, per quadratic probing from the +// home bucket, and then linked to the chain in a manner that maintains its quadratic order. +// The unique argument tells the function whether to skip searching for the key before inserting it (on rehashing, this +// step is unnecessary). +// The replace argument tells the function whether to replace an existing key. +// If replace is true, the function returns an iterator to the inserted key, or an end iterator if the key was not +// inserted because of the maximum load factor or displacement limit constraints. +// If replace is false, then the return value is as described above, except that if the key already exists, the function +// returns an iterator to the existing key. +static inline FORCE_INLINE VT_CAT( NAME, _itr ) VT_CAT( NAME, _insert_raw )( + NAME *table, + KEY_TY key, + #ifdef VAL_TY + VAL_TY *val, + #endif + bool unique, + bool replace +) +{ + uint64_t hash = HASH_FN( key ); + uint16_t hashfrag = vt_hashfrag( hash ); + size_t home_bucket = hash & table->buckets_mask; + + // Case 1: The home bucket is empty or contains a key that doesn't belong there. + // This case also implicitly handles the case of a zero bucket count, since home_bucket will be zero and metadata[ 0 ] + // will be the empty placeholder. + // In that scenario, the zero buckets_mask triggers the below load-factor check. + if( !( table->metadata[ home_bucket ] & VT_IN_HOME_BUCKET_MASK ) ) + { + if( + // Load-factor check. + VT_UNLIKELY( table->key_count + 1 > VT_CAT( NAME, _bucket_count )( table ) * MAX_LOAD ) || + // Vacate the home bucket if it contains a key. + ( table->metadata[ home_bucket ] != VT_EMPTY && VT_UNLIKELY( !VT_CAT( NAME, _evict )( table, home_bucket ) ) ) + ) + return VT_CAT( NAME, _end_itr )(); + + table->buckets[ home_bucket ].key = key; + #ifdef VAL_TY + table->buckets[ home_bucket ].val = *val; + #endif + table->metadata[ home_bucket ] = hashfrag | VT_IN_HOME_BUCKET_MASK | VT_DISPLACEMENT_MASK; + + ++table->key_count; + + VT_CAT( NAME, _itr ) itr = { + table->buckets + home_bucket, + table->metadata + home_bucket, + table->metadata + table->buckets_mask + 1, // Iteration stopper (i.e. the first of the four excess metadata). + home_bucket + }; + return itr; + } + + // Case 2: The home bucket contains the beginning of a chain. + + // Optionally, check the existing chain. + if( !unique ) + { + size_t bucket = home_bucket; + while( true ) + { + if( + ( table->metadata[ bucket ] & VT_HASH_FRAG_MASK ) == hashfrag && + VT_LIKELY( CMPR_FN( table->buckets[ bucket ].key, key ) ) + ) + { + if( replace ) + { + #ifdef KEY_DTOR_FN + KEY_DTOR_FN( table->buckets[ bucket ].key ); + #endif + table->buckets[ bucket ].key = key; + + #ifdef VAL_TY + #ifdef VAL_DTOR_FN + VAL_DTOR_FN( table->buckets[ bucket ].val ); + #endif + table->buckets[ bucket ].val = *val; + #endif + } + + VT_CAT( NAME, _itr ) itr = { + table->buckets + bucket, + table->metadata + bucket, + table->metadata + table->buckets_mask + 1, + home_bucket + }; + return itr; + } + + uint16_t displacement = table->metadata[ bucket ] & VT_DISPLACEMENT_MASK; + if( displacement == VT_DISPLACEMENT_MASK ) + break; + + bucket = ( home_bucket + vt_quadratic( displacement ) ) & table->buckets_mask; + } + } + + size_t empty; + uint16_t displacement; + if( + VT_UNLIKELY( + // Load-factor check. + table->key_count + 1 > VT_CAT( NAME, _bucket_count )( table ) * MAX_LOAD || + // Find the earliest empty bucket, per quadratic probing. + !VT_CAT( NAME, _find_first_empty )( table, home_bucket, &empty, &displacement ) + ) + ) + return VT_CAT( NAME, _end_itr )(); + + // Insert the new key (and value) in the empty bucket and link it to the chain. + + size_t prev = VT_CAT( NAME, _find_insert_location_in_chain )( table, home_bucket, displacement ); + + table->buckets[ empty ].key = key; + #ifdef VAL_TY + table->buckets[ empty ].val = *val; + #endif + table->metadata[ empty ] = hashfrag | ( table->metadata[ prev ] & VT_DISPLACEMENT_MASK ); + table->metadata[ prev ] = ( table->metadata[ prev ] & ~VT_DISPLACEMENT_MASK ) | displacement; + + ++table->key_count; + + VT_CAT( NAME, _itr ) itr = { + table->buckets + empty, + table->metadata + empty, + table->metadata + table->buckets_mask + 1, + home_bucket + }; + return itr; +} + +// Resizes the bucket array. +// This function assumes that bucket_count is a power of two and large enough to accommodate all keys without violating +// the maximum load factor. +// Returns false in the case of allocation failure. +// As this function is called very rarely in _insert and _get_or_insert, ideally it should not be inlined into those +// functions. +// In testing, the no-inline approach showed a performance benefit when inserting existing keys (i.e. replacing). +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wattributes" // Silence warning about combining noinline with static inline FORCE_INLINE. +__attribute__((noinline)) static inline +#elif defined( _MSC_VER ) +__declspec(noinline) static inline +#else +static inline +#endif +bool VT_CAT( NAME, _rehash )( NAME *table, size_t bucket_count ) +{ + // The attempt to resize the bucket array and rehash the keys must occur inside a loop that incrementally doubles the + // target bucket count because a failure could theoretically occur at any load factor due to the displacement limit. + while( true ) + { + NAME new_table = { + 0, + bucket_count - 1, + NULL, + NULL + #ifdef CTX_TY + , table->ctx + #endif + }; + + void *allocation = MALLOC_FN( + VT_CAT( NAME, _total_alloc_size )( &new_table ) + #ifdef CTX_TY + , &new_table.ctx + #endif + ); + + if( VT_UNLIKELY( !allocation ) ) + return false; + + new_table.buckets = (VT_CAT( NAME, _bucket ) *)allocation; + new_table.metadata = (uint16_t *)( (unsigned char *)allocation + VT_CAT( NAME, _metadata_offset )( &new_table ) ); + + memset( new_table.metadata, 0x00, ( bucket_count + 4 ) * sizeof( uint16_t ) ); + + // Iteration stopper at the end of the actual metadata array (i.e. the first of the four excess metadata). + new_table.metadata[ bucket_count ] = 0x01; + + for( size_t bucket = 0; bucket < VT_CAT( NAME, _bucket_count )( table ); ++bucket ) + if( table->metadata[ bucket ] != VT_EMPTY ) + { + VT_CAT( NAME, _itr ) itr = VT_CAT( NAME, _insert_raw )( + &new_table, + table->buckets[ bucket ].key, + #ifdef VAL_TY + &table->buckets[ bucket ].val, + #endif + true, + false + ); + + if( VT_UNLIKELY( VT_CAT( NAME, _is_end )( itr ) ) ) + break; + } + + // If a key could not be reinserted due to the displacement limit, double the bucket count and retry. + if( VT_UNLIKELY( new_table.key_count < table->key_count ) ) + { + FREE_FN( + new_table.buckets, + VT_CAT( NAME, _total_alloc_size )( &new_table ) + #ifdef CTX_TY + , &new_table.ctx + #endif + ); + + bucket_count *= 2; + continue; + } + + if( table->buckets_mask ) + FREE_FN( + table->buckets, + VT_CAT( NAME, _total_alloc_size )( table ) + #ifdef CTX_TY + , &table->ctx + #endif + ); + + *table = new_table; + return true; + } +} +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + +// Inserts a key, replacing the existing key if it already exists. +// This function wraps insert_raw in a loop that handles growing and rehashing the table if a new key cannot be inserted +// because of the maximum load factor or displacement limit constraints. +// Returns an iterator to the inserted key, or an end iterator in the case of allocation failure. +VT_API_FN_QUALIFIERS VT_CAT( NAME, _itr ) VT_CAT( NAME, _insert )( + NAME *table, + KEY_TY key + #ifdef VAL_TY + , VAL_TY val + #endif +) +{ + while( true ) + { + VT_CAT( NAME, _itr ) itr = VT_CAT( NAME, _insert_raw )( + table, + key, + #ifdef VAL_TY + &val, + #endif + false, + true + ); + + if( + // Lookup succeeded, in which case itr points to the found key. + VT_LIKELY( !VT_CAT( NAME, _is_end )( itr ) ) || + // Lookup failed and rehash also fails, in which case itr is an end iterator. + VT_UNLIKELY( + !VT_CAT( NAME, _rehash )( + table, table->buckets_mask ? VT_CAT( NAME, _bucket_count )( table ) * 2 : VT_MIN_NONZERO_BUCKET_COUNT + ) + ) + ) + return itr; + } +} + +// Same as NAME_insert, except that if the key already exists, no insertion occurs and the function returns an iterator +// to the existing key. +VT_API_FN_QUALIFIERS VT_CAT( NAME, _itr ) VT_CAT( NAME, _get_or_insert )( + NAME *table, + KEY_TY key + #ifdef VAL_TY + , VAL_TY val + #endif +) +{ + while( true ) + { + VT_CAT( NAME, _itr ) itr = VT_CAT( NAME, _insert_raw )( + table, + key, + #ifdef VAL_TY + &val, + #endif + false, + false + ); + + if( + // Lookup succeeded, in which case itr points to the found key. + VT_LIKELY( !VT_CAT( NAME, _is_end )( itr ) ) || + // Lookup failed and rehash also fails, in which case itr is an end iterator. + VT_UNLIKELY( + !VT_CAT( NAME, _rehash )( + table, table->buckets_mask ? VT_CAT( NAME, _bucket_count )( table ) * 2 : VT_MIN_NONZERO_BUCKET_COUNT + ) + ) + ) + return itr; + } +} + +// Returns an iterator pointing to the specified key, or an end iterator if the key does not exist. +VT_API_FN_QUALIFIERS VT_CAT( NAME, _itr ) VT_CAT( NAME, _get )( const NAME *table, +#ifdef KEY_GET_CONST + const KEY_TY key +#else + KEY_TY key +#endif + ) +{ + uint64_t hash = HASH_FN( key ); + size_t home_bucket = hash & table->buckets_mask; + + // If the home bucket is empty or contains a key that does not belong there, then our key does not exist. + // This check also implicitly handles the case of a zero bucket count, since home_bucket will be zero and + // metadata[ 0 ] will be the empty placeholder. + if( !( table->metadata[ home_bucket ] & VT_IN_HOME_BUCKET_MASK ) ) + return VT_CAT( NAME, _end_itr )(); + + // Traverse the chain of keys belonging to the home bucket. + uint16_t hashfrag = vt_hashfrag( hash ); + size_t bucket = home_bucket; + while( true ) + { + if( + ( table->metadata[ bucket ] & VT_HASH_FRAG_MASK ) == hashfrag && + VT_LIKELY( CMPR_FN( table->buckets[ bucket ].key, key ) ) + ) + { + VT_CAT( NAME, _itr ) itr = { + table->buckets + bucket, + table->metadata + bucket, + table->metadata + table->buckets_mask + 1, + home_bucket + }; + return itr; + } + + uint16_t displacement = table->metadata[ bucket ] & VT_DISPLACEMENT_MASK; + if( displacement == VT_DISPLACEMENT_MASK ) + return VT_CAT( NAME, _end_itr )(); + + bucket = ( home_bucket + vt_quadratic( displacement ) ) & table->buckets_mask; + } +} + +// Erases the key pointed to by the specified iterator. +// The erasure always occurs at the end of the chain to which the key belongs. +// If the key to be erased is not the last in the chain, it is swapped with the last so that erasure occurs at the end. +// This helps keep a chain's keys close to their home bucket for the sake of cache locality. +// Returns true if, in the case of iteration from first to end, NAME_next should now be called on the iterator to find +// the next key. +// This return value is necessary because at the iterator location, the erasure could result in an empty bucket, a +// bucket containing a moved key already visited during the iteration, or a bucket containing a moved key not yet +// visited. +VT_API_FN_QUALIFIERS bool VT_CAT( NAME, _erase_itr_raw )( NAME *table, VT_CAT( NAME, _itr ) itr ) +{ + --table->key_count; + size_t itr_bucket = itr.metadatum - table->metadata; + + // For now, we only call the value's destructor because the key may need to be hashed below to determine the home + // bucket. + #ifdef VAL_DTOR_FN + VAL_DTOR_FN( table->buckets[ itr_bucket ].val ); + #endif + + // Case 1: The key is the only one in its chain, so just remove it. + if( + table->metadata[ itr_bucket ] & VT_IN_HOME_BUCKET_MASK && + ( table->metadata[ itr_bucket ] & VT_DISPLACEMENT_MASK ) == VT_DISPLACEMENT_MASK + ) + { + #ifdef KEY_DTOR_FN + KEY_DTOR_FN( table->buckets[ itr_bucket ].key ); + #endif + table->metadata[ itr_bucket ] = VT_EMPTY; + return true; + } + + // Case 2 and 3 require that we know the key's home bucket, which the iterator may not have recorded. + if( itr.home_bucket == SIZE_MAX ) + { + if( table->metadata[ itr_bucket ] & VT_IN_HOME_BUCKET_MASK ) + itr.home_bucket = itr_bucket; + else + itr.home_bucket = HASH_FN( table->buckets[ itr_bucket ].key ) & table->buckets_mask; + } + + // The key can now be safely destructed for cases 2 and 3. + #ifdef KEY_DTOR_FN + KEY_DTOR_FN( table->buckets[ itr_bucket ].key ); + #endif + + // Case 2: The key is the last in a multi-key chain. + // Traverse the chain from the beginning and find the penultimate key. + // Then disconnect the key and erase. + if( ( table->metadata[ itr_bucket ] & VT_DISPLACEMENT_MASK ) == VT_DISPLACEMENT_MASK ) + { + size_t bucket = itr.home_bucket; + while( true ) + { + uint16_t displacement = table->metadata[ bucket ] & VT_DISPLACEMENT_MASK; + size_t next = ( itr.home_bucket + vt_quadratic( displacement ) ) & table->buckets_mask; + if( next == itr_bucket ) + { + table->metadata[ bucket ] |= VT_DISPLACEMENT_MASK; + table->metadata[ itr_bucket ] = VT_EMPTY; + return true; + } + + bucket = next; + } + } + + // Case 3: The chain has multiple keys, and the key is not the last one. + // Traverse the chain from the key to be erased and find the last and penultimate keys. + // Disconnect the last key from the chain, and swap it with the key to erase. + size_t bucket = itr_bucket; + while( true ) + { + size_t prev = bucket; + bucket = ( itr.home_bucket + vt_quadratic( table->metadata[ bucket ] & VT_DISPLACEMENT_MASK ) ) & + table->buckets_mask; + + if( ( table->metadata[ bucket ] & VT_DISPLACEMENT_MASK ) == VT_DISPLACEMENT_MASK ) + { + table->buckets[ itr_bucket ] = table->buckets[ bucket ]; + + table->metadata[ itr_bucket ] = ( table->metadata[ itr_bucket ] & ~VT_HASH_FRAG_MASK ) | ( + table->metadata[ bucket ] & VT_HASH_FRAG_MASK ); + + table->metadata[ prev ] |= VT_DISPLACEMENT_MASK; + table->metadata[ bucket ] = VT_EMPTY; + + // Whether the iterator should be advanced depends on whether the key moved to the iterator bucket came from + // before or after that bucket. + // In the former case, the iteration would already have hit the moved key, so the iterator should still be + // advanced. + if( bucket > itr_bucket ) + return false; + + return true; + } + } +} + +// Erases the specified key, if it exists. +// Returns true if a key was erased. +VT_API_FN_QUALIFIERS bool VT_CAT( NAME, _erase )( NAME *table, KEY_TY key ) +{ + VT_CAT( NAME, _itr ) itr = VT_CAT( NAME, _get)( table, key ); + if( VT_CAT( NAME, _is_end )( itr ) ) + return false; + + VT_CAT( NAME, _erase_itr_raw )( table, itr ); + return true; +} + +// Finds the first occupied bucket at or after the bucket pointed to by itr. +// This function scans four buckets at a time, ideally using intrinsics. +static inline FORCE_INLINE void VT_CAT( NAME, _fast_forward )( VT_CAT( NAME, _itr ) *itr ) +{ + while( true ) + { + uint64_t metadata; + memcpy( &metadata, itr->metadatum, sizeof( uint64_t ) ); + if( metadata ) + { + int offset = vt_first_nonzero_uint16( metadata ); + itr->data += offset; + itr->metadatum += offset; + itr->home_bucket = SIZE_MAX; + return; + } + + itr->data += 4; + itr->metadatum += 4; + } +} + +VT_API_FN_QUALIFIERS VT_CAT( NAME, _itr ) VT_CAT( NAME, _next )( VT_CAT( NAME, _itr ) itr ) +{ + ++itr.data; + ++itr.metadatum; + VT_CAT( NAME, _fast_forward )( &itr ); + return itr; +} + +// Returns the minimum bucket count required to accommodate a certain number of keys, which is governed by the maximum +// load factor. +static inline FORCE_INLINE size_t VT_CAT( NAME, _min_bucket_count_for_size )( size_t size ) +{ + if( size == 0 ) + return 0; + + // Round up to a power of two. + size_t bucket_count = VT_MIN_NONZERO_BUCKET_COUNT; + while( size > bucket_count * MAX_LOAD ) + bucket_count *= 2; + + return bucket_count; +} + +VT_API_FN_QUALIFIERS bool VT_CAT( NAME, _reserve )( NAME *table, size_t size ) +{ + size_t bucket_count = VT_CAT( NAME, _min_bucket_count_for_size )( size ); + + if( bucket_count <= VT_CAT( NAME, _bucket_count )( table ) ) + return true; + + return VT_CAT( NAME, _rehash )( table, bucket_count ); +} + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wcast-qual" + +VT_API_FN_QUALIFIERS bool VT_CAT( NAME, _shrink )( NAME *table ) +{ + size_t bucket_count = VT_CAT( NAME, _min_bucket_count_for_size )( table->key_count ); + + if( bucket_count == VT_CAT( NAME, _bucket_count )( table ) ) // Shrink unnecessary. + return true; + + if( bucket_count == 0 ) + { + FREE_FN( + table->buckets, + VT_CAT( NAME, _total_alloc_size )( table ) + #ifdef CTX_TY + , &table->ctx + #endif + ); + + table->buckets_mask = 0x0000000000000000ull; + table->metadata = (uint16_t *)&vt_empty_placeholder_metadatum; + return true; + } + + return VT_CAT( NAME, _rehash )( table, bucket_count ); +} + +#pragma GCC diagnostic pop + +VT_API_FN_QUALIFIERS VT_CAT( NAME, _itr ) VT_CAT( NAME, _first )( const NAME *table ) +{ + if( !table->key_count ) + return VT_CAT( NAME, _end_itr )(); + + VT_CAT( NAME, _itr ) itr = { table->buckets, table->metadata, table->metadata + table->buckets_mask + 1, SIZE_MAX }; + VT_CAT( NAME, _fast_forward )( &itr ); + return itr; +} + +VT_API_FN_QUALIFIERS void VT_CAT( NAME, _clear )( NAME *table ) +{ + if( !table->key_count ) + return; + + for( size_t i = 0; i < VT_CAT( NAME, _bucket_count )( table ); ++i ) + { + if( table->metadata[ i ] != VT_EMPTY ) + { + #ifdef KEY_DTOR_FN + KEY_DTOR_FN( table->buckets[ i ].key ); + #endif + #ifdef VAL_DTOR_FN + VAL_DTOR_FN( table->buckets[ i ].val ); + #endif + } + + table->metadata[ i ] = VT_EMPTY; + } + + table->key_count = 0; +} + +VT_API_FN_QUALIFIERS void VT_CAT( NAME, _cleanup )( NAME *table ) +{ + if( !table->buckets_mask ) + return; + + #if defined( KEY_DTOR_FN ) || defined( VAL_DTOR_FN ) + VT_CAT( NAME, _clear )( table ); + #endif + + FREE_FN( + table->buckets, + VT_CAT( NAME, _total_alloc_size )( table ) + #ifdef CTX_TY + , &table->ctx + #endif + ); + + VT_CAT( NAME, _init )( + table + #ifdef CTX_TY + , table->ctx + #endif + ); +} + +#endif + +/*--------------------------------------------------------------------------------------------------------------------*/ +/* Wrapper types and functions for the C11 generic API */ +/*--------------------------------------------------------------------------------------------------------------------*/ + +#if defined(__STDC_VERSION__) && \ + __STDC_VERSION__ >= 201112L && \ + !defined( IMPLEMENTATION_MODE ) && \ + !defined( VT_NO_C11_GENERIC_API ) \ + +typedef NAME VT_CAT( vt_table_, VT_TEMPLATE_COUNT ); +typedef VT_CAT( NAME, _itr ) VT_CAT( vt_table_itr_, VT_TEMPLATE_COUNT ); + +static inline FORCE_INLINE void VT_CAT( vt_init_, VT_TEMPLATE_COUNT )( + NAME *table + #ifdef CTX_TY + , CTX_TY ctx + #endif +) +{ + VT_CAT( NAME, _init )( + table + #ifdef CTX_TY + , ctx + #endif + ); +} + +static inline FORCE_INLINE bool VT_CAT( vt_init_clone_, VT_TEMPLATE_COUNT )( + NAME *table, + const NAME* source + #ifdef CTX_TY + , CTX_TY ctx + #endif +) +{ + return VT_CAT( NAME, _init_clone )( + table, + source + #ifdef CTX_TY + , ctx + #endif + ); +} + +static inline FORCE_INLINE size_t VT_CAT( vt_size_, VT_TEMPLATE_COUNT )( const NAME *table ) +{ + return VT_CAT( NAME, _size )( table ); +} + +static inline FORCE_INLINE size_t VT_CAT( vt_bucket_count_, VT_TEMPLATE_COUNT )( const NAME *table ) +{ + return VT_CAT( NAME, _bucket_count )( table ); +} + +static inline FORCE_INLINE bool VT_CAT( vt_is_end_, VT_TEMPLATE_COUNT )( VT_CAT( NAME, _itr ) itr ) +{ + return VT_CAT( NAME, _is_end )( itr ); +} + +static inline FORCE_INLINE VT_CAT( NAME, _itr ) VT_CAT( vt_insert_, VT_TEMPLATE_COUNT )( + NAME *table, + KEY_TY key + #ifdef VAL_TY + , VAL_TY val + #endif +) +{ + return VT_CAT( NAME, _insert )( + table, + key + #ifdef VAL_TY + , val + #endif + ); +} + +static inline FORCE_INLINE VT_CAT( NAME, _itr ) VT_CAT( vt_get_or_insert_, VT_TEMPLATE_COUNT )( + NAME *table, + KEY_TY key + #ifdef VAL_TY + , VAL_TY val + #endif +) +{ + return VT_CAT( NAME, _get_or_insert )( + table, + key + #ifdef VAL_TY + , val + #endif + ); +} + +static inline FORCE_INLINE VT_CAT( NAME, _itr ) VT_CAT( vt_get_, VT_TEMPLATE_COUNT )( const NAME *table, KEY_TY key ) +{ + return VT_CAT( NAME, _get )( table, key ); +} + +static inline FORCE_INLINE bool VT_CAT( vt_erase_, VT_TEMPLATE_COUNT )( NAME *table, KEY_TY key ) +{ + return VT_CAT( NAME, _erase )( table, key ); +} + +static inline FORCE_INLINE VT_CAT( NAME, _itr ) VT_CAT( vt_next_, VT_TEMPLATE_COUNT )( VT_CAT( NAME, _itr ) itr ) +{ + return VT_CAT( NAME, _next )( itr ); +} + +static inline FORCE_INLINE VT_CAT( NAME, _itr ) VT_CAT( vt_erase_itr_, VT_TEMPLATE_COUNT )( NAME *table, VT_CAT( NAME, _itr ) itr ) +{ + return VT_CAT( NAME, _erase_itr )( table, itr ); +} + +static inline FORCE_INLINE bool VT_CAT( vt_reserve_, VT_TEMPLATE_COUNT )( NAME *table, size_t bucket_count ) +{ + return VT_CAT( NAME, _reserve )( table, bucket_count ); +} + +static inline FORCE_INLINE bool VT_CAT( vt_shrink_, VT_TEMPLATE_COUNT )( NAME *table ) +{ + return VT_CAT( NAME, _shrink )( table ); +} + +static inline FORCE_INLINE VT_CAT( NAME, _itr ) VT_CAT( vt_first_, VT_TEMPLATE_COUNT )( const NAME *table ) +{ + return VT_CAT( NAME, _first )( table ); +} + +static inline FORCE_INLINE void VT_CAT( vt_clear_, VT_TEMPLATE_COUNT )( NAME *table ) +{ + VT_CAT( NAME, _clear )( table ); +} + +static inline FORCE_INLINE void VT_CAT( vt_cleanup_, VT_TEMPLATE_COUNT )( NAME *table ) +{ + VT_CAT( NAME, _cleanup )( table ); +} + +// Increment the template counter. +#if VT_TEMPLATE_COUNT_D1 == 0 +#undef VT_TEMPLATE_COUNT_D1 +#define VT_TEMPLATE_COUNT_D1 1 +#elif VT_TEMPLATE_COUNT_D1 == 1 +#undef VT_TEMPLATE_COUNT_D1 +#define VT_TEMPLATE_COUNT_D1 2 +#elif VT_TEMPLATE_COUNT_D1 == 2 +#undef VT_TEMPLATE_COUNT_D1 +#define VT_TEMPLATE_COUNT_D1 3 +#elif VT_TEMPLATE_COUNT_D1 == 3 +#undef VT_TEMPLATE_COUNT_D1 +#define VT_TEMPLATE_COUNT_D1 4 +#elif VT_TEMPLATE_COUNT_D1 == 4 +#undef VT_TEMPLATE_COUNT_D1 +#define VT_TEMPLATE_COUNT_D1 5 +#elif VT_TEMPLATE_COUNT_D1 == 5 +#undef VT_TEMPLATE_COUNT_D1 +#define VT_TEMPLATE_COUNT_D1 6 +#elif VT_TEMPLATE_COUNT_D1 == 6 +#undef VT_TEMPLATE_COUNT_D1 +#define VT_TEMPLATE_COUNT_D1 7 +#elif VT_TEMPLATE_COUNT_D1 == 7 +#undef VT_TEMPLATE_COUNT_D1 +#define VT_TEMPLATE_COUNT_D1 0 +#if VT_TEMPLATE_COUNT_D2 == 0 +#undef VT_TEMPLATE_COUNT_D2 +#define VT_TEMPLATE_COUNT_D2 1 +#elif VT_TEMPLATE_COUNT_D2 == 1 +#undef VT_TEMPLATE_COUNT_D2 +#define VT_TEMPLATE_COUNT_D2 2 +#elif VT_TEMPLATE_COUNT_D2 == 2 +#undef VT_TEMPLATE_COUNT_D2 +#define VT_TEMPLATE_COUNT_D2 3 +#elif VT_TEMPLATE_COUNT_D2 == 3 +#undef VT_TEMPLATE_COUNT_D2 +#define VT_TEMPLATE_COUNT_D2 4 +#elif VT_TEMPLATE_COUNT_D2 == 4 +#undef VT_TEMPLATE_COUNT_D2 +#define VT_TEMPLATE_COUNT_D2 5 +#elif VT_TEMPLATE_COUNT_D2 == 5 +#undef VT_TEMPLATE_COUNT_D2 +#define VT_TEMPLATE_COUNT_D2 6 +#elif VT_TEMPLATE_COUNT_D2 == 6 +#undef VT_TEMPLATE_COUNT_D2 +#define VT_TEMPLATE_COUNT_D2 7 +#elif VT_TEMPLATE_COUNT_D2 == 7 +#undef VT_TEMPLATE_COUNT_D2 +#define VT_TEMPLATE_COUNT_D2 0 +#if VT_TEMPLATE_COUNT_D3 == 0 +#undef VT_TEMPLATE_COUNT_D3 +#define VT_TEMPLATE_COUNT_D3 1 +#elif VT_TEMPLATE_COUNT_D3 == 1 +#undef VT_TEMPLATE_COUNT_D3 +#define VT_TEMPLATE_COUNT_D3 2 +#elif VT_TEMPLATE_COUNT_D3 == 2 +#undef VT_TEMPLATE_COUNT_D3 +#define VT_TEMPLATE_COUNT_D3 3 +#elif VT_TEMPLATE_COUNT_D3 == 3 +#undef VT_TEMPLATE_COUNT_D3 +#define VT_TEMPLATE_COUNT_D3 4 +#elif VT_TEMPLATE_COUNT_D3 == 4 +#undef VT_TEMPLATE_COUNT_D3 +#define VT_TEMPLATE_COUNT_D3 5 +#elif VT_TEMPLATE_COUNT_D3 == 5 +#undef VT_TEMPLATE_COUNT_D3 +#define VT_TEMPLATE_COUNT_D3 6 +#elif VT_TEMPLATE_COUNT_D3 == 6 +#undef VT_TEMPLATE_COUNT_D3 +#define VT_TEMPLATE_COUNT_D3 7 +#elif VT_TEMPLATE_COUNT_D3 == 7 +#error Sorry, the number of template instances is limited to 511. Define VT_NO_C11_GENERIC_API globally and use the \ +C99 prefixed function API to circumvent this restriction. +#endif +#endif +#endif + +#pragma GCC diagnostic pop + +#endif + +#undef NAME +#undef KEY_TY +#undef KEY_GET_CONST +#undef VAL_TY +#undef HASH_FN +#undef CMPR_FN +#undef MAX_LOAD +#undef KEY_DTOR_FN +#undef VAL_DTOR_FN +#undef CTX_TY +#undef MALLOC_FN +#undef FREE_FN +#undef HEADER_MODE +#undef IMPLEMENTATION_MODE +#undef VT_API_FN_QUALIFIERS + +#undef FORCE_INLINE diff --git a/src/w_wad.c b/src/w_wad.c index 8cc3fd214..5402e90a0 100644 --- a/src/w_wad.c +++ b/src/w_wad.c @@ -87,18 +87,32 @@ typedef struct } lumpchecklist_t; // Must be a power of two -#define LUMPNUMCACHESIZE 64 +#define LUMPNUMCACHESIZE 2048 // should be reasonable ig #define LUMPNUMCACHENAME 32 -typedef struct lumpnum_cache_s -{ - char lumpname[LUMPNUMCACHENAME]; - lumpnum_t lumpnum; - UINT32 hash; -} lumpnum_cache_t; +#define NAME lumpnum_map +#define KEY_TY char * +#define KEY_GET_CONST +#define VAL_TY lumpnum_t +#define HASH_FN vt_hash_string +#define CMPR_FN vt_cmpr_string +#define KEY_DTOR_FN free +#include "verstable.h" -static lumpnum_cache_t lumpnumcache[LUMPNUMCACHESIZE]; -static UINT16 lumpnumcacheindex = 0; +#define NAME folder_map +#define KEY_TY char * +#define KEY_GET_CONST +#define VAL_TY UINT16 +#define HASH_FN vt_hash_string +#define CMPR_FN vt_cmpr_string +#define KEY_DTOR_FN free +#include "verstable.h" + +static lumpnum_map lumpnumcache; + +typedef struct FolderCache { + folder_map map; +} FolderCache; static const char pat_central[] = {0x50, 0x4b, 0x01, 0x02, 0x00}; static const char pat_end[] = {0x50, 0x4b, 0x05, 0x06, 0x00}; @@ -116,6 +130,8 @@ wadfile_t *wadfiles[MAX_WADFILES] = {}; // 0 to numwadfiles-1 are valid // being ejected void W_Shutdown(void) { + lumpnum_map_cleanup(&lumpnumcache); + while (numwadfiles--) { wadfile_t *wad = wadfiles[numwadfiles]; @@ -127,18 +143,28 @@ void W_Shutdown(void) while (wad->numlumps--) { - Z_Free(wad->lumpinfo[wad->numlumps].longname); + //Z_Free(wad->lumpinfo[wad->numlumps].longname); Z_Free(wad->lumpinfo[wad->numlumps].fullname); } - M_AATreeFree(wad->startfolders); - M_AATreeFree(wad->endfolders); + folder_map_cleanup(&wad->startfolders->map); + free(wad->startfolders); + folder_map_cleanup(&wad->endfolders->map); + free(wad->endfolders); Z_Free(wad->lumpinfo); Z_Free(wad); } } +// the lumpnum cache needs to be initialized before use +// call this as early as possible! +void W_Startup(void) +{ + lumpnum_map_init(&lumpnumcache); + lumpnum_map_reserve(&lumpnumcache, LUMPNUMCACHESIZE); +} + //=========================================================================== // LUMP BASED ROUTINES //=========================================================================== @@ -259,7 +285,7 @@ static inline void W_LoadDehackedLumpsPK3(UINT16 wadnum) lumpinfo_t *lump_p = &wadfiles[wadnum]->lumpinfo[posStart]; size_t length = len + 1 + strlen(lump_p->fullname); // length of file name, '|', and lump name - char *name = malloc(length + 1); + char *name = (char *)(malloc(length + 1)); if (!name) I_Error("W_LoadDehackedLumpsPK3: Out of memory!\n"); @@ -295,7 +321,7 @@ static inline void W_LoadDehackedLumps(UINT16 wadnum) if (memcmp(lump_p->name, "SOC_", 4) == 0) // Check for generic SOC lump { // shameless copy+paste of code from LUA_LoadLump size_t length = len + 1 + strlen(lump_p->fullname); // length of file name, '|', and lump name - char *name = malloc(length + 1); + char *name = (char *)(malloc(length + 1)); if (!name) I_Error("W_LoadDehackedLumps: Out of memory!\n"); @@ -360,12 +386,12 @@ static inline INT32 W_MakeFileMD5(const char *filename, void *resblock) } // Invalidates the cache of lump numbers. Call this whenever a wad is added. -static void W_InvalidateLumpnumCache(void) +FUNCINLINE static ATTRINLINE void W_InvalidateLumpnumCache(void) { - memset(lumpnumcache, 0, sizeof(lumpnumcache)); + lumpnum_map_clear(&lumpnumcache); } -UINT32 W_HashLumpName(const char *name) +FUNCINLINE static ATTRINLINE UINT32 W_HashLumpName(const char *name) { return FNV1a_HashLowercaseString(name); } @@ -391,28 +417,28 @@ static restype_t ResourceFileDetect(const char* filename) */ static lumpinfo_t* ResGetLumpsStandalone(FILE* handle, UINT16* numlumps, const char* lumpname) { - lumpinfo_t* lumpinfo = Z_Calloc(sizeof (*lumpinfo), PU_STATIC, NULL); + lumpinfo_t* lumpinfo = (lumpinfo_t *)(Z_Calloc(sizeof (*lumpinfo), PU_STATIC, NULL)); lumpinfo->position = 0; fseek(handle, 0, SEEK_END); - lumpinfo->size = ftell(handle); + lumpinfo->size = (size_t)(ftell(handle)); fseek(handle, 0, SEEK_SET); strlcpy(lumpinfo->name, lumpname, sizeof(lumpinfo->name)); lumpinfo->namelength = strlen(lumpinfo->name); lumpinfo->hash.name = W_HashLumpName(lumpname); // Allocate the lump's long name. - lumpinfo->longname = Z_Malloc((lumpinfo->namelength + 1) * sizeof(char), PU_STATIC, NULL); + /*lumpinfo->longname = (char *)(Z_Malloc((lumpinfo->namelength + 1) * sizeof(char), PU_STATIC, NULL)); strcpy(lumpinfo->longname, lumpname); lumpinfo->longname[lumpinfo->namelength] = '\0'; lumpinfo->longnamelength = lumpinfo->namelength; - lumpinfo->hash.longname = lumpinfo->hash.name; + lumpinfo->hash.longname = lumpinfo->hash.name;*/ // Allocate the lump's full name. - lumpinfo->fullname = Z_Malloc((lumpinfo->namelength + 1) * sizeof(char), PU_STATIC, NULL); + lumpinfo->fullname = (char *)(Z_Malloc((lumpinfo->namelength + 1) * sizeof(char), PU_STATIC, NULL)); strcpy(lumpinfo->fullname, lumpname); lumpinfo->fullname[lumpinfo->namelength] = '\0'; lumpinfo->fullnamelength = lumpinfo->namelength; - lumpinfo->hash.fullname = lumpinfo->hash.name; + //lumpinfo->hash.fullname = lumpinfo->hash.name; *numlumps = 1; return lumpinfo; @@ -456,7 +482,7 @@ static lumpinfo_t* ResGetLumpsWad(FILE* handle, UINT16* nlmp, const char* filena // read wad file directory i = header.numlumps * sizeof(*fileinfo); - fileinfov = fileinfo = malloc(i); + fileinfov = fileinfo = (filelump_t *)(malloc(i)); if (fseek(handle, header.infotableofs, SEEK_SET) == -1 || fread(fileinfo, 1, i, handle) < i) @@ -468,7 +494,7 @@ static lumpinfo_t* ResGetLumpsWad(FILE* handle, UINT16* nlmp, const char* filena numlumps = header.numlumps; // fill in lumpinfo for this wad - lump_p = lumpinfo = Z_Malloc(numlumps * sizeof (*lumpinfo), PU_STATIC, NULL); + lump_p = lumpinfo = (lumpinfo_t *)(Z_Malloc(numlumps * sizeof (*lumpinfo), PU_STATIC, NULL)); for (i = 0; i < numlumps; i++, lump_p++, fileinfo++) { @@ -512,18 +538,18 @@ static lumpinfo_t* ResGetLumpsWad(FILE* handle, UINT16* nlmp, const char* filena lump_p->hash.name = W_HashLumpName(lump_p->name); // Allocate the lump's long name. - lump_p->longname = Z_Malloc(9 * sizeof(char), PU_STATIC, NULL); + /*lump_p->longname = (char *)(Z_Malloc(9 * sizeof(char), PU_STATIC, NULL)); strncpy(lump_p->longname, fileinfo->name, 8); lump_p->longname[8] = '\0'; lump_p->longnamelength = lump_p->namelength; - lump_p->hash.longname = lump_p->hash.name; + lump_p->hash.longname = lump_p->hash.name;*/ // Allocate the lump's full name. - lump_p->fullname = Z_Malloc(9 * sizeof(char), PU_STATIC, NULL); + lump_p->fullname = (char *)(Z_Malloc(9 * sizeof(char), PU_STATIC, NULL)); strncpy(lump_p->fullname, fileinfo->name, 8); lump_p->fullname[8] = '\0'; lump_p->fullnamelength = lump_p->namelength; - lump_p->hash.fullname = lump_p->hash.name; + //lump_p->hash.fullname = lump_p->hash.name; } *nlmp = numlumps; @@ -540,24 +566,24 @@ static boolean ResFindSignature(FILE* handle, const char endPat[], UINT32 startp int c; fseek(handle, 0, SEEK_END); - size_t len = ftell(handle); + size_t len = (size_t)(ftell(handle)); fseek(handle, startpos, SEEK_SET); - size_t remaining = len - startpos; - size_t chunkpos = startpos; + size_t remaining = (size_t)(len - startpos); + size_t chunkpos = (size_t)(startpos); s = endPat; //128k buffers size_t buffer_size = min(128 * 1024 * sizeof(char), remaining); - CLEANUP(pfree) char* buffer = (char*)(malloc(buffer_size)); + CLEANUP(pfree) char* buffer = (char *)(malloc(buffer_size)); size_t bytes_read = 0; while ((bytes_read = fread(buffer, 1, buffer_size, handle)) > 0) { for (size_t i = 0; i < bytes_read; i++) { - c = (int)buffer[i]; + c = (int)(buffer[i]); if (*s != c && s > endPat) // No match? s = endPat; // We "reset" the counter by sending the s pointer back to the start of the array. @@ -569,7 +595,7 @@ static boolean ResFindSignature(FILE* handle, const char endPat[], UINT32 startp if (*s == 0x00) // The array pointer has reached the key char which marks the end. It means we have matched the signature. { //the original function would leave the FILE* seeked to the end of the match - size_t foundpos = chunkpos + i + 1; + size_t foundpos = (size_t)(chunkpos + i + 1); fseek(handle, foundpos, SEEK_SET); return true; } @@ -646,7 +672,7 @@ static lumpinfo_t* ResGetLumpsZip(FILE* handle, UINT16* nlmp) // Contains entry number (number of lumps), and central directory start offset. fseek(handle, 0, SEEK_END); - const long startpos = (ftell(handle) - (22 + 65536)); + const long startpos = ftell(handle) - (22 + 65536); if (!ResFindSignature(handle, pat_end, max(0l, startpos))) { CONS_Alert(CONS_ERROR, "Missing central directory\n"); @@ -662,11 +688,11 @@ static lumpinfo_t* ResGetLumpsZip(FILE* handle, UINT16* nlmp) numlumps = SHORT(zend.entries); - lump_p = lumpinfo = Z_Malloc(numlumps * sizeof (*lumpinfo), PU_STATIC, NULL); + lump_p = lumpinfo = (lumpinfo_t *)(Z_Malloc(numlumps * sizeof (*lumpinfo), PU_STATIC, NULL)); fseek(handle, LONG(zend.cdiroffset), SEEK_SET); - CLEANUP(Z_Pfree) char *cdir = Z_Malloc(LONG(zend.cdirsize), PU_STATIC, &cdir); + CLEANUP(Z_Pfree) char *cdir = (char *)(Z_Malloc(LONG(zend.cdirsize), PU_STATIC, &cdir)); if (fread(cdir, 1, LONG(zend.cdirsize), handle) < (UINT32)(LONG(zend.cdirsize))) { @@ -679,7 +705,7 @@ static lumpinfo_t* ResGetLumpsZip(FILE* handle, UINT16* nlmp) for (i = 0; i < numlumps; i++, lump_p++) { - zentry_t *zentry = (zentry_t*)(cdir + offset); + zentry_t *zentry = (zentry_t *)(cdir + offset); char* fullname; char* trimname; char* dotpos; @@ -695,7 +721,7 @@ static lumpinfo_t* ResGetLumpsZip(FILE* handle, UINT16* nlmp) lump_p->disksize = LONG(zentry->compsize); lump_p->size = LONG(zentry->size); - fullname = (char*)(malloc(SHORT(zentry->namelen) + 1)); + fullname = (char *)(malloc(SHORT(zentry->namelen) + 1)); strncpy(fullname, (char *)(zentry + 1), SHORT(zentry->namelen)); fullname[zentry->namelen] = 0; @@ -713,15 +739,15 @@ static lumpinfo_t* ResGetLumpsZip(FILE* handle, UINT16* nlmp) lump_p->namelength = strlen(lump_p->name); lump_p->hash.name = W_HashLumpName(lump_p->name); - lump_p->longname = Z_Calloc(dotpos - trimname + 1, PU_STATIC, NULL); + /*lump_p->longname = (char *)(Z_Calloc(dotpos - trimname + 1, PU_STATIC, NULL)); strlcpy(lump_p->longname, trimname, dotpos - trimname + 1); lump_p->longnamelength = strlen(lump_p->longname); - lump_p->hash.longname = W_HashLumpName(lump_p->longname); + lump_p->hash.longname = W_HashLumpName(lump_p->longname);*/ - lump_p->fullname = (char*)(Z_Calloc(SHORT(zentry->namelen) + 1, PU_STATIC, NULL)); + lump_p->fullname = (char *)(Z_Calloc(SHORT(zentry->namelen) + 1, PU_STATIC, NULL)); strncpy(lump_p->fullname, fullname, SHORT(zentry->namelen)); lump_p->fullnamelength = zentry->namelen; - lump_p->hash.fullname = W_HashLumpName(lump_p->fullname); + //lump_p->hash.fullname = W_HashLumpName(lump_p->fullname); switch(SHORT(zentry->compression)) { @@ -896,17 +922,19 @@ UINT16 W_InitFile(const char *filename, boolean local, boolean startup) // // link wad file to search files // - wadfile = Z_Calloc(sizeof (*wadfile), PU_STATIC, NULL); + wadfile = (wadfile_t *)(Z_Calloc(sizeof (*wadfile), PU_STATIC, NULL)); wadfile->filename = Z_StrDup(filename); wadfile->handle = handle; - wadfile->numlumps = (UINT16)numlumps; + wadfile->numlumps = (UINT16)(numlumps); wadfile->lumpinfo = lumpinfo; wadfile->important = important; fseek(handle, 0, SEEK_END); - wadfile->filesize = (unsigned)ftell(handle); + wadfile->filesize = (unsigned)(ftell(handle)); wadfile->type = type; - wadfile->startfolders = M_AATreeAlloc(AATREE_STRING); - wadfile->endfolders = M_AATreeAlloc(AATREE_STRING); + wadfile->startfolders = (FolderCache *)malloc(sizeof(FolderCache)); + folder_map_init(&wadfile->startfolders->map); + wadfile->endfolders = (FolderCache *)malloc(sizeof(FolderCache)); + folder_map_init(&wadfile->endfolders->map); // already generated, just copy it over memcpy(&wadfile->md5sum, &md5sum, 16); @@ -931,7 +959,7 @@ UINT16 W_InitFile(const char *filename, boolean local, boolean startup) #ifdef HWRENDER // Read shaders from file if (rendermode == render_opengl && (vid.glstate == VID_GL_LIBRARY_LOADED)) - HWR_LoadCustomShadersFromFile(numwadfiles - 1, (type == RET_PK3)); + HWR_LoadCustomShadersFromFile(numwadfiles - 1, (boolean)(type == RET_PK3)); #endif // TODO: HACK ALERT - Load Lua & SOC stuff right here. I feel like this should be out of this place, but... Let's stick with this for now. @@ -1031,7 +1059,7 @@ INT32 W_AddAutoloadedLocalFiles(char **filenames) /** Make sure a lump number is valid. * Compiles away to nothing if PARANOIA is not defined. */ -static boolean TestValidLump(UINT16 wad, UINT16 lump) +static inline boolean TestValidLump(UINT16 wad, UINT16 lump) { I_Assert(wad < MAX_WADFILES); if (!wadfiles[wad]) // make sure the wad file exists @@ -1054,7 +1082,7 @@ const char *W_CheckNameForNumPwad(UINT16 wad, UINT16 lump) const char *W_CheckNameForNum(lumpnum_t lumpnum) { - return W_CheckNameForNumPwad(WADFILENUM(lumpnum),LUMPNUM(lumpnum)); + return W_CheckNameForNumPwad(WADFILENUM(lumpnum), LUMPNUM(lumpnum)); } // @@ -1108,7 +1136,7 @@ UINT16 W_CheckNumForNamePwad(const char *name, UINT16 wad, UINT16 startlump) // Should be the only version, but that's not possible until we fix // all the instances of non null-terminated strings in the codebase... // -UINT16 W_CheckNumForLongNamePwad(const char *name, UINT16 wad, UINT16 startlump) +/*UINT16 W_CheckNumForLongNamePwad(const char *name, UINT16 wad, UINT16 startlump) { UINT16 i; UINT32 hash; @@ -1140,43 +1168,46 @@ UINT16 W_CheckNumForLongNamePwad(const char *name, UINT16 wad, UINT16 startlump) // not found. return INT16_MAX; -} +}*/ UINT16 W_CheckNumForMarkerStartPwad(const char *name, UINT16 wad, UINT16 startlump) { UINT16 marker; + marker = W_CheckNumForNamePwad(name, wad, startlump); if (marker != INT16_MAX) marker++; // Do not count the first marker + return marker; } // Look for the first lump from a folder. UINT16 W_CheckNumForFolderStartPK3(const char *name, UINT16 wad, UINT16 startlump) { - size_t name_length; - INT32 i; + UINT16 i; + size_t namelen; lumpinfo_t *lump_p = wadfiles[wad]->lumpinfo + startlump; - name_length = strlen(name); - void *val = M_AATreeGetString(wadfiles[wad]->startfolders, name); - if (val != NULL) - return (uintptr_t)val; + folder_map_itr it = folder_map_get(&wadfiles[wad]->startfolders->map, name); + if (!folder_map_is_end(it)) + return it.data->val; + + namelen = strlen(name); for (i = startlump; i < wadfiles[wad]->numlumps; i++, lump_p++) { - if (strnicmp(name, lump_p->fullname, name_length) == 0) + if (strnicmp(name, lump_p->fullname, namelen) == 0) { /* SLADE is special and puts a single directory entry. Skip that. */ - if (strlen(lump_p->fullname) == name_length) + if (lump_p->fullnamelength == namelen) i++; - M_AATreeSetString(wadfiles[wad]->startfolders, name, (void *)(uintptr_t)i); + folder_map_insert(&wadfiles[wad]->startfolders->map, strdup(name), i); return i; } } - M_AATreeSetString(wadfiles[wad]->startfolders, name, (void *)INT16_MAX); + folder_map_insert(&wadfiles[wad]->startfolders->map, strdup(name), INT16_MAX); return INT16_MAX; } @@ -1185,21 +1216,23 @@ UINT16 W_CheckNumForFolderStartPK3(const char *name, UINT16 wad, UINT16 startlum // Returns the position of the lumpinfo entry. UINT16 W_CheckNumForFolderEndPK3(const char *name, UINT16 wad, UINT16 startlump) { - INT32 i; + UINT16 i; + size_t namelen; lumpinfo_t *lump_p = wadfiles[wad]->lumpinfo + startlump; - size_t name_length = strlen(name); - void *val = M_AATreeGetString(wadfiles[wad]->endfolders, name); - if (val != NULL) - return (uintptr_t)val; + folder_map_itr it = folder_map_get(&wadfiles[wad]->endfolders->map, name); + if (!folder_map_is_end(it)) + return it.data->val; + + namelen = strlen(name); for (i = startlump; i < wadfiles[wad]->numlumps; i++, lump_p++) { - if (strnicmp(name, lump_p->fullname, name_length)) + if (strnicmp(name, lump_p->fullname, namelen)) break; } - M_AATreeSetString(wadfiles[wad]->endfolders, name, (void *)(uintptr_t)i); + folder_map_insert(&wadfiles[wad]->endfolders->map, strdup(name), i); return i; } @@ -1207,13 +1240,15 @@ UINT16 W_CheckNumForFolderEndPK3(const char *name, UINT16 wad, UINT16 startlump) // Returns lump position in PK3's lumpinfo, or INT16_MAX if not found. UINT16 W_CheckNumForFullNamePK3(const char *name, UINT16 wad, UINT16 startlump) { - INT32 i; + UINT16 i; + size_t namelen; lumpinfo_t *lump_p = wadfiles[wad]->lumpinfo + startlump; - size_t name_length = strlen(name); + + namelen = strlen(name); for (i = startlump; i < wadfiles[wad]->numlumps; i++, lump_p++) { - if (!strnicmp(name, lump_p->fullname, name_length)) + if (!strnicmp(name, lump_p->fullname, namelen)) { return i; } @@ -1223,61 +1258,19 @@ UINT16 W_CheckNumForFullNamePK3(const char *name, UINT16 wad, UINT16 startlump) return INT16_MAX; } -static lumpnum_t CheckLumpInCache(const char *name, boolean longname) +FUNCINLINE static ATTRINLINE lumpnum_t CheckLumpInCache(const char *name) { - if (longname) - { - UINT32 hash = quickncasehash(name, 32); - - // Loop backwards so that we check most recent entries first - for (INT32 i = lumpnumcacheindex + LUMPNUMCACHESIZE; i > lumpnumcacheindex; i--) - { - if (lumpnumcache[i & (LUMPNUMCACHESIZE - 1)].hash == hash - && fasticmp(lumpnumcache[i & (LUMPNUMCACHESIZE - 1)].lumpname, name)) - { - lumpnumcacheindex = i & (LUMPNUMCACHESIZE - 1); - return lumpnumcache[lumpnumcacheindex].lumpnum; - } - } - } - else - { - UINT32 hash = quickncasehash(name, 8); - - // Loop backwards so that we check most recent entries first - for (INT32 i = lumpnumcacheindex + LUMPNUMCACHESIZE; i > lumpnumcacheindex; i--) - { - if (lumpnumcache[i & (LUMPNUMCACHESIZE - 1)].hash == hash - && lumpnumcache[i & (LUMPNUMCACHESIZE - 1)].lumpname[8] == '\0' - && strnicmp(lumpnumcache[i & (LUMPNUMCACHESIZE - 1)].lumpname, name, 8) == 0) - { - lumpnumcacheindex = i & (LUMPNUMCACHESIZE - 1); - return lumpnumcache[lumpnumcacheindex].lumpnum; - } - } - } + lumpnum_map_itr it = lumpnum_map_get(&lumpnumcache, name); + if (!lumpnum_map_is_end(it)) + return it.data->val; return LUMPERROR; } -static void AddLumpToCache(lumpnum_t lumpnum, const char *name, boolean longname) +FUNCINLINE static ATTRINLINE void AddLumpToCache(lumpnum_t lumpnum, const char *name) { - if (longname && strlen(name) >= LUMPNUMCACHENAME) - return; - - lumpnumcacheindex = (lumpnumcacheindex + 1) & (LUMPNUMCACHESIZE - 1); - memset(lumpnumcache[lumpnumcacheindex].lumpname, '\0', LUMPNUMCACHENAME); - if (longname) - { - strlcpy(lumpnumcache[lumpnumcacheindex].lumpname, name, LUMPNUMCACHENAME); - lumpnumcache[lumpnumcacheindex].hash = quickncasehash(name, LUMPNUMCACHENAME); - } - else - { - strncpy(lumpnumcache[lumpnumcacheindex].lumpname, name, 8); - lumpnumcache[lumpnumcacheindex].hash = quickncasehash(name, 8); - } - lumpnumcache[lumpnumcacheindex].lumpnum = lumpnum; + I_Assert(strlen(name) <= 8); // we dont have longnames, so assume this is a non terminated string + lumpnum_map_insert(&lumpnumcache, strdup(name), lumpnum); } // @@ -1293,14 +1286,14 @@ lumpnum_t W_CheckNumForName(const char *name) return LUMPERROR; // Check the lumpnumcache first. - lumpnum_t cachenum = CheckLumpInCache(name, false); + lumpnum_t cachenum = CheckLumpInCache(name); if (cachenum != LUMPERROR) return cachenum; // scan wad files backwards so patch lump files take precedence for (i = numwadfiles - 1; i >= 0; i--) { - check = W_CheckNumForNamePwad(name, (UINT16)i, 0); + check = W_CheckNumForNamePwad(name, (UINT16)(i), 0); if (check != INT16_MAX) break; //found it @@ -1314,7 +1307,7 @@ lumpnum_t W_CheckNumForName(const char *name) { // Update the cache. lumpnum_t lumpnum = (i << 16) | check; - AddLumpToCache(lumpnum, name, false); + AddLumpToCache(lumpnum, name); return lumpnum; } @@ -1326,7 +1319,7 @@ lumpnum_t W_CheckNumForName(const char *name) // Should be the only version, but that's not possible until we fix // all the instances of non null-terminated strings in the codebase... // -lumpnum_t W_CheckNumForLongName(const char *name) +/*lumpnum_t W_CheckNumForLongName(const char *name) { INT32 i; lumpnum_t check = INT16_MAX; @@ -1335,14 +1328,14 @@ lumpnum_t W_CheckNumForLongName(const char *name) return LUMPERROR; // Check the lumpnumcache first. - lumpnum_t cachenum = CheckLumpInCache(name, true); + lumpnum_t cachenum = CheckLumpInCache(name); if (cachenum != LUMPERROR) return cachenum; // scan wad files backwards so patch lump files take precedence for (i = numwadfiles - 1; i >= 0; i--) { - check = W_CheckNumForLongNamePwad(name, (UINT16)i, 0); + check = W_CheckNumForLongNamePwad(name, (UINT16)(i), 0); if (check != INT16_MAX) break; //found it @@ -1356,11 +1349,11 @@ lumpnum_t W_CheckNumForLongName(const char *name) { // Update the cache. lumpnum_t lumpnum = (i << 16) | check; - AddLumpToCache(lumpnum, name, true); + AddLumpToCache(lumpnum, name); return lumpnum; } -} +}*/ // // W_GetNumForName @@ -1385,7 +1378,7 @@ lumpnum_t W_GetNumForName(const char *name) // Should be the only version, but that's not possible until we fix // all the instances of non null-terminated strings in the codebase... // -lumpnum_t W_GetNumForLongName(const char *name) +/*lumpnum_t W_GetNumForLongName(const char *name) { lumpnum_t i; @@ -1395,7 +1388,7 @@ lumpnum_t W_GetNumForLongName(const char *name) I_Error("W_GetNumForLongName: %s not found!\n", name); return i; -} +}*/ // // W_CheckNumForNameInBlock @@ -1412,32 +1405,49 @@ lumpnum_t W_CheckNumForNameInBlock(const char *name, const char *blockstart, con { if (wadfiles[i]->type == RET_WAD) { - bsid = W_CheckNumForNamePwad(blockstart, (UINT16)i, 0); + bsid = W_CheckNumForNamePwad(blockstart, (UINT16)(i), 0); if (bsid == INT16_MAX) continue; // Start block doesn't exist? - beid = W_CheckNumForNamePwad(blockend, (UINT16)i, 0); + beid = W_CheckNumForNamePwad(blockend, (UINT16)(i), 0); if (beid == INT16_MAX) continue; // End block doesn't exist? - check = W_CheckNumForNamePwad(name, (UINT16)i, bsid); + check = W_CheckNumForNamePwad(name, (UINT16)(i), bsid); if (check < beid) - return (i<<16)+check; // found it, in our constraints + return (i << 16)+check; // found it, in our constraints } } + return LUMPERROR; } // Used by Lua. Case sensitive lump checking, quickly... UINT8 W_LumpExists(const char *name) { - INT32 i,j; + INT32 i, j; + UINT32 hash; + size_t namelen; + + // Check the lumpnumcache first. + lumpnum_t cachenum = CheckLumpInCache(name); + if (cachenum != LUMPERROR) + return true; + + namelen = strlen(name); + hash = W_HashLumpName(name); + for (i = numwadfiles - 1; i >= 0; i--) { lumpinfo_t *lump_p = wadfiles[i]->lumpinfo; for (j = 0; j < wadfiles[i]->numlumps; ++j, ++lump_p) - if (fastcmp(lump_p->name, name)) + { + if (lump_p->namelength == namelen + && lump_p->hash.name == hash + && fastcmp(lump_p->name, name)) return true; + } } + return false; } @@ -1597,13 +1607,13 @@ size_t W_ReadLumpHeaderPwad(UINT16 wad, UINT16 lump, void *dest, size_t size, si fseek(handle, (long)(l->position + offset), SEEK_SET); // But let's not copy it yet. We support different compression formats on lumps, so we need to take that into account. - switch(wadfiles[wad]->lumpinfo[lump].compression) + switch (wadfiles[wad]->lumpinfo[lump].compression) { case CM_NOCOMPRESSION: // If it's uncompressed, we directly write the data into our destination, and return the bytes read. #ifdef NO_PNG_LUMPS { size_t bytesread = fread(dest, 1, size, handle); - ErrorIfPNG((UINT8 *)dest, bytesread, wadfiles[wad]->filename, l->fullname); + ErrorIfPNG((UINT8 *)(dest), bytesread, wadfiles[wad]->filename, l->fullname); return bytesread; } #else @@ -1616,8 +1626,8 @@ size_t W_ReadLumpHeaderPwad(UINT16 wad, UINT16 lump, void *dest, size_t size, si char *decData; // Lump's decompressed real data. size_t retval; // Helper var, lzf_decompress returns 0 when an error occurs. - rawData = Z_Malloc(l->disksize, PU_STATIC, NULL); - decData = Z_Malloc(l->size, PU_STATIC, NULL); + rawData = (char *)(Z_Malloc(l->disksize, PU_STATIC, NULL)); + decData = (char *)(Z_Malloc(l->size, PU_STATIC, NULL)); if (fread(rawData, 1, l->disksize, handle) < l->disksize) I_Error("wad %d, lump %d: cannot read compressed data", wad, lump); @@ -1644,7 +1654,7 @@ size_t W_ReadLumpHeaderPwad(UINT16 wad, UINT16 lump, void *dest, size_t size, si Z_Free(rawData); Z_Free(decData); #ifdef NO_PNG_LUMPS - ErrorIfPNG((UINT8 *)dest, size, wadfiles[wad]->filename, l->fullname); + ErrorIfPNG((UINT8 *)(dest), size, wadfiles[wad]->filename, l->fullname); #endif return size; #else @@ -1664,8 +1674,8 @@ size_t W_ReadLumpHeaderPwad(UINT16 wad, UINT16 lump, void *dest, size_t size, si unsigned long rawSize = l->disksize; unsigned long decSize = size; - rawData = Z_Malloc(rawSize, PU_STATIC, NULL); - decData = dest; + rawData = (UINT8 *)(Z_Malloc(rawSize, PU_STATIC, NULL)); + decData = (UINT8 *)(dest); if (fread(rawData, 1, rawSize, handle) < rawSize) I_Error("wad %d, lump %d: cannot read compressed data", wad, lump); @@ -1701,7 +1711,7 @@ size_t W_ReadLumpHeaderPwad(UINT16 wad, UINT16 lump, void *dest, size_t size, si Z_Free(rawData); #ifdef NO_PNG_LUMPS - ErrorIfPNG((UINT8 *)dest, size, wadfiles[wad]->filename, l->fullname); + ErrorIfPNG((UINT8 *)(dest), size, wadfiles[wad]->filename, l->fullname); #endif return size; } @@ -1889,7 +1899,7 @@ void *W_CacheSoftwarePatchNumPwad(UINT16 wad, UINT16 lump, INT32 tag) W_ReadLumpHeaderPwad(wad, lump, lumpdata, 0, 0); dest = Z_Calloc(sizeof(patch_t), tag, &lumpcache[lump]); - Patch_Create((softwarepatch_t*)lumpdata, len, dest); + Patch_Create((softwarepatch_t *)(lumpdata), len, dest); Z_Free(lumpdata); } @@ -1911,7 +1921,7 @@ void *W_CachePatchNumPwad(UINT16 wad, UINT16 lump, INT32 tag) if (!TestValidLump(wad, lump)) return NULL; - patch = (patch_t *)W_CacheSoftwarePatchNumPwad(wad, lump, tag); + patch = (patch_t *)(W_CacheSoftwarePatchNumPwad(wad, lump, tag)); if (patch == NULL) return NULL; @@ -1922,7 +1932,7 @@ void *W_CachePatchNumPwad(UINT16 wad, UINT16 lump, INT32 tag) Patch_CreateGL(patch); #endif - return (void *)patch; + return (void*)(patch); } void *W_CachePatchNum(lumpnum_t lumpnum, INT32 tag) @@ -1940,17 +1950,20 @@ void *W_GetCachedPatchNumPwad(UINT16 wad, UINT16 lump) #ifdef ROTSPRITE // Caches a rotsprite for patch rotation. -void *W_GetCachedRotPatchPwad(UINT16 wadnum, UINT16 lumpnum) +void *W_GetCachedRotPatchPwad(UINT16 wad, UINT16 lump) { - lumpcache_t *rotcache = wadfiles[wadnum]->rotcache; + if (!TestValidLump(wad, lump)) + return NULL; - if (!rotcache[lumpnum]) + lumpcache_t *rotcache = wadfiles[wad]->rotcache; + + if (!rotcache[lump]) { rotsprite_t *rspr = RotatedPatch_Create(ROTANGLES); - Z_SetUser(rspr, (void **)(&rotcache[lumpnum])); + Z_SetUser(rspr, (void**)(&rotcache[lump])); } - return (void *)(rotcache[lumpnum]); + return (void*)(rotcache[lump]); } #endif // ROTSPRITE @@ -1963,7 +1976,7 @@ void W_UnlockCachedPatch(void *patch) // have different lifetimes from software's. #ifdef HWRENDER if (rendermode == render_opengl) - HWR_UnlockCachedPatch((GLPatch_t *)((patch_t *)patch)->hardware); + HWR_UnlockCachedPatch((GLPatch_t *)(((patch_t *)(patch))->hardware)); else #endif Z_ChangeTag(patch, PU_PATCH); @@ -1998,7 +2011,7 @@ void *W_CachePatchNameRotated(const char *name, INT32 rotationangle, INT32 tag) if (rotationangle < 1 || rotationangle >= ROTANGLES) return W_CachePatchNum(num, tag); - rspr = (rotsprite_t *)W_GetCachedRotPatchPwad(WADFILENUM(num),LUMPNUM(num)); + rspr = (rotsprite_t *)(W_GetCachedRotPatchPwad(WADFILENUM(num), LUMPNUM(num))); if (rspr == NULL) return W_CachePatchNum(W_GetNumForName("MISSING"), tag); @@ -2007,7 +2020,7 @@ void *W_CachePatchNameRotated(const char *name, INT32 rotationangle, INT32 tag) { INT32 xpivot = 0, ypivot = 0; - ptr = (patch_t *)W_CachePatchNum(num, PU_PATCH); + ptr = (patch_t *)(W_CachePatchNum(num, PU_PATCH)); if (ptr == NULL) return W_CachePatchNum(W_GetNumForName("MISSING"), tag); @@ -2200,7 +2213,7 @@ static int W_VerifyPK3(FILE *fp, lumpchecklist_t *checklist, boolean status) fseek(fp, 0, SEEK_END); file_size = ftell(fp); - const long startpos = (file_size - (22 + 65536)); + const long startpos = file_size - (22 + 65536); if (!ResFindSignature(fp, pat_end, max(0l, startpos))) return true; @@ -2214,7 +2227,7 @@ static int W_VerifyPK3(FILE *fp, lumpchecklist_t *checklist, boolean status) fseek(fp, LONG(zend.cdiroffset), SEEK_SET); - CLEANUP(pfree) char *cdir = (char*)(malloc(LONG(zend.cdirsize))); + CLEANUP(pfree) char *cdir = (char *)(malloc(LONG(zend.cdirsize))); if (fread(cdir, 1, LONG(zend.cdirsize), fp) < (UINT32)(LONG(zend.cdirsize))) { @@ -2225,7 +2238,7 @@ static int W_VerifyPK3(FILE *fp, lumpchecklist_t *checklist, boolean status) for (i = 0; i < numlumps; i++) { - zentry_t *zentry = (zentry_t*)(cdir + offset); + zentry_t *zentry = (zentry_t *)(cdir + offset); char* fullname; char* trimname; char* dotpos; @@ -2237,7 +2250,7 @@ static int W_VerifyPK3(FILE *fp, lumpchecklist_t *checklist, boolean status) if (verified == true) { - fullname = (char*)(malloc(SHORT(zentry->namelen) + 1)); + fullname = (char *)(malloc(SHORT(zentry->namelen) + 1)); strncpy(fullname, (char *)(zentry + 1), SHORT(zentry->namelen)); fullname[zentry->namelen] = 0; @@ -2448,7 +2461,7 @@ static int W_CheckPK3Contains(FILE *fp, lumpchecklist_t *checklist) fseek(fp, 0, SEEK_END); file_size = ftell(fp); - const long startpos = (file_size - (22 + 65536)); + const long startpos = file_size - (22 + 65536); if (!ResFindSignature(fp, pat_end, max(0l, startpos))) return true; @@ -2462,7 +2475,7 @@ static int W_CheckPK3Contains(FILE *fp, lumpchecklist_t *checklist) fseek(fp, LONG(zend.cdiroffset), SEEK_SET); - CLEANUP(pfree) char *cdir = (char*)(malloc(LONG(zend.cdirsize))); + CLEANUP(pfree) char *cdir = (char *)(malloc(LONG(zend.cdirsize))); if (fread(cdir, 1, LONG(zend.cdirsize), fp) < (UINT32)(LONG(zend.cdirsize))) { @@ -2473,7 +2486,7 @@ static int W_CheckPK3Contains(FILE *fp, lumpchecklist_t *checklist) for (i = 0; i < numlumps; i++) { - zentry_t *zentry = (zentry_t*)(cdir + offset); + zentry_t *zentry = (zentry_t *)(cdir + offset); CLEANUP(pfree) char* fullname = NULL; char* trimname; char* dotpos; @@ -2483,7 +2496,7 @@ static int W_CheckPK3Contains(FILE *fp, lumpchecklist_t *checklist) return false; } - fullname = (char*)(malloc(SHORT(zentry->namelen) + 1)); + fullname = (char *)(malloc(SHORT(zentry->namelen) + 1)); strncpy(fullname, (char *)(zentry + 1), SHORT(zentry->namelen)); fullname[zentry->namelen] = 0; @@ -2621,14 +2634,14 @@ virtres_t* vres_GetMap(lumpnum_t lumpnum) size_t *vsizecache; // Remember that we're assuming that the WAD will have a specific set of lumps in a specific order. - UINT8 *wadData = (UINT8*)(W_CacheLumpNum(lumpnum, PU_LEVEL)); + UINT8 *wadData = (UINT8 *)(W_CacheLumpNum(lumpnum, PU_LEVEL)); if (wadData == NULL) I_Error("vres_GetMap: Invalid WadData!\n"); - filelump_t *fileinfo = (filelump_t *)(wadData + LONG(((wadinfo_t *)wadData)->infotableofs)); + filelump_t *fileinfo = (filelump_t *)(wadData + LONG(((wadinfo_t *)(wadData))->infotableofs)); - i = LONG(((wadinfo_t *)wadData)->numlumps); - vsizecache = (size_t*)(Z_Malloc(sizeof(size_t)*i, PU_LEVEL, NULL)); + i = LONG(((wadinfo_t *)(wadData))->numlumps); + vsizecache = (size_t *)(Z_Malloc(sizeof(size_t)*i, PU_LEVEL, NULL)); for (realentry = 0; realentry < i; realentry++) { @@ -2640,7 +2653,7 @@ virtres_t* vres_GetMap(lumpnum_t lumpnum) numlumps++; } - vlumps = (virtlump_t*)(Z_Malloc(sizeof(virtlump_t)*numlumps, PU_LEVEL, NULL)); + vlumps = (virtlump_t *)(Z_Malloc(sizeof(virtlump_t)*numlumps, PU_LEVEL, NULL)); if (vlumps == NULL) I_Error("vres_GetMap: Out of memory!\n"); @@ -2662,7 +2675,7 @@ virtres_t* vres_GetMap(lumpnum_t lumpnum) // Play it safe with the name in this case. memcpy(vlumps[i].name, name, 8); vlumps[i].name[8] = '\0'; - vlumps[i].data = (UINT8*)( + vlumps[i].data = (UINT8 *)( Z_Malloc(vlumps[i].size, PU_LEVEL, NULL) // This is memory inefficient, sorry about that. ); memcpy(vlumps[i].data, wadData + LONG((fileinfo + realentry)->filepos), vlumps[i].size); @@ -2689,7 +2702,7 @@ virtres_t* vres_GetMap(lumpnum_t lumpnum) } numlumps++; - vlumps = (virtlump_t*)(Z_Malloc(sizeof(virtlump_t)*numlumps, PU_LEVEL, NULL)); + vlumps = (virtlump_t *)(Z_Malloc(sizeof(virtlump_t)*numlumps, PU_LEVEL, NULL)); if (vlumps == NULL) I_Error("vres_GetMap: Out of memory!\n"); @@ -2710,11 +2723,11 @@ virtres_t* vres_GetMap(lumpnum_t lumpnum) vlumps[i].size = W_LumpLength(lumpnum); memcpy(vlumps[i].name, name, 8); vlumps[i].name[8] = '\0'; - vlumps[i].data = (UINT8*)(W_CacheLumpNum(lumpnum, PU_LEVEL)); + vlumps[i].data = (UINT8 *)(W_CacheLumpNum(lumpnum, PU_LEVEL)); } } - vres = (virtres_t*)(Z_Malloc(sizeof(virtres_t), PU_LEVEL, NULL)); + vres = (virtres_t *)(Z_Malloc(sizeof(virtres_t), PU_LEVEL, NULL)); vres->vlumps = vlumps; vres->numlumps = numlumps; diff --git a/src/w_wad.h b/src/w_wad.h index 6240c31f3..fc7f25fec 100644 --- a/src/w_wad.h +++ b/src/w_wad.h @@ -68,11 +68,11 @@ typedef struct unsigned long position; // filelump_t filepos unsigned long disksize; // filelump_t size char name[9]; // filelump_t name[] e.g. "LongEntr" - char *longname; // e.g. "LongEntryName" + //char *longname; // e.g. "LongEntryName" char *fullname; // e.g. "Folder/Subfolder/LongEntryName.extension" size_t namelength; // length of name - size_t longnamelength; // length of longname + //size_t longnamelength; // length of longname size_t fullnamelength; // length of fullname size_t size; // real (uncompressed) size @@ -80,8 +80,8 @@ typedef struct struct { UINT32 name; // hash of name - UINT32 longname; // hash of longname - UINT32 fullname; // hash of fullname + //UINT32 longname; // hash of longname + //UINT32 fullname; // hash of fullname } hash; } lumpinfo_t; @@ -126,7 +126,6 @@ typedef enum restype RET_UNKNOWN, } restype_t; - typedef struct wadfile_s { char *filename; @@ -137,8 +136,8 @@ typedef struct wadfile_s #ifdef ROTSPRITE lumpcache_t *rotcache; // Cache rotsprites for rotating patches. #endif - aatree_t *startfolders; - aatree_t *endfolders; + struct FolderCache *startfolders; + struct FolderCache *endfolders; UINT16 numlumps; // this wad's number of resources FILE *handle; UINT32 filesize; // for network @@ -155,6 +154,7 @@ extern wadfile_t *wadfiles[MAX_WADFILES]; // ========================================================================= +void W_Startup(void); void W_Shutdown(void); // Opens a WAD file. Returns the FILE * handle for the file, or NULL if not found or could not be opened @@ -174,13 +174,11 @@ INT32 W_AddAutoloadedLocalFiles(char **filenames); #define W_FileHasFolders(wadfile) ((wadfile)->type == RET_PK3) -UINT32 W_HashLumpName(const char *name); - const char *W_CheckNameForNumPwad(UINT16 wad, UINT16 lump); const char *W_CheckNameForNum(lumpnum_t lumpnum); UINT16 W_CheckNumForNamePwad(const char *name, UINT16 wad, UINT16 startlump); // checks only in one pwad -UINT16 W_CheckNumForLongNamePwad(const char *name, UINT16 wad, UINT16 startlump); +//UINT16 W_CheckNumForLongNamePwad(const char *name, UINT16 wad, UINT16 startlump); /* Find the first lump after F_START for instance. */ UINT16 W_CheckNumForMarkerStartPwad(const char *name, UINT16 wad, UINT16 startlump); @@ -190,9 +188,9 @@ UINT16 W_CheckNumForFolderStartPK3(const char *name, UINT16 wad, UINT16 startlum UINT16 W_CheckNumForFolderEndPK3(const char *name, UINT16 wad, UINT16 startlump); lumpnum_t W_CheckNumForName(const char *name); -lumpnum_t W_CheckNumForLongName(const char *name); +//lumpnum_t W_CheckNumForLongName(const char *name); lumpnum_t W_GetNumForName(const char *name); // like W_CheckNumForName but I_Error on LUMPERROR -lumpnum_t W_GetNumForLongName(const char *name); +//lumpnum_t W_GetNumForLongName(const char *name); lumpnum_t W_CheckNumForNameInBlock(const char *name, const char *blockstart, const char *blockend); UINT8 W_LumpExists(const char *name); // Lua uses this. UINT8 W_CheckMultipleLumps(const char* lump, ...); // variadic version of above lmao @@ -233,7 +231,7 @@ void *W_CacheSoftwarePatchNumPwad(UINT16 wad, UINT16 lump, INT32 tag); void *W_CacheSoftwarePatchNum(lumpnum_t lumpnum, INT32 tag); #ifdef ROTSPRITE -void *W_GetCachedRotPatchPwad(UINT16 wadnum, UINT16 lumpnum); // Get patch-based rotsprites from the cache. +void *W_GetCachedRotPatchPwad(UINT16 wad, UINT16 lump); // Get patch-based rotsprites from the cache. void *W_CachePatchNameRotated(const char *name, INT32 rotationangle, INT32 tag); #endif