Text rendering rewrite and optimizations; some refactoring (#129)
* wip font rendering stuff; hashtable monstrosity is temporary * various text rendering fixes/improvements * HashTables™ 3.0 * Add some comments to aid navigating the hashtable macro maze * overhaul text rendering API; add default and example shaders * text: implement text_render for spellcard effect; misc fixes * README: update dependencies Bye SDL_ttf, hello freetype2. * text_draw: fix resolution/scale-dependent bugs * make text_draw fallback to the current shader, fix hud and stagetext * repair the bgm loading * fix spell practice mode * fix walloftext forgot one site of text_draw earlier * fix wrapped text rendering * fix and simplify the hud text shader * dynamic glyph cache * implement font size change on window resize/quality setting change/etc. * rename text shaders for consistency * preloads for fonts and text shaders * make the stagetext shader look somewhat better * text_render: attempt to normalize height * small improvement for stagetext
This commit is contained in:
parent
433b9869e2
commit
322edd0dce
75 changed files with 3620 additions and 1429 deletions
|
@ -16,11 +16,12 @@ Installation
|
|||
Dependencies
|
||||
^^^^^^^^^^^^
|
||||
|
||||
- SDL2 >= 2.0.5, SDL2_ttf, SDL2_mixer, SDL2_image
|
||||
- SDL2 >= 2.0.5, SDL2_mixer, SDL2_image
|
||||
- zlib
|
||||
- libzip >= 1.0
|
||||
- libpng >= 1.5.0
|
||||
- libjpeg
|
||||
- freetype2
|
||||
- OpenGL >= 3.3
|
||||
|
||||
Build-only dependencies
|
||||
|
|
|
@ -94,6 +94,7 @@ foreach flag : [
|
|||
'-Wunreachable-code-loop-increment',
|
||||
'-Wgnu',
|
||||
'-Wcast-align',
|
||||
'-Wcast-align=strict',
|
||||
]
|
||||
if cc.has_argument(flag)
|
||||
taisei_c_warnargs += flag
|
||||
|
@ -105,21 +106,21 @@ taisei_c_args += taisei_c_warnargs
|
|||
static = get_option('static')
|
||||
|
||||
dep_sdl2 = dependency('sdl2', version : '>=2.0.5', required : true, static : static)
|
||||
dep_sdl2_ttf = dependency('SDL2_ttf', required : true, static : static)
|
||||
dep_sdl2_mixer = dependency('SDL2_mixer', required : false, static : static)
|
||||
dep_sdl2_image = dependency('SDL2_image', required : true, static : static)
|
||||
dep_zlib = dependency('zlib', required : true, static : static)
|
||||
dep_png = dependency('libpng', version : '>=1.5', required : true, static : static)
|
||||
dep_zip = dependency('libzip', version : '>=1.0', required : false, static : static)
|
||||
dep_freetype = dependency('freetype2', required : true, static : static)
|
||||
dep_cglm = subproject('cglm').get_variable('cglm_dep')
|
||||
dep_m = cc.find_library('m', required : false)
|
||||
|
||||
taisei_deps = [
|
||||
dep_sdl2,
|
||||
dep_sdl2_ttf,
|
||||
dep_sdl2_image,
|
||||
dep_zlib,
|
||||
dep_png,
|
||||
dep_freetype,
|
||||
dep_m,
|
||||
dep_cglm,
|
||||
]
|
||||
|
|
|
@ -1,41 +0,0 @@
|
|||
#version 330 core
|
||||
|
||||
#include "interface/standard.glslh"
|
||||
|
||||
UNIFORM(1) vec4 colorAtop;
|
||||
UNIFORM(2) vec4 colorAbot;
|
||||
UNIFORM(3) vec4 colorBtop;
|
||||
UNIFORM(4) vec4 colorBbot;
|
||||
UNIFORM(5) vec4 colortint;
|
||||
UNIFORM(6) float split;
|
||||
|
||||
void main(void) {
|
||||
vec4 texel = texture(tex, texCoord);
|
||||
|
||||
float vsplit;
|
||||
vec4 cAt;
|
||||
vec4 cAb;
|
||||
vec4 cBt;
|
||||
vec4 cBb;
|
||||
|
||||
// branching on a uniform is fine
|
||||
if(split < 0.0) {
|
||||
vsplit = -split;
|
||||
cAt = colorBtop;
|
||||
cAb = colorBbot;
|
||||
cBt = colorAtop;
|
||||
cBb = colorAbot;
|
||||
} else {
|
||||
vsplit = split;
|
||||
cAt = colorAtop;
|
||||
cAb = colorAbot;
|
||||
cBt = colorBtop;
|
||||
cBb = colorBbot;
|
||||
}
|
||||
|
||||
fragColor = texel * colortint * (
|
||||
texCoord.x >= vsplit ?
|
||||
mix(cBt, cBb, texCoord.y) :
|
||||
mix(cAt, cAb, texCoord.y)
|
||||
);
|
||||
}
|
|
@ -1,2 +0,0 @@
|
|||
|
||||
glsl_objects = standard.vert hud_text.frag
|
|
@ -15,13 +15,18 @@ ATTRIBUTE(2) vec2 vertTexCoord;
|
|||
/*
|
||||
* Per-instance attributes
|
||||
*/
|
||||
ATTRIBUTE(3) mat4 spriteVMTransform;
|
||||
ATTRIBUTE(3) mat4 spriteVMTransform;
|
||||
// 4
|
||||
// 5
|
||||
// 6
|
||||
ATTRIBUTE(7) vec4 spriteRGBA;
|
||||
ATTRIBUTE(8) vec4 spriteTexRegion;
|
||||
ATTRIBUTE(9) float spriteCustomParam;
|
||||
ATTRIBUTE(7) mat4 spriteTexTransform;
|
||||
// 8
|
||||
// 9
|
||||
// 10
|
||||
ATTRIBUTE(11) vec4 spriteRGBA;
|
||||
ATTRIBUTE(12) vec4 spriteTexRegion;
|
||||
ATTRIBUTE(13) vec2 spriteDimensions;
|
||||
ATTRIBUTE(14) float spriteCustomParam;
|
||||
#endif
|
||||
|
||||
#ifdef FRAG_STAGE
|
||||
|
@ -30,10 +35,12 @@ OUT(0) vec4 fragColor;
|
|||
|
||||
UNIFORM(0) sampler2D tex;
|
||||
|
||||
VARYING(0) vec2 texCoordRaw;
|
||||
VARYING(1) vec2 texCoord;
|
||||
VARYING(2) vec4 texRegion;
|
||||
VARYING(3) vec4 color;
|
||||
VARYING(4) float customParam;
|
||||
VARYING(0) vec2 texCoordRaw;
|
||||
VARYING(1) vec2 texCoord;
|
||||
VARYING(2) vec2 texCoordOverlay;
|
||||
VARYING(3) vec4 texRegion;
|
||||
VARYING(4) vec4 color;
|
||||
VARYING(5) vec2 dimensions;
|
||||
VARYING(6) float customParam;
|
||||
|
||||
#endif
|
||||
|
|
|
@ -19,10 +19,18 @@ void main(void) {
|
|||
texCoord = uv_to_region(spriteTexRegion, vertTexCoord);
|
||||
#endif
|
||||
|
||||
#ifdef SPRITE_OUT_TEXCOORD_OVERLAY
|
||||
texCoordOverlay = (spriteTexTransform * vec4(vertTexCoord, 0.0, 1.0)).xy;
|
||||
#endif
|
||||
|
||||
#ifdef SPRITE_OUT_TEXREGION
|
||||
texRegion = spriteTexRegion;
|
||||
#endif
|
||||
|
||||
#ifdef SPRITE_OUT_DIMENSIONS
|
||||
dimensions = spriteDimensions;
|
||||
#endif
|
||||
|
||||
#ifdef SPRITE_OUT_CUSTOM
|
||||
customParam = spriteCustomParam;
|
||||
#endif
|
||||
|
|
|
@ -13,7 +13,6 @@ glsl_files = files(
|
|||
'boss_zoom.frag.glsl',
|
||||
'glitch.frag.glsl',
|
||||
'graph.frag.glsl',
|
||||
'hud_text.frag.glsl',
|
||||
'ingame_menu.frag.glsl',
|
||||
'laser_generic.vert.glsl',
|
||||
'marisa_laser.frag.glsl',
|
||||
|
@ -39,11 +38,16 @@ glsl_files = files(
|
|||
'sprite_youmu_myon_shot.frag.glsl',
|
||||
'stage6_sky.frag.glsl',
|
||||
'stage6_sky.vert.glsl',
|
||||
'stagetext.frag.glsl',
|
||||
'standard.frag.glsl',
|
||||
'standard.vert.glsl',
|
||||
'standardnotex.frag.glsl',
|
||||
'standardnotex.vert.glsl',
|
||||
'text_default.frag.glsl',
|
||||
'text_default.vert.glsl',
|
||||
'text_example.frag.glsl',
|
||||
'text_example.vert.glsl',
|
||||
'text_hud.frag.glsl',
|
||||
'text_stagetext.frag.glsl',
|
||||
'texture_post_load.frag.glsl',
|
||||
'tower_light.frag.glsl',
|
||||
'tower_light.vert.glsl',
|
||||
|
|
|
@ -13,6 +13,7 @@ void main(void) {
|
|||
pos = mod(pos,vec2(1.0+(0.01/w),1.0));
|
||||
pos *= vec2(w,h);
|
||||
vec4 clr = texture(tex, pos);
|
||||
clr.gb = clr.rr;
|
||||
clr.a *= float(pos.x < w && pos.y < h)*(2.*t-t*t);
|
||||
|
||||
fragColor = clr;
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
#version 330 core
|
||||
|
||||
#include "lib/render_context.glslh"
|
||||
#include "interface/standard.glslh"
|
||||
|
||||
UNIFORM(1) sampler2D trans;
|
||||
UNIFORM(2) vec3 color;
|
||||
UNIFORM(3) float t;
|
||||
|
||||
void main(void) {
|
||||
vec2 pos = texCoordRaw;
|
||||
vec2 f = pos-vec2(0.5,0.5);
|
||||
pos += (f*0.05*sin(10.0*(length(f)+t)))*(1.0-t);
|
||||
|
||||
pos = clamp(pos,0.0,1.0);
|
||||
vec4 texel = texture(tex, (r_textureMatrix*vec4(pos,0.0,1.0)).xy);
|
||||
|
||||
texel.a *= clamp((texture(trans, pos).r+0.5)*2.5*t-0.5, 0.0, 1.0);
|
||||
fragColor = vec4(color,1.0)*texel;
|
||||
}
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
|
||||
glsl_objects = standard.vert stagetext.frag
|
7
resources/shader/text_default.frag.glsl
Normal file
7
resources/shader/text_default.frag.glsl
Normal file
|
@ -0,0 +1,7 @@
|
|||
#version 330 core
|
||||
|
||||
#include "interface/sprite.glslh"
|
||||
|
||||
void main(void) {
|
||||
fragColor = color * vec4(1, 1, 1, texture(tex, texCoord).r);
|
||||
}
|
1
resources/shader/text_default.prog
Normal file
1
resources/shader/text_default.prog
Normal file
|
@ -0,0 +1 @@
|
|||
glsl_objects = text_default.vert text_default.frag
|
7
resources/shader/text_default.vert.glsl
Normal file
7
resources/shader/text_default.vert.glsl
Normal file
|
@ -0,0 +1,7 @@
|
|||
#version 330 core
|
||||
|
||||
#define SPRITE_OUT_COLOR
|
||||
#define SPRITE_OUT_TEXCOORD
|
||||
#define SPRITE_OUT_TEXCOORD_OVERLAY
|
||||
|
||||
#include "lib/sprite_default.vert.glslh"
|
30
resources/shader/text_example.frag.glsl
Normal file
30
resources/shader/text_example.frag.glsl
Normal file
|
@ -0,0 +1,30 @@
|
|||
#version 330 core
|
||||
|
||||
#include "lib/render_context.glslh"
|
||||
#include "interface/sprite.glslh"
|
||||
#include "lib/util.glslh"
|
||||
|
||||
void main(void) {
|
||||
vec2 tc = texCoord;
|
||||
vec2 tc_overlay = texCoordOverlay;
|
||||
|
||||
// Transform local glyph texture coordinates.
|
||||
tc *= dimensions;
|
||||
tc.x += 5 * sin(3 * tc_overlay.y + customParam);
|
||||
tc /= dimensions;
|
||||
|
||||
// Compute alpha multiplier used to chop off unwanted texels from the atlas.
|
||||
float a = float(tc.x >= 0 && tc.x <= 1 && tc.y >= 0 && tc.y <= 1);
|
||||
|
||||
// Map local coordinates to the region of the atlas texture that contains our glyph.
|
||||
vec2 tc_atlas = uv_to_region(texRegion, tc);
|
||||
|
||||
// Display the glyph.
|
||||
fragColor = color * vec4(1, 1, 1, texture(tex, tc_atlas).r * a);
|
||||
|
||||
// Visualize global overlay coordinates. You could use them to span a texture across all glyphs.
|
||||
fragColor *= vec4(tc_overlay.x, tc_overlay.y, 0, 1);
|
||||
|
||||
// Visualize the local coordinates.
|
||||
// fragColor = vec4(mod(tc.x, 1), float(length(tc - vec2(0.5, 0.5)) < 0.5), mod(tc.y, 1), 1.0);
|
||||
}
|
1
resources/shader/text_example.prog
Normal file
1
resources/shader/text_example.prog
Normal file
|
@ -0,0 +1 @@
|
|||
glsl_objects = text_example.vert text_example.frag
|
35
resources/shader/text_example.vert.glsl
Normal file
35
resources/shader/text_example.vert.glsl
Normal file
|
@ -0,0 +1,35 @@
|
|||
#version 330 core
|
||||
|
||||
#include "lib/defs.glslh"
|
||||
#include "lib/render_context.glslh"
|
||||
#include "lib/util.glslh"
|
||||
#include "interface/sprite.glslh"
|
||||
|
||||
void main(void) {
|
||||
// Enlarge the quad to make some room for effects.
|
||||
float scale = 2;
|
||||
vec2 pos = vertPos * scale;
|
||||
gl_Position = r_projectionMatrix * spriteVMTransform * vec4(pos, 0.0, 1.0);
|
||||
|
||||
// Adjust texture coordinates so that the glyph remains in the center, unaffected by the scaling factor.
|
||||
// Some extra code is required in the fragment shader to chop off the unwanted bits of the texture.
|
||||
vec2 tc = vertTexCoord * scale - vec2(0.5 * (scale - 1));
|
||||
|
||||
// Pass tc not mapped to the texture region, because we want to do per-fragment transformations on it.
|
||||
texCoord = tc;
|
||||
|
||||
// Pass the normalized texture region, so that we can map texCoord to it later in the fragment shader.
|
||||
texRegion = spriteTexRegion;
|
||||
|
||||
// Global overlay coordinates for this primitive.
|
||||
texCoordOverlay = (spriteTexTransform * vec4(tc, 0.0, 1.0)).xy;
|
||||
|
||||
// Fragment shader needs to know the sprite dimensions so that it can denormalize texCoord for processing.
|
||||
dimensions = spriteDimensions;
|
||||
|
||||
// Arbitrary custom parameter provided by the application. You can use it to pass e.g. times/frames.
|
||||
customParam = spriteCustomParam;
|
||||
|
||||
// Should be obvious.
|
||||
color = spriteRGBA;
|
||||
}
|
9
resources/shader/text_hud.frag.glsl
Normal file
9
resources/shader/text_hud.frag.glsl
Normal file
|
@ -0,0 +1,9 @@
|
|||
#version 330 core
|
||||
|
||||
#include "lib/render_context.glslh"
|
||||
#include "interface/sprite.glslh"
|
||||
|
||||
void main(void) {
|
||||
vec4 texel = texture(tex, texCoord);
|
||||
fragColor = vec4(color.rgb, color.a * texel.r) * mix(1.0, 0.8, texCoordOverlay.y);
|
||||
}
|
2
resources/shader/text_hud.prog
Normal file
2
resources/shader/text_hud.prog
Normal file
|
@ -0,0 +1,2 @@
|
|||
|
||||
glsl_objects = text_default.vert text_hud.frag
|
39
resources/shader/text_stagetext.frag.glsl
Normal file
39
resources/shader/text_stagetext.frag.glsl
Normal file
|
@ -0,0 +1,39 @@
|
|||
#version 330 core
|
||||
|
||||
#include "lib/render_context.glslh"
|
||||
#include "interface/sprite.glslh"
|
||||
#include "lib/util.glslh"
|
||||
|
||||
UNIFORM(1) sampler2D trans;
|
||||
|
||||
float tc_mask(vec2 tc) {
|
||||
return float(tc.x >= 0 && tc.x <= 1 && tc.y >= 0 && tc.y <= 1);
|
||||
}
|
||||
|
||||
void main(void) {
|
||||
float t = customParam;
|
||||
vec2 tc = texCoord;
|
||||
vec2 tc_overlay = texCoordOverlay;
|
||||
vec2 f = tc_overlay - vec2(0.5);
|
||||
|
||||
tc *= dimensions;
|
||||
tc += 5 * f * sin(10 * (length(f) + t)) * (1 - t);
|
||||
tc /= dimensions;
|
||||
|
||||
float a = tc_mask(tc);
|
||||
|
||||
vec4 texel = texture(tex, uv_to_region(texRegion, tc));
|
||||
vec4 textfrag = vec4(color.rgb, a * color.a * texel.r);
|
||||
|
||||
tc -= vec2(1) / dimensions;
|
||||
a = tc_mask(tc);
|
||||
|
||||
texel = texture(tex, uv_to_region(texRegion, tc));
|
||||
vec4 shadowfrag = vec4(vec3(0), a * color.a * texel.r);
|
||||
|
||||
fragColor = textfrag;
|
||||
fragColor = mix(shadowfrag, textfrag, sqrt(textfrag.a));
|
||||
|
||||
tc_overlay = clamp(tc_overlay,0.01,0.99); // The overlay coordinates are outside of [0,1] in the padding region, so we make sure there are no wrap around artifacts when a bit of text is distorted to this region.
|
||||
fragColor.a *= clamp((texture(trans, tc_overlay).r + 0.5) * 2.5 * t-0.5, 0.0, 1.0);
|
||||
}
|
2
resources/shader/text_stagetext.prog
Normal file
2
resources/shader/text_stagetext.prog
Normal file
|
@ -0,0 +1,2 @@
|
|||
|
||||
glsl_objects = text_example.vert text_stagetext.frag
|
|
@ -18,7 +18,7 @@
|
|||
CurrentBGM current_bgm = { .name = NULL };
|
||||
|
||||
static char *saved_bgm;
|
||||
static Hashtable *sfx_volumes;
|
||||
static ht_str2int_t sfx_volumes;
|
||||
|
||||
static struct enqueued_sound {
|
||||
LIST_INTERFACE(struct enqueued_sound);
|
||||
|
@ -173,7 +173,6 @@ static void bgm_cfg_volume_callback(ConfigIndex idx, ConfigValue v) {
|
|||
}
|
||||
|
||||
static bool store_sfx_volume(const char *key, const char *val, void *data) {
|
||||
Hashtable *ht = data;
|
||||
int vol = atoi(val);
|
||||
|
||||
if(vol < 0 || vol > 128) {
|
||||
|
@ -184,14 +183,15 @@ static bool store_sfx_volume(const char *key, const char *val, void *data) {
|
|||
log_debug("Default volume for %s is now %i", key, vol);
|
||||
|
||||
if(vol != DEFAULT_SFX_VOLUME) {
|
||||
hashtable_set_string(ht, key, (void*)(intptr_t)vol);
|
||||
ht_set(&sfx_volumes, key, vol);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void load_config_files(void) {
|
||||
sfx_volumes = hashtable_new_stringkeys();
|
||||
parse_keyvalue_file_cb(SFX_PATH_PREFIX "volumes.conf", store_sfx_volume, sfx_volumes);
|
||||
ht_create(&sfx_volumes);
|
||||
parse_keyvalue_file_cb(SFX_PATH_PREFIX "volumes.conf", store_sfx_volume, NULL);
|
||||
}
|
||||
|
||||
static inline char* get_bgm_desc(char *name) {
|
||||
|
@ -201,13 +201,7 @@ static inline char* get_bgm_desc(char *name) {
|
|||
}
|
||||
|
||||
int get_default_sfx_volume(const char *sfx) {
|
||||
void *v = hashtable_get_string(sfx_volumes, sfx);
|
||||
|
||||
if(v != NULL) {
|
||||
return (intptr_t)v;
|
||||
}
|
||||
|
||||
return DEFAULT_SFX_VOLUME;
|
||||
return ht_get(&sfx_volumes, sfx, DEFAULT_SFX_VOLUME);
|
||||
}
|
||||
|
||||
void resume_bgm(void) {
|
||||
|
@ -315,9 +309,5 @@ void audio_init(void) {
|
|||
|
||||
void audio_shutdown(void) {
|
||||
audio_backend_shutdown();
|
||||
|
||||
if(sfx_volumes) {
|
||||
hashtable_free(sfx_volumes);
|
||||
sfx_volumes = NULL;
|
||||
}
|
||||
ht_destroy(&sfx_volumes);
|
||||
}
|
||||
|
|
45
src/boss.c
45
src/boss.c
|
@ -39,24 +39,30 @@ Boss* create_boss(char *name, char *ani, char *dialog, complex pos) {
|
|||
return buf;
|
||||
}
|
||||
|
||||
void draw_boss_text(Alignment align, float x, float y, const char *text, Font *fnt, Color clr) {
|
||||
Color color_saved = r_color_current();
|
||||
void draw_boss_text(Alignment align, float x, float y, const char *text, const char *fnt, Color clr) {
|
||||
ShaderProgram *sh_prev = r_shader_current();
|
||||
r_shader("text_default");
|
||||
text_draw(text, &(TextParams) {
|
||||
.pos = { x + 1, y + 1 },
|
||||
.color = derive_color(rgb(0, 0, 0), CLRMASK_A, clr),
|
||||
.font = fnt,
|
||||
.align = align,
|
||||
});
|
||||
|
||||
r_shader_standard();
|
||||
r_color(derive_color(rgb(0, 0, 0), CLRMASK_A, clr));
|
||||
draw_text(align, x+1, y+1, text, fnt);
|
||||
r_color(clr);
|
||||
draw_text(align, x, y, text, fnt);
|
||||
r_shader("sprite_default");
|
||||
|
||||
r_color(color_saved);
|
||||
text_draw(text, &(TextParams) {
|
||||
.pos = { x, y },
|
||||
.color = clr,
|
||||
.font = fnt,
|
||||
.align = align,
|
||||
});
|
||||
r_shader_ptr(sh_prev);
|
||||
}
|
||||
|
||||
void spell_opening(Boss *b, int time) {
|
||||
complex x0 = VIEWPORT_W/2+I*VIEWPORT_H/3.5;
|
||||
float f = clamp((time-40.)/60.,0,1);
|
||||
complex x = x0 + (VIEWPORT_W+I*35 - x0) * f*(f+1)*0.5;
|
||||
int strw = stringwidth(b->current->name,_fonts.standard);
|
||||
int strw = text_width(get_font("standard"), b->current->name, 0);
|
||||
|
||||
bool cullcap_saved = r_capability_current(RCAP_CULL_FACE);
|
||||
r_disable(RCAP_CULL_FACE);
|
||||
|
@ -66,7 +72,7 @@ void spell_opening(Boss *b, int time) {
|
|||
float scale = f+1.*(1-f)*(1-f)*(1-f);
|
||||
r_mat_scale(scale,scale,1);
|
||||
r_mat_rotate_deg(360*f,1,1,0);
|
||||
draw_boss_text(AL_Right, strw/2*(1-f), 0, b->current->name, _fonts.standard, rgb(1, 1, 1));
|
||||
draw_boss_text(ALIGN_RIGHT, strw/2*(1-f), 0, b->current->name, "standard", rgb(1, 1, 1));
|
||||
r_mat_pop();
|
||||
|
||||
r_capability(RCAP_CULL_FACE, cullcap_saved);
|
||||
|
@ -276,7 +282,7 @@ static void ent_draw_boss(EntityInterface *ent) {
|
|||
if(boss->current->type == AT_Move && global.frames - boss->current->starttime > 0 && boss_attack_is_final(boss, boss->current))
|
||||
return;
|
||||
|
||||
draw_boss_text(AL_Left, 10, 20, boss->name, _fonts.standard, rgb(1, 1, 1));
|
||||
draw_boss_text(ALIGN_LEFT, 10, 20, boss->name, "standard", rgb(1, 1, 1));
|
||||
|
||||
if(!boss->current)
|
||||
return;
|
||||
|
@ -298,16 +304,19 @@ static void ent_draw_boss(EntityInterface *ent) {
|
|||
}
|
||||
|
||||
snprintf(buf, sizeof(buf), "%.2f", remaining);
|
||||
draw_boss_text(AL_Center, VIEWPORT_W - 24, 10, buf, _fonts.standard, textclr);
|
||||
draw_boss_text(ALIGN_CENTER, VIEWPORT_W - 24, 10, buf, "standard", textclr);
|
||||
|
||||
StageProgress *p = get_spellstage_progress(boss->current, NULL, false);
|
||||
if(p) {
|
||||
float a = clamp((global.frames - boss->current->starttime - 60) / 60.0, 0, 1);
|
||||
snprintf(buf, sizeof(buf), "%u / %u", p->num_cleared, p->num_played);
|
||||
draw_boss_text(AL_Right,
|
||||
VIEWPORT_W + stringwidth(buf, _fonts.small) * pow(1 - a, 2),
|
||||
35 + stringheight(buf, _fonts.small),
|
||||
buf, _fonts.small, rgba(1, 1, 1, a)
|
||||
|
||||
Font *font = get_font("small");
|
||||
|
||||
draw_boss_text(ALIGN_RIGHT,
|
||||
VIEWPORT_W + text_width(font, buf, 0) * pow(1 - a, 2),
|
||||
35 + text_height(font, buf, 0),
|
||||
buf, "small", rgba(1, 1, 1, a)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -236,11 +236,11 @@ static double entry_height(CreditsEntry *e, double *head, double *body) {
|
|||
if(*(e->data[0]) == '*') {
|
||||
total += *head = get_tex("yukkureimu")->h * CREDITS_YUKKURI_SCALE;
|
||||
} else {
|
||||
total += *head = font_line_spacing(_fonts.mainmenu);
|
||||
total += *head = font_get_lineskip(get_font("big"));
|
||||
}
|
||||
|
||||
if(e->lines > 1) {
|
||||
total += *body += (e->lines - 0.5) * font_line_spacing(_fonts.standard);
|
||||
total += *body += (e->lines - 0.5) * font_get_lineskip(get_font("standard"));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -323,9 +323,14 @@ static void credits_draw_entry(CreditsEntry *e) {
|
|||
r_mat_pop();
|
||||
r_mat_translate(0, yukkuri_spr->h * CREDITS_YUKKURI_SCALE * 0.5, 0);
|
||||
} else {
|
||||
Font *font = i ? _fonts.standard : _fonts.mainmenu;
|
||||
draw_text(AL_Center, 0, 0, e->data[i], font);
|
||||
r_mat_translate(0, font_line_spacing(font), 0);
|
||||
Font *font = get_font(i ? "standard" : "big");
|
||||
r_shader("text_default");
|
||||
text_draw(e->data[i], &(TextParams) {
|
||||
.align = ALIGN_CENTER,
|
||||
.font_ptr = font,
|
||||
});
|
||||
r_shader_standard();
|
||||
r_mat_translate(0, font_get_lineskip(font), 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -120,7 +120,11 @@ void draw_dialog(Dialog *dialog) {
|
|||
if(dialog->messages[dialog->pos].side == Right)
|
||||
r_color3(0.6,0.6,1);
|
||||
|
||||
draw_text_auto_wrapped(AL_Center, VIEWPORT_W/2, VIEWPORT_H-110, dialog->messages[dialog->pos].msg, VIEWPORT_W * 0.85, _fonts.standard);
|
||||
r_shader("text_default");
|
||||
text_draw_wrapped(dialog->messages[dialog->pos].msg, VIEWPORT_W * 0.85, &(TextParams) {
|
||||
.pos = { VIEWPORT_W/2, VIEWPORT_H-110 },
|
||||
.align = ALIGN_CENTER
|
||||
});
|
||||
|
||||
if(dialog->messages[dialog->pos].side == Right)
|
||||
r_color3(1,1,1);
|
||||
|
|
|
@ -161,7 +161,13 @@ static void ending_draw(Ending *e) {
|
|||
draw_sprite_p(SCREEN_W/2, SCREEN_H/2, e->entries[e->pos].sprite);
|
||||
}
|
||||
|
||||
draw_text_auto_wrapped(AL_Center, SCREEN_W/2, VIEWPORT_H*4/5, e->entries[e->pos].msg, SCREEN_W * 0.85, _fonts.standard);
|
||||
r_shader("text_default");
|
||||
text_draw_wrapped(e->entries[e->pos].msg, SCREEN_W * 0.85, &(TextParams) {
|
||||
.pos = { SCREEN_W/2, VIEWPORT_H*4/5 },
|
||||
.align = ALIGN_CENTER,
|
||||
});
|
||||
r_shader_standard();
|
||||
|
||||
r_color4(1,1,1,1);
|
||||
}
|
||||
|
||||
|
|
528
src/hashtable.c
528
src/hashtable.c
|
@ -8,530 +8,18 @@
|
|||
|
||||
#include "taisei.h"
|
||||
|
||||
#define HT_IMPL
|
||||
#include "hashtable.h"
|
||||
#include "list.h"
|
||||
#include "util.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <zlib.h>
|
||||
#include <stdio.h>
|
||||
#include <SDL_mutex.h>
|
||||
uint32_t (*htutil_hashfunc_string)(uint32_t crc, const char *str);
|
||||
|
||||
// #define HT_USE_MUTEX
|
||||
|
||||
typedef struct HashtableElement {
|
||||
LIST_INTERFACE(struct HashtableElement);
|
||||
void *data;
|
||||
void *key;
|
||||
hash_t hash;
|
||||
} HashtableElement;
|
||||
|
||||
struct Hashtable {
|
||||
HashtableElement **table;
|
||||
HTCmpFunc cmp_func;
|
||||
HTHashFunc hash_func;
|
||||
HTCopyFunc copy_func;
|
||||
HTFreeFunc free_func;
|
||||
size_t num_elements;
|
||||
size_t table_size;
|
||||
size_t hash_mask;
|
||||
|
||||
struct {
|
||||
SDL_mutex *mutex;
|
||||
SDL_cond *cond;
|
||||
uint readers;
|
||||
bool writing;
|
||||
} sync;
|
||||
};
|
||||
|
||||
typedef struct HashtableIterator {
|
||||
Hashtable *hashtable;
|
||||
size_t bucketnum;
|
||||
HashtableElement *elem;
|
||||
} HashtableIterator;
|
||||
|
||||
/*
|
||||
* Generic functions
|
||||
*/
|
||||
|
||||
Hashtable* hashtable_new(HTCmpFunc cmp_func, HTHashFunc hash_func, HTCopyFunc copy_func, HTFreeFunc free_func) {
|
||||
Hashtable *ht = malloc(sizeof(Hashtable));
|
||||
size_t size = HT_MIN_SIZE;
|
||||
|
||||
if(!cmp_func) {
|
||||
cmp_func = hashtable_cmpfunc_ptr;
|
||||
}
|
||||
|
||||
if(!copy_func) {
|
||||
copy_func = hashtable_copyfunc_ptr;
|
||||
}
|
||||
|
||||
ht->table = calloc(size, sizeof(HashtableElement*));
|
||||
ht->table_size = size;
|
||||
ht->hash_mask = size - 1;
|
||||
ht->num_elements = 0;
|
||||
ht->cmp_func = cmp_func;
|
||||
ht->hash_func = hash_func;
|
||||
ht->copy_func = copy_func;
|
||||
ht->free_func = free_func;
|
||||
|
||||
ht->sync.writing = false;
|
||||
ht->sync.readers = 0;
|
||||
ht->sync.mutex = SDL_CreateMutex();
|
||||
ht->sync.cond = SDL_CreateCond();
|
||||
|
||||
assert(ht->hash_func != NULL);
|
||||
|
||||
return ht;
|
||||
}
|
||||
|
||||
static void hashtable_begin_write(Hashtable *ht) {
|
||||
SDL_LockMutex(ht->sync.mutex);
|
||||
|
||||
while(ht->sync.writing || ht->sync.readers) {
|
||||
SDL_CondWait(ht->sync.cond, ht->sync.mutex);
|
||||
}
|
||||
|
||||
ht->sync.writing = true;
|
||||
SDL_UnlockMutex(ht->sync.mutex);
|
||||
}
|
||||
|
||||
static void hashtable_end_write(Hashtable *ht) {
|
||||
SDL_LockMutex(ht->sync.mutex);
|
||||
ht->sync.writing = false;
|
||||
SDL_CondBroadcast(ht->sync.cond);
|
||||
SDL_UnlockMutex(ht->sync.mutex);
|
||||
}
|
||||
|
||||
static void hashtable_begin_read(Hashtable *ht) {
|
||||
SDL_LockMutex(ht->sync.mutex);
|
||||
|
||||
while(ht->sync.writing) {
|
||||
SDL_CondWait(ht->sync.cond, ht->sync.mutex);
|
||||
}
|
||||
|
||||
++ht->sync.readers;
|
||||
SDL_UnlockMutex(ht->sync.mutex);
|
||||
}
|
||||
|
||||
static void hashtable_end_read(Hashtable *ht) {
|
||||
SDL_LockMutex(ht->sync.mutex);
|
||||
|
||||
if(!--ht->sync.readers) {
|
||||
SDL_CondSignal(ht->sync.cond);
|
||||
}
|
||||
|
||||
SDL_UnlockMutex(ht->sync.mutex);
|
||||
}
|
||||
|
||||
void hashtable_lock(Hashtable *ht) {
|
||||
assert(ht != NULL);
|
||||
hashtable_begin_read(ht);
|
||||
}
|
||||
|
||||
void hashtable_unlock(Hashtable *ht) {
|
||||
assert(ht != NULL);
|
||||
hashtable_end_read(ht);
|
||||
}
|
||||
|
||||
static void* hashtable_delete_callback(List **vlist, List *velem, void *vht) {
|
||||
Hashtable *ht = vht;
|
||||
HashtableElement *elem = (HashtableElement*)velem;
|
||||
|
||||
if(ht->free_func) {
|
||||
ht->free_func(elem->key);
|
||||
}
|
||||
|
||||
free(list_unlink(vlist, velem));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void hashtable_unset_all_internal(Hashtable *ht) {
|
||||
for(size_t i = 0; i < ht->table_size; ++i) {
|
||||
list_foreach((ht->table + i), hashtable_delete_callback, ht);
|
||||
}
|
||||
|
||||
ht->num_elements = 0;
|
||||
}
|
||||
|
||||
void hashtable_unset_all(Hashtable *ht) {
|
||||
assert(ht != NULL);
|
||||
hashtable_begin_write(ht);
|
||||
hashtable_unset_all_internal(ht);
|
||||
hashtable_end_write(ht);
|
||||
}
|
||||
|
||||
void hashtable_free(Hashtable *ht) {
|
||||
if(!ht) {
|
||||
return;
|
||||
}
|
||||
|
||||
hashtable_unset_all(ht);
|
||||
|
||||
SDL_DestroyCond(ht->sync.cond);
|
||||
SDL_DestroyMutex(ht->sync.mutex);
|
||||
|
||||
free(ht->table);
|
||||
free(ht);
|
||||
}
|
||||
|
||||
static HashtableElement* hashtable_get_internal(Hashtable *ht, void *key, hash_t hash) {
|
||||
HashtableElement *elems = ht->table[hash & ht->hash_mask];
|
||||
|
||||
for(HashtableElement *e = elems; e; e = e->next) {
|
||||
if(hash == e->hash && ht->cmp_func(key, e->key)) {
|
||||
return e;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void* hashtable_get(Hashtable *ht, void *key) {
|
||||
assert(ht != NULL);
|
||||
|
||||
hash_t hash = ht->hash_func(key);
|
||||
HashtableElement *elem;
|
||||
void *data;
|
||||
|
||||
hashtable_begin_read(ht);
|
||||
elem = hashtable_get_internal(ht, key, hash);
|
||||
data = elem ? elem->data : NULL;
|
||||
hashtable_end_read(ht);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
void* hashtable_get_unsafe(Hashtable *ht, void *key) {
|
||||
assert(ht != NULL);
|
||||
|
||||
hash_t hash = ht->hash_func(key);
|
||||
HashtableElement *elem;
|
||||
void *data;
|
||||
|
||||
elem = hashtable_get_internal(ht, key, hash);
|
||||
data = elem ? elem->data : NULL;
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
static bool hashtable_set_internal(Hashtable *ht, HashtableElement **table, size_t hash_mask, hash_t hash, void *key, void *data, void* (*datafunc)(void*), bool allow_overwrite, void **val) {
|
||||
size_t idx = hash & hash_mask;
|
||||
HashtableElement *elems = table[idx], *elem;
|
||||
void *result = NULL;
|
||||
|
||||
for(HashtableElement *e = elems; e; e = e->next) {
|
||||
if(hash == e->hash && ht->cmp_func(key, e->key)) {
|
||||
if(!allow_overwrite) {
|
||||
if(val != NULL) {
|
||||
*val = e->data;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
if(ht->free_func) {
|
||||
ht->free_func(e->key);
|
||||
}
|
||||
|
||||
free(list_unlink(&elems, e));
|
||||
ht->num_elements--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(datafunc != NULL) {
|
||||
data = datafunc(data);
|
||||
}
|
||||
|
||||
if(val != NULL) {
|
||||
*val = data;
|
||||
}
|
||||
|
||||
if(data) {
|
||||
elem = malloc(sizeof(HashtableElement));
|
||||
ht->copy_func(&elem->key, key);
|
||||
elem->hash = ht->hash_func(elem->key);
|
||||
elem->data = data;
|
||||
list_push(&elems, elem);
|
||||
ht->num_elements++;
|
||||
result = data;
|
||||
}
|
||||
|
||||
table[idx] = elems;
|
||||
return result;
|
||||
}
|
||||
|
||||
static void hashtable_resize(Hashtable *ht, size_t new_size) {
|
||||
assert(new_size != ht->table_size);
|
||||
HashtableElement **new_table = calloc(new_size, sizeof(HashtableElement*));
|
||||
size_t new_hash_mask = new_size - 1;
|
||||
|
||||
for(size_t i = 0; i < ht->table_size; ++i) {
|
||||
for(HashtableElement *e = ht->table[i]; e; e = e->next) {
|
||||
hashtable_set_internal(ht, new_table, new_hash_mask, e->hash, e->key, e->data, NULL, true, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
hashtable_unset_all_internal(ht);
|
||||
free(ht->table);
|
||||
|
||||
ht->table = new_table;
|
||||
|
||||
log_debug("Resized hashtable at %p: %"PRIuMAX" -> %"PRIuMAX"",
|
||||
(void*)ht, (uintmax_t)ht->table_size, (uintmax_t)new_size);
|
||||
// hashtable_print_stringkeys(ht);
|
||||
|
||||
ht->table_size = new_size;
|
||||
ht->hash_mask = new_hash_mask;
|
||||
}
|
||||
|
||||
void hashtable_set(Hashtable *ht, void *key, void *data) {
|
||||
assert(ht != NULL);
|
||||
|
||||
hash_t hash = ht->hash_func(key);
|
||||
|
||||
hashtable_begin_write(ht);
|
||||
hashtable_set_internal(ht, ht->table, ht->hash_mask, hash, key, data, NULL, true, NULL);
|
||||
|
||||
if(ht->num_elements == ht->table_size) {
|
||||
hashtable_resize(ht, ht->table_size * 2);
|
||||
}
|
||||
|
||||
hashtable_end_write(ht);
|
||||
}
|
||||
|
||||
bool hashtable_try_set(Hashtable *ht, void *key, void *data, void* (*datafunc)(void*), void **val) {
|
||||
assert(ht != NULL);
|
||||
|
||||
hash_t hash = ht->hash_func(key);
|
||||
|
||||
hashtable_begin_write(ht);
|
||||
bool result = hashtable_set_internal(ht, ht->table, ht->hash_mask, hash, key, data, datafunc, false, val);
|
||||
|
||||
if(ht->num_elements == ht->table_size) {
|
||||
hashtable_resize(ht, ht->table_size * 2);
|
||||
}
|
||||
|
||||
hashtable_end_write(ht);
|
||||
return result;
|
||||
}
|
||||
|
||||
void hashtable_unset(Hashtable *ht, void *key) {
|
||||
hashtable_set(ht, key, NULL);
|
||||
}
|
||||
|
||||
void hashtable_unset_deferred(Hashtable *ht, void *key, ListContainer **list) {
|
||||
assert(ht != NULL);
|
||||
ListContainer *c = list_push(list, list_wrap_container(NULL));
|
||||
ht->copy_func(&c->data, key);
|
||||
}
|
||||
|
||||
void hashtable_unset_deferred_now(Hashtable *ht, ListContainer **list) {
|
||||
ListContainer *next;
|
||||
assert(ht != NULL);
|
||||
|
||||
for(ListContainer *c = *list; c; c = next) {
|
||||
next = c->next;
|
||||
hashtable_unset(ht, c->data);
|
||||
|
||||
if(ht->free_func) {
|
||||
ht->free_func(c->data);
|
||||
}
|
||||
|
||||
free(list_unlink(list, c));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Iteration functions
|
||||
*/
|
||||
|
||||
void* hashtable_foreach(Hashtable *ht, HTIterCallback callback, void *arg) {
|
||||
assert(ht != NULL);
|
||||
|
||||
void *ret = NULL;
|
||||
|
||||
hashtable_begin_read(ht);
|
||||
|
||||
for(size_t i = 0; i < ht->table_size; ++i) {
|
||||
for(HashtableElement *e = ht->table[i]; e; e = e->next) {
|
||||
ret = callback(e->key, e->data, arg);
|
||||
if(ret) {
|
||||
hashtable_end_read(ht);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hashtable_end_read(ht);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
HashtableIterator* hashtable_iter(Hashtable *ht) {
|
||||
assert(ht != NULL);
|
||||
HashtableIterator *iter = malloc(sizeof(HashtableIterator));
|
||||
iter->hashtable = ht;
|
||||
iter->bucketnum = (size_t)-1;
|
||||
return iter;
|
||||
}
|
||||
|
||||
bool hashtable_iter_next(HashtableIterator *iter, void **out_key, void **out_data) {
|
||||
Hashtable *ht = iter->hashtable;
|
||||
|
||||
if(iter->bucketnum == (size_t)-1) {
|
||||
iter->bucketnum = 0;
|
||||
iter->elem = ht->table[iter->bucketnum];
|
||||
void htutil_init(void) {
|
||||
if(SDL_HasSSE42()) {
|
||||
log_info("Using SSE4.2-accelerated CRC32 as the string hash function");
|
||||
htutil_hashfunc_string = crc32str_sse42;
|
||||
} else {
|
||||
iter->elem = iter->elem->next;
|
||||
}
|
||||
|
||||
while(!iter->elem) {
|
||||
if(++iter->bucketnum == ht->table_size) {
|
||||
free(iter);
|
||||
return false;
|
||||
}
|
||||
|
||||
iter->elem = ht->table[iter->bucketnum];
|
||||
}
|
||||
|
||||
if(out_key) {
|
||||
*out_key = iter->elem->key;
|
||||
}
|
||||
|
||||
if(out_data) {
|
||||
*out_data = iter->elem->data;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Convenience functions for hashtables with string keys
|
||||
*/
|
||||
|
||||
bool hashtable_cmpfunc_string(void *str1, void *str2) {
|
||||
return !strcmp((const char*)str1, (const char*)(str2));
|
||||
}
|
||||
|
||||
hash_t hashtable_hashfunc_string(void *vstr) {
|
||||
return crc32str(0, (const char*)vstr);
|
||||
}
|
||||
|
||||
hash_t hashtable_hashfunc_string_sse42(void *vstr) {
|
||||
return crc32str_sse42(0, (const char*)vstr);
|
||||
}
|
||||
|
||||
void hashtable_copyfunc_string(void **dst, void *src) {
|
||||
*dst = malloc(strlen((char*)src) + 1);
|
||||
strcpy(*dst, src);
|
||||
}
|
||||
|
||||
// #define hashtable_freefunc_string free
|
||||
|
||||
Hashtable* hashtable_new_stringkeys(void) {
|
||||
return hashtable_new(
|
||||
hashtable_cmpfunc_string,
|
||||
SDL_HasSSE42() ? hashtable_hashfunc_string_sse42 : hashtable_hashfunc_string,
|
||||
hashtable_copyfunc_string,
|
||||
hashtable_freefunc_string
|
||||
);
|
||||
}
|
||||
|
||||
void* hashtable_get_string(Hashtable *ht, const char *key) {
|
||||
return hashtable_get(ht, (void*)key);
|
||||
}
|
||||
|
||||
void hashtable_set_string(Hashtable *ht, const char *key, void *data) {
|
||||
hashtable_set(ht, (void*)key, data);
|
||||
}
|
||||
|
||||
void hashtable_unset_string(Hashtable *ht, const char *key) {
|
||||
hashtable_unset(ht, (void*)key);
|
||||
}
|
||||
|
||||
/*
|
||||
* Misc convenience functions
|
||||
*/
|
||||
|
||||
void* hashtable_iter_free_data(void *key, void *data, void *arg) {
|
||||
free(data);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool hashtable_cmpfunc_ptr(void *p1, void *p2) {
|
||||
return p1 == p2;
|
||||
}
|
||||
|
||||
void hashtable_copyfunc_ptr(void **dst, void *src) {
|
||||
*dst = src;
|
||||
}
|
||||
|
||||
hash_t hashtable_hashfunc_ptr(void *val) {
|
||||
hash_t x = (uintptr_t)val & ((1u << 31u) - 1u);
|
||||
x = ((x >> 16) ^ x) * 0x45d9f3b;
|
||||
x = ((x >> 16) ^ x) * 0x45d9f3b;
|
||||
x = (x >> 16) ^ x;
|
||||
return x;
|
||||
}
|
||||
|
||||
/*
|
||||
* Diagnostic functions
|
||||
*/
|
||||
|
||||
void hashtable_get_stats(Hashtable *ht, HashtableStats *stats) {
|
||||
assert(ht != NULL);
|
||||
assert(stats != NULL);
|
||||
|
||||
memset(stats, 0, sizeof(HashtableStats));
|
||||
|
||||
for(size_t i = 0; i < ht->table_size; ++i) {
|
||||
int elems = 0;
|
||||
|
||||
for(HashtableElement *e = ht->table[i]; e; e = e->next) {
|
||||
++elems;
|
||||
++stats->num_elements;
|
||||
}
|
||||
|
||||
if(elems > 1) {
|
||||
stats->collisions += elems - 1;
|
||||
}
|
||||
|
||||
if(!elems) {
|
||||
++stats->free_buckets;
|
||||
} else if(elems > stats->max_per_bucket) {
|
||||
stats->max_per_bucket = elems;
|
||||
}
|
||||
log_info("Using software fallback CRC32 as the string hash function");
|
||||
htutil_hashfunc_string = crc32str;
|
||||
}
|
||||
}
|
||||
|
||||
size_t hashtable_get_approx_overhead(Hashtable *ht) {
|
||||
size_t o = sizeof(Hashtable) + sizeof(HashtableElement*) * ht->table_size;
|
||||
|
||||
for(HashtableIterator *i = hashtable_iter(ht); hashtable_iter_next(i, NULL, NULL);) {
|
||||
o += sizeof(HashtableElement);
|
||||
}
|
||||
|
||||
return o;
|
||||
}
|
||||
|
||||
void hashtable_print_stringkeys(Hashtable *ht) {
|
||||
HashtableStats stats;
|
||||
hashtable_get_stats(ht, &stats);
|
||||
|
||||
log_debug("------ %p:", (void*)ht);
|
||||
for(size_t i = 0; i < ht->table_size; ++i) {
|
||||
log_debug("[bucket %"PRIuMAX"] %p", (uintmax_t)i, (void*)ht->table[i]);
|
||||
|
||||
for(HashtableElement *e = ht->table[i]; e; e = e->next) {
|
||||
log_debug(" -- %s (%"PRIuMAX"): %p", (char*)e->key, (uintmax_t)e->hash, e->data);
|
||||
}
|
||||
}
|
||||
|
||||
log_debug(
|
||||
"%i total elements, %i unused buckets, %i collisions, max %i elems per bucket, %lu approx overhead",
|
||||
stats.num_elements, stats.free_buckets, stats.collisions, stats.max_per_bucket,
|
||||
(ulong)hashtable_get_approx_overhead(ht)
|
||||
);
|
||||
}
|
||||
|
|
102
src/hashtable.h
102
src/hashtable.h
|
@ -9,67 +9,55 @@
|
|||
#pragma once
|
||||
#include "taisei.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "list.h"
|
||||
|
||||
#define HT_MIN_SIZE 16
|
||||
|
||||
typedef struct Hashtable Hashtable;
|
||||
typedef struct HashtableIterator HashtableIterator;
|
||||
typedef struct HashtableStats HashtableStats;
|
||||
/*
|
||||
* hash_t
|
||||
*
|
||||
* Type of the hash values.
|
||||
*/
|
||||
typedef uint32_t hash_t;
|
||||
|
||||
struct HashtableStats {
|
||||
uint free_buckets;
|
||||
uint collisions;
|
||||
uint max_per_bucket;
|
||||
uint num_elements;
|
||||
};
|
||||
/*
|
||||
* htutil_init
|
||||
*
|
||||
* One-time setup function.
|
||||
*/
|
||||
void htutil_init(void);
|
||||
|
||||
typedef bool (*HTCmpFunc)(void *key1, void *key2);
|
||||
typedef hash_t (*HTHashFunc)(void *key);
|
||||
typedef void (*HTCopyFunc)(void **dstkey, void *srckey);
|
||||
typedef void (*HTFreeFunc)(void *key);
|
||||
typedef void* (*HTIterCallback)(void *key, void *data, void *arg);
|
||||
/*
|
||||
* htutil_hashfunc_uint32
|
||||
*
|
||||
* Hash function for 32-bit integers
|
||||
* NOTE: assuming hash_t stays uint32_t, this function has no collisions.
|
||||
*/
|
||||
static inline attr_must_inline hash_t htutil_hashfunc_uint32(uint32_t x) {
|
||||
x = ((x >> 16) ^ x) * 0x45d9f3b;
|
||||
x = ((x >> 16) ^ x) * 0x45d9f3b;
|
||||
x = (x >> 16) ^ x;
|
||||
return (hash_t)x;
|
||||
}
|
||||
|
||||
Hashtable* hashtable_new(HTCmpFunc cmp_func, HTHashFunc hash_func, HTCopyFunc copy_func, HTFreeFunc free_func);
|
||||
void hashtable_free(Hashtable *ht);
|
||||
void* hashtable_get(Hashtable *ht, void *key) attr_hot;
|
||||
void* hashtable_get_unsafe(Hashtable *ht, void *key) attr_hot;
|
||||
void hashtable_set(Hashtable *ht, void *key, void *data);
|
||||
bool hashtable_try_set(Hashtable *ht, void *key, void *data, void* (*datafunc)(void*), void **val);
|
||||
void hashtable_unset(Hashtable *ht, void *key);
|
||||
void hashtable_unset_deferred(Hashtable *ht, void *key, ListContainer **list);
|
||||
void hashtable_unset_deferred_now(Hashtable *ht, ListContainer **list);
|
||||
void hashtable_unset_all(Hashtable *ht);
|
||||
/*
|
||||
* htutil_hashfunc_uint64
|
||||
*
|
||||
* Hash function for 64-bit integers.
|
||||
*/
|
||||
static inline attr_must_inline hash_t htutil_hashfunc_uint64(uint64_t x) {
|
||||
x = (x ^ (x >> 30)) * UINT64_C(0xbf58476d1ce4e5b9);
|
||||
x = (x ^ (x >> 27)) * UINT64_C(0x94d049bb133111eb);
|
||||
x = x ^ (x >> 31);
|
||||
return (hash_t)x;
|
||||
}
|
||||
|
||||
void* hashtable_foreach(Hashtable *ht, HTIterCallback callback, void *arg);
|
||||
/*
|
||||
* htutil_hashfunc_string
|
||||
*
|
||||
* Hash function for null-terminated strings.
|
||||
*/
|
||||
extern uint32_t (*htutil_hashfunc_string)(uint32_t crc, const char *str);
|
||||
|
||||
// THIS IS NOT THREAD-SAFE. You have to use hashtable_lock/unlock to make it so.
|
||||
HashtableIterator* hashtable_iter(Hashtable *ht);
|
||||
bool hashtable_iter_next(HashtableIterator *iter, void **out_key, void **out_data);
|
||||
// Import public declarations for the predefined hashtable types.
|
||||
#define HT_DECL
|
||||
#include "hashtable_predefs.inc.h"
|
||||
|
||||
bool hashtable_cmpfunc_string(void *str1, void *str2) attr_hot;
|
||||
hash_t hashtable_hashfunc_string(void *vstr) attr_hot;
|
||||
hash_t hashtable_hashfunc_string_sse42(void *vstr) attr_hot;
|
||||
void hashtable_copyfunc_string(void **dst, void *src);
|
||||
#define hashtable_freefunc_string free
|
||||
Hashtable* hashtable_new_stringkeys(void);
|
||||
|
||||
void* hashtable_get_string(Hashtable *ht, const char *key);
|
||||
void hashtable_set_string(Hashtable *ht, const char *key, void *data);
|
||||
void hashtable_unset_string(Hashtable *ht, const char *key);
|
||||
void hashtable_unset_deferred_string(Hashtable *ht, const char *key);
|
||||
|
||||
void* hashtable_iter_free_data(void *key, void *data, void *arg);
|
||||
bool hashtable_cmpfunc_ptr(void *p1, void *p2);
|
||||
void hashtable_copyfunc_ptr(void **dst, void *src);
|
||||
hash_t hashtable_hashfunc_ptr(void *val);
|
||||
|
||||
void hashtable_print_stringkeys(Hashtable *ht);
|
||||
size_t hashtable_get_approx_overhead(Hashtable *ht);
|
||||
void hashtable_get_stats(Hashtable *ht, HashtableStats *stats);
|
||||
|
||||
void hashtable_lock(Hashtable *ht);
|
||||
void hashtable_unlock(Hashtable *ht);
|
||||
// NOTE: For the hashtable API, see hashtable.inc.h
|
||||
// NOTE: For type-generic wrappers around the API, see hashtable_predefs.inc.h
|
||||
|
|
1045
src/hashtable.inc.h
Normal file
1045
src/hashtable.inc.h
Normal file
File diff suppressed because it is too large
Load diff
10
src/hashtable_incproxy.inc.h
Normal file
10
src/hashtable_incproxy.inc.h
Normal file
|
@ -0,0 +1,10 @@
|
|||
|
||||
#ifdef _HT_IMPL
|
||||
#define HT_IMPL
|
||||
#endif
|
||||
|
||||
#ifdef _HT_DECL
|
||||
#define HT_DECL
|
||||
#endif
|
||||
|
||||
#include "hashtable.inc.h"
|
191
src/hashtable_predefs.inc.h
Normal file
191
src/hashtable_predefs.inc.h
Normal file
|
@ -0,0 +1,191 @@
|
|||
|
||||
// Sets up the predefined hashtable types.
|
||||
// Add more as necessary.
|
||||
|
||||
#ifdef HT_IMPL
|
||||
#define _HT_IMPL
|
||||
#endif
|
||||
|
||||
#ifdef HT_DECL
|
||||
#define _HT_DECL
|
||||
#endif
|
||||
|
||||
/*
|
||||
* str2ptr
|
||||
*
|
||||
* Maps strings to void pointers.
|
||||
*/
|
||||
#define HT_SUFFIX str2ptr
|
||||
#define HT_KEY_TYPE char*
|
||||
#define HT_VALUE_TYPE void*
|
||||
#define HT_FUNC_FREE_KEY(key) free(key)
|
||||
#define HT_FUNC_KEYS_EQUAL(key1, key2) (!strcmp(key1, key2))
|
||||
#define HT_FUNC_HASH_KEY(key) htutil_hashfunc_string(0, key)
|
||||
#define HT_FUNC_COPY_KEY(dst, src) (*(dst) = strdup(src))
|
||||
#define HT_KEY_CONST
|
||||
#define HT_VALUE_CONST
|
||||
#include "hashtable_incproxy.inc.h"
|
||||
|
||||
/*
|
||||
* str2ptr_ts
|
||||
*
|
||||
* Maps strings to void pointers (thread-safe).
|
||||
*/
|
||||
#define HT_SUFFIX str2ptr_ts
|
||||
#define HT_KEY_TYPE char*
|
||||
#define HT_VALUE_TYPE void*
|
||||
#define HT_FUNC_FREE_KEY(key) free(key)
|
||||
#define HT_FUNC_KEYS_EQUAL(key1, key2) (!strcmp(key1, key2))
|
||||
#define HT_FUNC_HASH_KEY(key) htutil_hashfunc_string(0, key)
|
||||
#define HT_FUNC_COPY_KEY(dst, src) (*(dst) = strdup(src))
|
||||
#define HT_KEY_CONST
|
||||
#define HT_VALUE_CONST
|
||||
#define HT_THREAD_SAFE
|
||||
#include "hashtable_incproxy.inc.h"
|
||||
|
||||
/*
|
||||
* str2int
|
||||
*
|
||||
* Maps strings to 64-bit integers.
|
||||
*/
|
||||
#define HT_SUFFIX str2int
|
||||
#define HT_KEY_TYPE char*
|
||||
#define HT_VALUE_TYPE int64_t
|
||||
#define HT_FUNC_FREE_KEY(key) free(key)
|
||||
#define HT_FUNC_KEYS_EQUAL(key1, key2) (!strcmp(key1, key2))
|
||||
#define HT_FUNC_HASH_KEY(key) htutil_hashfunc_string(0, key)
|
||||
#define HT_FUNC_COPY_KEY(dst, src) (*(dst) = strdup(src))
|
||||
#define HT_KEY_CONST
|
||||
#include "hashtable_incproxy.inc.h"
|
||||
|
||||
/*
|
||||
* str2int_ts
|
||||
*
|
||||
* Maps strings to 64-bit integers.
|
||||
*/
|
||||
#define HT_SUFFIX str2int_ts
|
||||
#define HT_KEY_TYPE char*
|
||||
#define HT_VALUE_TYPE int64_t
|
||||
#define HT_FUNC_FREE_KEY(key) free(key)
|
||||
#define HT_FUNC_KEYS_EQUAL(key1, key2) (!strcmp(key1, key2))
|
||||
#define HT_FUNC_HASH_KEY(key) htutil_hashfunc_string(0, key)
|
||||
#define HT_FUNC_COPY_KEY(dst, src) (*(dst) = strdup(src))
|
||||
#define HT_KEY_CONST
|
||||
#define HT_THREAD_SAFE
|
||||
#include "hashtable_incproxy.inc.h"
|
||||
|
||||
/*
|
||||
* int2int
|
||||
*
|
||||
* Maps 64-bit integers to 64-bit integers.
|
||||
*/
|
||||
#define HT_SUFFIX int2int
|
||||
#define HT_KEY_TYPE int64_t
|
||||
#define HT_VALUE_TYPE int64_t
|
||||
#define HT_FUNC_HASH_KEY(key) htutil_hashfunc_uint64((uint64_t)(key))
|
||||
#include "hashtable_incproxy.inc.h"
|
||||
|
||||
/*
|
||||
* int2int_ts
|
||||
*
|
||||
* Maps 64-bit integers to 64-bit integers (thread-safe).
|
||||
*/
|
||||
#define HT_SUFFIX int2int_ts
|
||||
#define HT_KEY_TYPE int64_t
|
||||
#define HT_VALUE_TYPE int64_t
|
||||
#define HT_FUNC_HASH_KEY(key) htutil_hashfunc_uint64((uint64_t)(key))
|
||||
#define HT_THREAD_SAFE
|
||||
#include "hashtable_incproxy.inc.h"
|
||||
|
||||
/*
|
||||
* C11 generic selection witchcraft.
|
||||