From 8a717a2b2ebfe7f47004202df4b393f488b1aceb Mon Sep 17 00:00:00 2001 From: NishiOwO Date: Wed, 7 Jan 2026 04:24:22 +0900 Subject: [PATCH] complete rewrite --- .gitmodules | 3 - CMakeLists.txt | 40 +- client/gl.c | 298 --- client/gl_font.c | 60 - client/gl_main.c | 67 - client/gl_shader.c | 108 - client/gl_util.c | 34 - client/main.c | 103 - client/net.c | 107 - client/ui.c | 125 -- engine/CMakeLists.txt | 11 + engine/README.txt | 3 + .../external}/glad/CMakeLists.txt | 0 .../external}/glad/include/KHR/khrplatform.h | 0 .../external}/glad/include/glad/glad.h | 0 {external => engine/external}/glad/src/glad.c | 0 engine/net/.clang-format | 20 + engine/net/CMakeLists.txt | 21 + engine/net/LICENSE | 24 + engine/net/Makefile | 33 + engine/net/include/fishsoup.h | 86 + engine/net/src/client.c | 93 + engine/net/src/server.c | 199 ++ engine/net/src/socket.c | 200 ++ {external/stb => engine/net/src}/stb_ds.c | 2 +- engine/net/src/stb_ds.h | 1895 +++++++++++++++++ engine/net/testclient.c | 11 + engine/net/testserver.c | 18 + engine/tools/CMakeLists.txt | 9 + .../tools/gb_dumpfont/gb_dumpfont.c | 0 .../tools/gb_dumpfont}/stb_image_write.c | 0 external/libfishsoup | 1 - external/stb/stb_image.c | 2 - include/af_client.h | 77 - include/af_common.h | 59 - include/af_config.h.in | 6 - include/af_server.h | 8 - server/main.c | 13 - server/net.c | 119 -- src/log.c | 14 - src/version.c | 11 - 41 files changed, 2625 insertions(+), 1255 deletions(-) delete mode 100644 client/gl.c delete mode 100644 client/gl_font.c delete mode 100644 client/gl_main.c delete mode 100644 client/gl_shader.c delete mode 100644 client/gl_util.c delete mode 100644 client/main.c delete mode 100644 client/net.c delete mode 100644 client/ui.c create mode 100644 engine/CMakeLists.txt create mode 100644 engine/README.txt rename {external => engine/external}/glad/CMakeLists.txt (100%) rename {external => engine/external}/glad/include/KHR/khrplatform.h (100%) rename {external => engine/external}/glad/include/glad/glad.h (100%) rename {external => engine/external}/glad/src/glad.c (100%) create mode 100644 engine/net/.clang-format create mode 100644 engine/net/CMakeLists.txt create mode 100644 engine/net/LICENSE create mode 100644 engine/net/Makefile create mode 100644 engine/net/include/fishsoup.h create mode 100644 engine/net/src/client.c create mode 100644 engine/net/src/server.c create mode 100644 engine/net/src/socket.c rename {external/stb => engine/net/src}/stb_ds.c (60%) create mode 100644 engine/net/src/stb_ds.h create mode 100644 engine/net/testclient.c create mode 100644 engine/net/testserver.c create mode 100644 engine/tools/CMakeLists.txt rename tools/af_dumpfont.c => engine/tools/gb_dumpfont/gb_dumpfont.c (100%) rename {external/stb => engine/tools/gb_dumpfont}/stb_image_write.c (100%) delete mode 160000 external/libfishsoup delete mode 100644 external/stb/stb_image.c delete mode 100644 include/af_client.h delete mode 100644 include/af_common.h delete mode 100644 include/af_config.h.in delete mode 100644 include/af_server.h delete mode 100644 server/main.c delete mode 100644 server/net.c delete mode 100644 src/log.c delete mode 100644 src/version.c diff --git a/.gitmodules b/.gitmodules index 3e75478..cea3040 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,3 @@ -[submodule "external/libfishsoup"] - path = external/libfishsoup - url = ../libfishsoup [submodule "external/milsko"] path = external/milsko url = https://gitea.nishi.boats/pyrite-dev/milsko diff --git a/CMakeLists.txt b/CMakeLists.txt index f023abd..6578505 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,47 +5,9 @@ include(GNUInstallDirs) set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_FULL_LIBDIR}") set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) -add_subdirectory(external/libfishsoup) add_subdirectory(external/milsko) -add_subdirectory(external/glad) -file(GLOB SERVER_SRC server/*.c src/*.c) -file(GLOB CLIENT_SRC client/*.c src/*.c) - -add_executable(af_server ${SERVER_SRC} external/stb/stb_ds.c) -add_executable(af_client ${CLIENT_SRC} external/stb/stb_image.c) -add_executable(af_dumpfont tools/af_dumpfont.c external/stb/stb_image_write.c) - -target_include_directories(af_server PRIVATE include external/stb ${CMAKE_CURRENT_BINARY_DIR}) -target_include_directories(af_client PRIVATE include external/stb ${CMAKE_CURRENT_BINARY_DIR}) -target_include_directories(af_dumpfont PRIVATE include external/stb ${CMAKE_CURRENT_BINARY_DIR}) - -target_compile_definitions(af_server PRIVATE AF_SERVER) -target_compile_definitions(af_client PRIVATE AF_CLIENT) - -target_link_libraries(af_server PRIVATE fishsoup) -target_link_libraries(af_client PRIVATE fishsoup Mw glad) - -if(CMAKE_SYSTEM_NAME STREQUAL "Windows") - target_link_libraries(af_client PRIVATE glu32) -else() - find_package(PkgConfig) - pkg_check_modules(GLU REQUIRED glu) - target_compile_options(af_client PRIVATE ${GLU_CFLAGS_OTHER}) - target_include_directories(af_client PRIVATE ${GLU_INCLUDE_DIRS}) - target_link_options(af_client PRIVATE ${GLU_LDFLAGS_OTHER}) - target_link_directories(af_client PRIVATE ${GLU_LIBRARY_DIRS}) - target_link_libraries(af_client PRIVATE ${GLU_LIBRARIES} m) -endif() - -configure_file(include/af_config.h.in af_config.h) - -install( - TARGETS af_server af_client af_dumpfont - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} -) +add_subdirectory(engine) install( FILES resource/logo.png resource/font.png resource/shadow.vs resource/shadow.fs diff --git a/client/gl.c b/client/gl.c deleted file mode 100644 index 6c15cd2..0000000 --- a/client/gl.c +++ /dev/null @@ -1,298 +0,0 @@ -#include - -#define SHADOW_WIDTH 1024 -#define SHADOW_HEIGHT 1024 - -double gl_cam_lr = 0; -double gl_cam_ud = 0; -double gl_cam_x; -double gl_cam_y; -double gl_cam_z; - -int gl_scene = AF_SCENE_MAIN; - -GLuint gl_shadow; - -static GLuint shadow_texture; - -gl_scene_t gl_scenes[] = { - {gl_main_changed, gl_main_init, gl_main_draw, gl_main_after}, /**/ - {NULL, NULL, NULL, NULL} /**/ -}; - -static char gl_status[256]; - -void gl_resize(int width, int height) { - glViewport(0, 0, width, height); -} - -void gl_init(void) { - GLfloat lightamb[] = {0.25, 0.25, 0.25, 1.0}; - GLfloat lightcol[] = {1.0, 1.0, 1.0, 1.0}; - - glEnable(GL_DEPTH_TEST); - glEnable(GL_NORMALIZE); - glEnable(GL_BLEND); - glEnable(GL_COLOR_MATERIAL); - glEnable(GL_LIGHT0); - glEnable(GL_LIGHTING); - glEnable(GL_CULL_FACE); - - glLightfv(GL_LIGHT0, GL_AMBIENT, lightamb); - glLightfv(GL_LIGHT0, GL_DIFFUSE, lightcol); - glLightfv(GL_LIGHT0, GL_SPECULAR, lightcol); - - glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER, GL_TRUE); - - glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE); - - glPixelStorei(GL_UNPACK_ALIGNMENT, 4); - - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - - gl_font_init(); - - gl_status[0] = 0; - - glGenTextures(1, &shadow_texture); - glBindTexture(GL_TEXTURE_2D, shadow_texture); - glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, SHADOW_WIDTH, SHADOW_HEIGHT, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, 0); - - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); - - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE); - - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL); - - glTexParameteri(GL_TEXTURE_2D, GL_DEPTH_TEXTURE_MODE, GL_LUMINANCE); - - glBindTexture(GL_TEXTURE_2D, 0); - - if(gl_shader(&gl_shadow, DATAROOTDIR "/assaultfish/shadow.vs", DATAROOTDIR "/assaultfish/shadow.fs")) { - glUseProgram(gl_shadow); - glUniform1i(glGetUniformLocation(gl_shadow, "texture"), 1); - glUniform1i(glGetUniformLocation(gl_shadow, "texture_in"), 0); - } -} - -void gl_cube(double x, double y, double z, double xs, double ys, double zs, double xr, double yr, double zr) { - int i; - glPushMatrix(); - glTranslatef(x, y, z); - glScalef(xs, ys, zs); - glRotatef(xr, 1, 0, 0); - glRotatef(yr, 0, 1, 0); - glRotatef(zr, 0, 0, 1); - for(i = 0; i < 6; i++) { - glBegin(GL_QUADS); - glNormal3f(0, 0, 1); - glTexCoord2f(0, 0); - glVertex3f(-0.5, 0.5, 0.5); - glTexCoord2f(0, 1); - glVertex3f(-0.5, -0.5, 0.5); - glTexCoord2f(1, 1); - glVertex3f(0.5, -0.5, 0.5); - glTexCoord2f(1, 0); - glVertex3f(0.5, 0.5, 0.5); - glEnd(); - - if(i == 3) { - glRotatef(90, 1, 0, 0); - } else if(i == 4) { - glRotatef(180, 1, 0, 0); - } else { - glRotatef(90, 0, 1, 0); - } - } - glPopMatrix(); -} - -static void gl_common(void) { - char buf1[128]; - char buf2[128]; - int l; - gl_util_begin_2d(); - - glColor4f(0, 0, 0, 0.5); - sprintf(buf1, "AssaultFish %s", AF_VERSION); - sprintf(buf2, "%s", AF_COPYRIGHT); - - l = gl_font_width(buf1); - if(l < gl_font_width(buf2)) l = gl_font_width(buf2); - - glBegin(GL_QUADS); - glVertex2f(MwGetInteger(opengl, MwNwidth) - l, MwGetInteger(opengl, MwNheight) - gl_font_height(buf1) - gl_font_height(buf2)); - glVertex2f(MwGetInteger(opengl, MwNwidth) - l, MwGetInteger(opengl, MwNheight)); - glVertex2f(MwGetInteger(opengl, MwNwidth), MwGetInteger(opengl, MwNheight)); - glVertex2f(MwGetInteger(opengl, MwNwidth), MwGetInteger(opengl, MwNheight) - gl_font_height(buf1) - gl_font_height(buf2)); - glEnd(); - - glColor3f(1, 1, 1); - - gl_font_text(buf1, MwGetInteger(opengl, MwNwidth) - gl_font_width(buf1), MwGetInteger(opengl, MwNheight) - gl_font_height(buf1) - gl_font_height(buf2), 1); - - gl_font_text(buf2, MwGetInteger(opengl, MwNwidth) - gl_font_width(buf2), MwGetInteger(opengl, MwNheight) - gl_font_height(buf2), 1); - - gl_font_text(gl_status, 0, 0, 1); - - gl_util_end_2d(); -} - -static void scene(void) { - if(gl_scenes[gl_scene].draw != NULL) gl_scenes[gl_scene].draw(); -} - -void gl_render(void) { - GLfloat lightpos[4] = {2.5, 5, -2.5, 1}; - GLint viewport[4]; - GLdouble modelview[16]; - GLdouble modelview2[16]; - - if(gl_scenes[gl_scene].init != NULL) gl_scenes[gl_scene].init(); - - glClear(GL_DEPTH_BUFFER_BIT); - - glGetIntegerv(GL_VIEWPORT, &viewport[0]); - - glViewport(0, 0, SHADOW_WIDTH, SHADOW_HEIGHT); - - glMatrixMode(GL_PROJECTION); - glLoadIdentity(); - - glMatrixMode(GL_MODELVIEW); - glLoadIdentity(); - gluPerspective(60, (double)SHADOW_WIDTH / SHADOW_HEIGHT, 0.01, 100.0); - gluLookAt(lightpos[0], lightpos[1], lightpos[2], 0, 0, 0, 0, 1, 0); - - glGetDoublev(GL_MODELVIEW_MATRIX, &modelview[0]); - - glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); - - glDisable(GL_LIGHTING); - - glCullFace(GL_FRONT); - - scene(); - - glActiveTexture(GL_TEXTURE1); - glBindTexture(GL_TEXTURE_2D, shadow_texture); - glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, SHADOW_WIDTH, SHADOW_HEIGHT); - glBindTexture(GL_TEXTURE_2D, 0); - glActiveTexture(GL_TEXTURE0); - - glViewport(viewport[0], viewport[1], viewport[2], viewport[3]); - glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); - glEnable(GL_LIGHTING); - glCullFace(GL_BACK); - - glMatrixMode(GL_PROJECTION); - glLoadIdentity(); - gluPerspective(60, (double)MwGetInteger(opengl, MwNwidth) / MwGetInteger(opengl, MwNheight), 0.01, 100.0); - - glMatrixMode(GL_MODELVIEW); - glLoadIdentity(); - gluLookAt(gl_cam_x, gl_cam_y, gl_cam_z, gl_cam_x + cos(gl_cam_lr / 180.0 * M_PI), gl_cam_y + sin(gl_cam_ud / 180.0 * M_PI), gl_cam_z + sin(gl_cam_lr / 180.0 * M_PI), 0, 1, 0); - glGetDoublev(GL_MODELVIEW_MATRIX, &modelview2[0]); - - glMatrixMode(GL_TEXTURE); - glLoadIdentity(); - - glTranslated(0.5, 0.5, 0.5); - glScaled(0.5, 0.5, 0.5); - glMultMatrixd(modelview); - glMultMatrixd(modelview2); - - glMatrixMode(GL_MODELVIEW); - - glActiveTexture(GL_TEXTURE1); - glBindTexture(GL_TEXTURE_2D, shadow_texture); - glEnable(GL_TEXTURE_2D); - glActiveTexture(GL_TEXTURE0); - - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - - glLightfv(GL_LIGHT0, GL_POSITION, lightpos); - - scene(); - - glActiveTexture(GL_TEXTURE1); - glDisable(GL_TEXTURE_2D); - glBindTexture(GL_TEXTURE_2D, 0); - glActiveTexture(GL_TEXTURE0); - - glMatrixMode(GL_TEXTURE); - glLoadIdentity(); - - glMatrixMode(GL_MODELVIEW); - - gl_common(); - - if(gl_scenes[gl_scene].after != NULL) gl_scenes[gl_scene].after(); -} - -void gl_scene_change(void) { - if(gl_scenes[gl_scene].changed != NULL) gl_scenes[gl_scene].changed(); - - gl_status[0] = 0; -} - -void gl_set_status(const char* status) { - strcpy(gl_status, status); -} - -static void cross(double* out, double v00, double v01, double v02, double v10, double v11, double v12) { - out[0] = v01 * v12 - v02 * v11; - out[1] = v02 * v10 - v00 * v12; - out[2] = v00 * v11 - v01 * v10; -} - -/* - * v0-v2 - * | / - * v1/ - */ -void gl_calc_normal3(double* out, double v00, double v01, double v02, double v10, double v11, double v12, double v20, double v21, double v22) { - double v[3]; - double l = 0; - int i; - - cross(&v[0], /**/ - v10 - v00, v11 - v01, v12 - v02, /**/ - v20 - v00, v21 - v01, v22 - v02 /**/ - ); - - l = sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]); - - for(i = 0; i < 3; i++) { - out[i] = v[i] / l; - } -} - -/* - * v0-v3 - * | | - * v1-v2 - */ -void gl_calc_normal4(double* out, double v00, double v01, double v02, double v10, double v11, double v12, double v20, double v21, double v22, double v30, double v31, double v32) { - double v0[3]; - double v1[3]; - int i; - double l = 0; - - gl_calc_normal3(&v0[0], v00, v01, v02, v10, v11, v12, v30, v31, v32); - gl_calc_normal3(&v1[0], v10, v11, v12, v20, v21, v22, v30, v31, v32); - - for(i = 0; i < 3; i++) { - out[i] = v0[i] + v1[i]; - } - - l = sqrt(out[0] * out[0] + out[1] * out[1] + out[2] * out[2]); - - for(i = 0; i < 3; i++) { - out[i] = out[i] / l; - } -} diff --git a/client/gl_font.c b/client/gl_font.c deleted file mode 100644 index 72231fb..0000000 --- a/client/gl_font.c +++ /dev/null @@ -1,60 +0,0 @@ -#include - -#include - -static GLuint font_atlas; -static int font_width = 0, font_height = 0; - -void gl_font_init(void) { - unsigned int ch; - unsigned char* data = stbi_load(DATAROOTDIR "/assaultfish/font.png", &font_width, &font_height, &ch, 4); - - glGenTextures(1, &font_atlas); - glBindTexture(GL_TEXTURE_2D, font_atlas); - if(data != NULL) { - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, font_width, font_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); - free(data); - } - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glBindTexture(GL_TEXTURE_2D, 0); -} - -void gl_font_text(const char* text, int x, int y, double scale) { - int i; - int gw = font_width / 16 * scale, gh = font_height / 16 * scale; - int is = glIsEnabled(GL_TEXTURE_2D) ? 1 : 0; - - gl_util_begin_2d(); - - if(!is) glEnable(GL_TEXTURE_2D); - glBindTexture(GL_TEXTURE_2D, font_atlas); - - glBegin(GL_QUADS); - for(i = 0; text[i] != 0; i++) { - double bx = 1.0 / 16 * (text[i] % 16), by = 1.0 / 16 * (text[i] / 16); - - glTexCoord2f(bx, by); - glVertex2f(i * gw + x, y); - glTexCoord2f(bx + 1.0 / 16, by); - glVertex2f(i * gw + x + gw, y); - glTexCoord2f(bx + 1.0 / 16, by + 1.0 / 16); - glVertex2f(i * gw + x + gw, y + gh); - glTexCoord2f(bx, by + 1.0 / 16); - glVertex2f(i * gw + x, y + gh); - } - glEnd(); - - glBindTexture(GL_TEXTURE_2D, 0); - if(!is) glDisable(GL_TEXTURE_2D); - - gl_util_end_2d(); -} - -int gl_font_width(const char* text) { - return font_width / 16 * strlen(text); -} - -int gl_font_height(const char* text) { - return font_height / 16; -} diff --git a/client/gl_main.c b/client/gl_main.c deleted file mode 100644 index 8e78651..0000000 --- a/client/gl_main.c +++ /dev/null @@ -1,67 +0,0 @@ -#include - -#include - -static double rad; - -void gl_main_changed(void) { - rad = 0; -} - -void gl_main_init(void) { - gl_cam_x = 0; - gl_cam_y = 0; - gl_cam_z = -1; - - gl_cam_lr = 90; - gl_cam_ud = 0; -} - -void gl_main_draw(void) { - int i; - - glUniform1f(glGetUniformLocation(gl_shadow, "solid_color"), 1); - - glPushMatrix(); - glRotatef(rad, 1, 0, 0); - glRotatef(rad, 0, 1, 0); - glRotatef(rad, 0, 0, 1); - for(i = 0; i < 8; i++) { - int c = (i % 2) == 0 ? 1 : 0; - double n[3]; - double u = 0.3; - - gl_calc_normal3(&n[0], 0, u * 1.4, 0, -u, 0, u, u, 0, u); - - glColor3f(c ? 0.75 : 0, 0, c ? 0 : 0.75); - glBegin(GL_TRIANGLES); - - glNormal3f(n[0], n[1], n[2]); - glVertex3f(0, u * 1.4, 0); - glVertex3f(-u, 0, u); - glVertex3f(u, 0, u); - glEnd(); - glRotatef(90, 0, 1, 0); - if(i == 3) { - glRotatef(180, 0, 0, 1); - glRotatef(90, 0, 1, 0); - } - } - - glPopMatrix(); - - glColor3f(1, 1, 1); - glBegin(GL_QUADS); - glNormal3f(0, 1, 0); - glVertex3f(-5, -1, -5); - glVertex3f(-5, -1, 5); - glVertex3f(5, -1, 5); - glVertex3f(5, -1, -5); - glEnd(); - - glUniform1f(glGetUniformLocation(gl_shadow, "solid_color"), 0); -} - -void gl_main_after(void) { - rad += 60.0 / (1000.0 / AF_WAIT_MS); -} diff --git a/client/gl_shader.c b/client/gl_shader.c deleted file mode 100644 index b01ce46..0000000 --- a/client/gl_shader.c +++ /dev/null @@ -1,108 +0,0 @@ -#include - -static void err(const char* path, GLuint shader) { - GLint len, ret; - char* e; - - glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &len); - - e = malloc(len); - e[len] = 0; - - glGetShaderInfoLog(shader, len, &ret, e); - - fprintf(stderr, "%s: %s\n", path, e); - - free(e); -} - -int gl_shader(GLuint* r, const char* vs, const char* fs) { - GLuint vsi; - GLuint fsi; - FILE* f; - int vsl = 0; - int fsl = 0; - char* vss; - char* fss; - GLint st; - - if((f = fopen(vs, "rb")) != NULL) { - fseek(f, 0, SEEK_END); - vsl = ftell(f); - fseek(f, 0, SEEK_SET); - - vss = malloc(vsl + 1); - fread(vss, vsl, 1, f); - vss[vsl] = 0; - - fclose(f); - } else { - return 0; - } - - if((f = fopen(fs, "rb")) != NULL) { - fseek(f, 0, SEEK_END); - fsl = ftell(f); - fseek(f, 0, SEEK_SET); - - fss = malloc(fsl + 1); - fread(fss, fsl, 1, f); - fss[fsl] = 0; - - fclose(f); - } else { - free(vss); - - return 0; - } - - vsi = glCreateShader(GL_VERTEX_SHADER); - fsi = glCreateShader(GL_FRAGMENT_SHADER); - - glShaderSource(vsi, 1, (const GLchar**)&vss, NULL); - glShaderSource(fsi, 1, (const GLchar**)&fss, NULL); - - glCompileShader(vsi); - glGetShaderiv(vsi, GL_COMPILE_STATUS, &st); - if(!st) { - err(vs, vsi); - - glDeleteShader(vsi); - glDeleteShader(fsi); - free(vss); - free(fss); - return 0; - } - - glCompileShader(fsi); - glGetShaderiv(fsi, GL_COMPILE_STATUS, &st); - if(!st) { - err(fs, fsi); - - glDeleteShader(vsi); - glDeleteShader(fsi); - free(vss); - free(fss); - return 0; - } - - free(vss); - free(fss); - - *r = glCreateProgram(); - - glAttachShader(*r, vsi); - glAttachShader(*r, fsi); - - glDeleteShader(vsi); - glDeleteShader(fsi); - - glLinkProgram(*r); - glGetProgramiv(*r, GL_LINK_STATUS, &st); - if(!st) { - glDeleteProgram(*r); - return 0; - } - - return 1; -} diff --git a/client/gl_util.c b/client/gl_util.c deleted file mode 100644 index fc205e1..0000000 --- a/client/gl_util.c +++ /dev/null @@ -1,34 +0,0 @@ -#include - -void gl_util_begin_2d(void) { - glUseProgram(0); - - glDisable(GL_DEPTH_TEST); - glDisable(GL_LIGHTING); - glDisable(GL_CULL_FACE); - - glMatrixMode(GL_PROJECTION); - glPushMatrix(); - glLoadIdentity(); - glOrtho(0, MwGetInteger(opengl, MwNwidth), MwGetInteger(opengl, MwNheight), 0, -1, 1); - - glMatrixMode(GL_MODELVIEW); - glPushMatrix(); - glLoadIdentity(); -} - -void gl_util_end_2d(void) { - glMatrixMode(GL_MODELVIEW); - glPopMatrix(); - - glMatrixMode(GL_PROJECTION); - glPopMatrix(); - - glMatrixMode(GL_MODELVIEW); - - glEnable(GL_CULL_FACE); - glEnable(GL_LIGHTING); - glEnable(GL_DEPTH_TEST); - - glUseProgram(gl_shadow); -} diff --git a/client/main.c b/client/main.c deleted file mode 100644 index f0a343f..0000000 --- a/client/main.c +++ /dev/null @@ -1,103 +0,0 @@ -#include - -MwWidget root, window, opengl; - -static const MwRect sizes[] = { - {0, 0, 640, 480}, - {0, 0, 800, 600}, - {0, 0, 1024, 768}, - {0, 0, 1280, 1024}, -}; - -static void resize(MwWidget handle, void* user, void* client) { - int ww = MwGetInteger(window, MwNwidth); - int wh = MwGetInteger(window, MwNheight); - - MwVaApply(opengl, - MwNwidth, ww, - MwNheight, wh, - NULL); - - gl_resize(ww, wh); -} - -static void tick(MwWidget handle, void* user, void* client) { - net_poll(); - - gl_render(); - - MwOpenGLSwapBuffer(opengl); -} - -static void* gl_load(const char* name) { - return MwOpenGLGetProcAddress(opengl, name); -} - -void scene_change(int scene) { - gl_scene = scene; - - gl_scene_change(); - ui_scene_change(); -} - -int main(int argc, char** argv) { - MwSizeHints sh; - MwRect rc; - int i; - - MwLibraryInit(); - - root = MwVaCreateWidget(NULL, "root", NULL, 0, 0, 0, 0, - MwNwaitMS, AF_WAIT_MS, - MwNbitmapFont, 0, - MwNmodernLook, 1, - NULL); - - window = MwVaCreateWidget(MwWindowClass, "main", root, 0, 0, 640, 480, - MwNtitle, "AssaultFish", - NULL); - - MwGetScreenSize(window, &rc); - - for(i = sizeof(sizes) / sizeof(sizes[0]) - 1; i >= 0; i--) { - if(sizes[i].width < rc.width && sizes[i].height < rc.height) { - sh.min_width = sh.max_width = sizes[i].width; - sh.min_height = sh.max_height = sizes[i].height; - break; - } - } - - for(i = 1; i < argc; i++) { - if(strcmp(argv[i], "--width") == 0 && (i + 1) < argc) { - sh.min_width = sh.max_width = atoi(argv[i + 1]); - } else if(strcmp(argv[i], "--height") == 0 && (i + 1) < argc) { - sh.min_height = sh.max_height = atoi(argv[i + 1]); - } - } - - MwVaApply(window, - MwNsizeHints, &sh, - MwNx, (rc.width - sh.max_width) / 2, - MwNy, (rc.height - sh.max_height) / 2, - MwNwidth, sh.max_width, - MwNheight, sh.max_height, - NULL); - - opengl = MwCreateWidget(MwOpenGLClass, "opengl", window, 0, 0, sh.max_width, sh.max_height); - - MwOpenGLMakeCurrent(opengl); - - gladLoadGLLoader(gl_load); - - resize(window, NULL, NULL); - - gl_init(); - ui_init(); - - scene_change(AF_SCENE_MAIN); - - MwAddUserHandler(window, MwNresizeHandler, resize, NULL); - MwAddUserHandler(root, MwNtickHandler, tick, NULL); - - MwLoop(root); -} diff --git a/client/net.c b/client/net.c deleted file mode 100644 index 9c8543d..0000000 --- a/client/net.c +++ /dev/null @@ -1,107 +0,0 @@ -#include - -static fishsoup_client_t* client = NULL; - -enum OPAQUE_STATES { - OPAQUE_WAITING_HELLO = 0, - OPAQUE_JOINING -}; - -typedef struct opaque { - char username[256]; - int state; -} opaque_t; - -static void net_return(MwWidget handle, void* user, void* client) { - scene_change(AF_SCENE_MAIN); -} - -static void on_packet(fishsoup_client_t* self, fishsoup_packet_t* pkt) { - opaque_t* o = self->user; - - if(pkt->header.type == AF_PACKET_HELLO && pkt->header.length == sizeof(af_packet_hello_t) && o->state == OPAQUE_WAITING_HELLO) { - af_packet_hello_t* p_hello = pkt->data; - unsigned char mj, mi, pt; - - af_common_version(&mj, &mi, &pt); - - af_common_log_info("HELLO packet: Server version %hhu.%hhu.%hhu\n", p_hello->major, p_hello->minor, p_hello->patch); - - if(mj == p_hello->major && mi >= p_hello->minor && (mi == p_hello->minor ? (pt >= p_hello->patch) : (1))) { - fishsoup_packet_t p; - af_packet_join_t p_join; - - af_common_log_info("Server version is compatible with this client\n"); - - p.header.type = AF_PACKET_JOIN; - p.header.length = sizeof(p_join); - p.data = &p_join; - - memcpy(p_join.username, o->username, 256); - - fishsoup_packet_send(self->fd, &p); - - o->state = OPAQUE_JOINING; - - gl_set_status("Joining"); - - return; - } - - af_common_log_info("Server version is incompatible with this client\n"); - MwAddUserHandler(ui_message("Server version is incompatible with this client"), MwNactivateHandler, net_return, NULL); - fishsoup_client_destroy(self); - client = NULL; - } else if(pkt->header.type == AF_PACKET_OK && pkt->header.length == sizeof(af_packet_ok_t) && o->state == OPAQUE_JOINING) { - af_packet_ok_t* p_ok = pkt->data; - - af_common_log_info("OK Packet: Joined successfully\n"); - - gl_set_status("Downloading data"); - } else if(pkt->header.type == AF_PACKET_ERROR && pkt->header.length == sizeof(af_packet_error_t) && o->state == OPAQUE_JOINING) { - af_packet_error_t* p_error = pkt->data; - - af_common_log_info("ERROR Packet: Failed to join\n"); - - fishsoup_client_destroy(self); - client = NULL; - } -} - -static void on_disconnect(fishsoup_client_t* self) { - free(self->user); -} - -void net_connect(const char* username, const char* hostname, int port) { - opaque_t* o; - - scene_change(AF_SCENE_CONNECTING); - client = fishsoup_client(hostname, port == 0 ? AF_DEFAULT_PORT : port); - if(client == NULL) { - MwAddUserHandler(ui_message("Failed to connect"), MwNactivateHandler, net_return, NULL); - return; - } - - o = client->user = malloc(sizeof(opaque_t)); - o->state = OPAQUE_WAITING_HELLO; - memset(o->username, 0, 256); - memcpy(o->username, username, strlen(username)); - - fishsoup_client_on_packet(client, on_packet); - fishsoup_client_on_disconnect(client, on_disconnect); - - gl_set_status("Waiting for server to be ready"); -} - -void net_poll(void) { - int ret; - if(client == NULL) return; - - while(client != NULL && (ret = fishsoup_client_poll(client)) > 0); - if(ret < 0) { - MwAddUserHandler(ui_message("Connection destroyed"), MwNactivateHandler, net_return, NULL); - fishsoup_client_destroy(client); - client = NULL; - return; - } -} diff --git a/client/ui.c b/client/ui.c deleted file mode 100644 index 3605c75..0000000 --- a/client/ui.c +++ /dev/null @@ -1,125 +0,0 @@ -#include - -MwWidget ui_notice(int width, int height) { - if(width == 0 && height == 0) { - width = 400; - height = 300; - } - - return MwVaCreateWidget(MwFrameClass, "notice", window, (MwGetInteger(window, MwNwidth) - width) / 2, (MwGetInteger(window, MwNheight) - height) / 2, width, height, - MwNhasBorder, 1, - MwNinverted, 0, - NULL); -} - -void ui_notice_destroy(MwWidget widget) { - MwDestroyWidget(widget); -} - -static void ui_login_join(MwWidget handle, void* user, void* client) { - char* username = MwStringDuplicate(MwGetText(MwGetVoid(user, "VusernameEntry"), MwNtext)); - char* hostname = MwStringDuplicate(MwGetText(MwGetVoid(user, "VhostnameEntry"), MwNtext)); - int port = atoi(MwGetText(MwGetVoid(user, "VportEntry"), MwNtext)); - - MwLLDestroyPixmap(MwGetVoid(user, MwNpixmap)); - ui_notice_destroy(user); - - net_connect(username, hostname, port); - - free(username); - free(hostname); -} - -static void ui_login(void) { - MwWidget wnd = ui_notice(0, 0); - int bw = MwDefaultBorderWidth(wnd); - MwWidget join = MwVaCreateWidget(MwButtonClass, "join", wnd, MwGetInteger(wnd, MwNwidth) - bw - 10 - 96, MwGetInteger(wnd, MwNheight) - bw - 10 - 24, 96, 24, - MwNtext, "Join", - NULL); - MwWidget box = MwVaCreateWidget(MwBoxClass, "box", wnd, bw, bw, MwGetInteger(wnd, MwNwidth) - bw * 2, MwGetInteger(wnd, MwNheight) - bw * 2 - 10 - 24, - MwNorientation, MwVERTICAL, - MwNmargin, 10, - MwNpadding, 10, - NULL); - MwWidget icon = MwVaCreateWidget(MwImageClass, "icon", box, 0, 0, 0, 0, - MwNfillArea, 0, - MwNfixedSize, 100, - NULL); - MwWidget entrybox[3]; - MwWidget entryinput[3]; - MwLLPixmap px = MwLoadImage(wnd, DATAROOTDIR "/assaultfish/logo.png"); - const char* labels[3] = { - "Username", - "Hostname", - "Port"}; - const char* entries[3] = { - "JohnDoe", - "127.0.0.1", - "", - }; - int i; - char buf[16]; - - sprintf(buf, "%d", AF_DEFAULT_PORT); - - entries[2] = buf; - - for(i = 0; i < 3; i++) { - entrybox[i] = MwVaCreateWidget(MwBoxClass, "entrybox", box, 0, 0, 0, 0, - MwNfixedSize, 24, - MwNmargin, 10, - NULL); - MwVaCreateWidget(MwLabelClass, "label", entrybox[i], 0, 0, 0, 0, - MwNfixedSize, 100, - MwNtext, labels[i], - MwNalignment, MwALIGNMENT_END, - NULL); - entryinput[i] = MwVaCreateWidget(MwEntryClass, "entry", entrybox[i], 0, 0, 0, 0, - MwNtext, entries[i], - NULL); - } - - MwVaApply(icon, - MwNpixmap, px, - NULL); - - MwVaApply(wnd, - MwNpixmap, px, - "VusernameEntry", entryinput[0], - "VhostnameEntry", entryinput[1], - "VportEntry", entryinput[2], - NULL); - - MwAddUserHandler(join, MwNactivateHandler, ui_login_join, wnd); -} - -static void ui_message_ok(MwWidget handle, void* user, void* client) { - MwDispatchUserHandler(user, MwNactivateHandler, NULL); - - ui_notice_destroy(user); -} - -MwWidget ui_message(const char* message) { - MwWidget wnd = ui_notice(0, 0); - int bw = MwDefaultBorderWidth(wnd); - MwWidget label = MwVaCreateWidget(MwLabelClass, "label", wnd, bw, (MwGetInteger(wnd, MwNheight) - 24) / 2, MwGetInteger(wnd, MwNwidth) - bw * 2, 24, - MwNtext, message, - NULL); - MwWidget ok = MwVaCreateWidget(MwButtonClass, "ok", wnd, MwGetInteger(wnd, MwNwidth) - bw - 10 - 96, MwGetInteger(wnd, MwNheight) - bw - 10 - 24, 96, 24, - MwNtext, "OK", - NULL); - - MwAddUserHandler(ok, MwNactivateHandler, ui_message_ok, wnd); - - return wnd; -} - -void ui_scene_change(void) { - if(gl_scene == AF_SCENE_MAIN) { - ui_login(); - } -} - -void ui_init(void) { - MwToggleDarkTheme(root, 1); -} diff --git a/engine/CMakeLists.txt b/engine/CMakeLists.txt new file mode 100644 index 0000000..d42aa8a --- /dev/null +++ b/engine/CMakeLists.txt @@ -0,0 +1,11 @@ +cmake_minimum_required(VERSION 3.11) +project(assaultfish) + +include(GNUInstallDirs) +set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_FULL_LIBDIR}") +set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) +add_subdirectory(external/glad) + +add_subdirectory(net) + +add_subdirectory(tools) diff --git a/engine/README.txt b/engine/README.txt new file mode 100644 index 0000000..89fc665 --- /dev/null +++ b/engine/README.txt @@ -0,0 +1,3 @@ +GearBox game engine source code + +Licensed under 3-clause BSD diff --git a/external/glad/CMakeLists.txt b/engine/external/glad/CMakeLists.txt similarity index 100% rename from external/glad/CMakeLists.txt rename to engine/external/glad/CMakeLists.txt diff --git a/external/glad/include/KHR/khrplatform.h b/engine/external/glad/include/KHR/khrplatform.h similarity index 100% rename from external/glad/include/KHR/khrplatform.h rename to engine/external/glad/include/KHR/khrplatform.h diff --git a/external/glad/include/glad/glad.h b/engine/external/glad/include/glad/glad.h similarity index 100% rename from external/glad/include/glad/glad.h rename to engine/external/glad/include/glad/glad.h diff --git a/external/glad/src/glad.c b/engine/external/glad/src/glad.c similarity index 100% rename from external/glad/src/glad.c rename to engine/external/glad/src/glad.c diff --git a/engine/net/.clang-format b/engine/net/.clang-format new file mode 100644 index 0000000..beacb20 --- /dev/null +++ b/engine/net/.clang-format @@ -0,0 +1,20 @@ +--- +Language: Cpp +UseTab: Always +TabWidth: 8 +AlignConsecutiveAssignments: + Enabled: true +AlignConsecutiveDeclarations: + Enabled: true +IndentWidth: 8 +PointerAlignment: Left +ColumnLimit: 0 +AllowShortIfStatementsOnASingleLine: Always +AllowShortBlocksOnASingleLine: Never +AllowShortFunctionsOnASingleLine: Empty +AllowShortLoopsOnASingleLine: true +SpaceBeforeParens: Never +AlignEscapedNewlines: DontAlign +SortIncludes: false +AllowShortEnumsOnASingleLine: false +#IndentPPDirectives: AfterHash diff --git a/engine/net/CMakeLists.txt b/engine/net/CMakeLists.txt new file mode 100644 index 0000000..8859a05 --- /dev/null +++ b/engine/net/CMakeLists.txt @@ -0,0 +1,21 @@ +cmake_minimum_required(VERSION 3.11) +project(libfishsoup) + +add_library(fishsoup SHARED src/client.c src/server.c src/socket.c src/stb_ds.c) + +target_include_directories(fishsoup PUBLIC include) + +find_package(PkgConfig) +pkg_check_modules(ZLIB REQUIRED zlib) +target_compile_options(fishsoup PRIVATE ${ZLIB_CFLAGS_OTHER}) +target_include_directories(fishsoup PRIVATE ${ZLIB_INCLUDE_DIRS}) +target_link_options(fishsoup PRIVATE ${ZLIB_LDFLAGS_OTHER}) +target_link_directories(fishsoup PRIVATE ${ZLIB_LIBRARY_DIRS}) +target_link_libraries(fishsoup PRIVATE ${ZLIB_LIBRARIES}) + +include(GNUInstallDirs) +install( + TARGETS fishsoup + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} +) diff --git a/engine/net/LICENSE b/engine/net/LICENSE new file mode 100644 index 0000000..23d9ab2 --- /dev/null +++ b/engine/net/LICENSE @@ -0,0 +1,24 @@ +Copyright (c) 2025, Fishsoup +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: +* Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. +* Neither the name of the nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/engine/net/Makefile b/engine/net/Makefile new file mode 100644 index 0000000..44c6085 --- /dev/null +++ b/engine/net/Makefile @@ -0,0 +1,33 @@ +CC = cc +CFLAGS = -g -Iinclude -fPIC `pkg-config --cflags zlib` +LDFLAGS = -shared +LIBS = `pkg-config --libs zlib` + +OBJS = src/client.o src/server.o src/socket.o src/stb_ds.o + +LIB = lib + +SO = .so + +.PHONY: all format clean +.SUFFIXES: .c .o + +all: $(LIB)fishsoup$(SO) + +format: + clang-format --verbose -i `find src include "(" -name "*.c" -or -name "*.h" ")" -and -not -name "stb_*.h"` test*.c + +$(LIB)fishsoup$(SO): $(OBJS) + $(CC) $(LDFLAGS) -o $@ $(OBJS) $(LIBS) + +testclient: testclient.c $(LIB)fishsoup$(SO) + cc -o $@ testclient.c -Wl,-R./ -L./ -Iinclude -lfishsoup + +testserver: testserver.c $(LIB)fishsoup$(SO) + cc -o $@ testserver.c -Wl,-R./ -L./ -Iinclude -lfishsoup + +.c.o: + $(CC) $(CFLAGS) -c -o $@ $< + +clean: + rm -f *$(SO) $(OBJS) ./testserver ./testclient diff --git a/engine/net/include/fishsoup.h b/engine/net/include/fishsoup.h new file mode 100644 index 0000000..c904e83 --- /dev/null +++ b/engine/net/include/fishsoup.h @@ -0,0 +1,86 @@ +#ifndef __FISHSOUP_H__ +#define __FISHSOUP_H__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* TCP */ +#define FISHSOUP_PORT 3219 + +#define FISHSOUP_PING 3 + +enum fishsoup_packet_type { + FISHSOUP_PACKET_PING = 0, + FISHSOUP_PACKET_PONG, + FISHSOUP_PACKET_USER +}; + +typedef struct fishsoup_client fishsoup_client_t; +typedef struct fishsoup_server fishsoup_server_t; +typedef struct fishsoup_packet_header fishsoup_packet_header_t; +typedef struct fishsoup_packet fishsoup_packet_t; + +struct fishsoup_client { + int fd; + time_t last_ping; + void (*on_packet)(fishsoup_client_t* client, fishsoup_packet_t* packet); + void (*on_disconnect)(fishsoup_client_t* client); + void* user; + struct { + int sent_ping; + int cleanup; + } server; +}; + +struct fishsoup_server { + int fd; + struct sockaddr_in address; + fishsoup_client_t* clients; + void* user; + void (*on_client)(fishsoup_server_t* server, fishsoup_client_t* client); + void (*on_tick)(fishsoup_server_t* server); +}; + +#pragma pack(1) +struct fishsoup_packet_header { + unsigned char type; + unsigned short length; /* ok, seriously, who sends 64k at ONCE */ +}; +#pragma pack() + +/* !!! REMEMBER TO FREE DATA AFTER RECV UNLESS IT'S NULL !!! */ +struct fishsoup_packet { + fishsoup_packet_header_t header; + void* data; +}; + +fishsoup_client_t* fishsoup_client(const char* host, int port); +void fishsoup_client_on_packet(fishsoup_client_t* client, void (*on_packet)(fishsoup_client_t* client, fishsoup_packet_t* packet)); +void fishsoup_client_on_disconnect(fishsoup_client_t* client, void (*on_disconnect)(fishsoup_client_t* client)); +int fishsoup_client_poll(fishsoup_client_t* client); +void fishsoup_client_destroy(fishsoup_client_t* client); + +fishsoup_server_t* fishsoup_server(int port); +int fishsoup_server_clients(fishsoup_server_t* server); +void fishsoup_server_loop(fishsoup_server_t* server); +void fishsoup_server_on_client(fishsoup_server_t* server, void (*on_client)(fishsoup_server_t* server, fishsoup_client_t* client)); +void fishsoup_server_on_tick(fishsoup_server_t* server, void (*on_tick)(fishsoup_server_t* server)); +void fishsoup_server_broadcast(fishsoup_server_t* server, fishsoup_packet_t* packet); +void fishsoup_server_destroy(fishsoup_server_t* server); + +int fishsoup_fd_send(int fd, const void* buf, int length); +int fishsoup_fd_recv(int fd, void* buf, int length, int to); +int fishsoup_packet_send(int fd, fishsoup_packet_t* packet); +int fishsoup_packet_recv(int fd, fishsoup_packet_t* packet); + +#endif diff --git a/engine/net/src/client.c b/engine/net/src/client.c new file mode 100644 index 0000000..ff1310a --- /dev/null +++ b/engine/net/src/client.c @@ -0,0 +1,93 @@ +#include + +fishsoup_client_t* fishsoup_client(const char* host, int port) { + fishsoup_client_t* client = malloc(sizeof(*client)); + struct addrinfo hints; + char p[32]; + struct addrinfo* result; + struct addrinfo* rp; + + if(port == 0) port = FISHSOUP_PORT; + sprintf(p, "%d", port); + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + if(getaddrinfo(host, p, &hints, &result) != 0) { + free(client); + return NULL; + } + + for(rp = result; rp != NULL; rp = rp->ai_next) { + int val = 1; + + if((client->fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol)) < 0) { + continue; + } + + val = 1; + setsockopt(client->fd, IPPROTO_TCP, TCP_NODELAY, (char*)&val, sizeof(val)); + + if(connect(client->fd, rp->ai_addr, rp->ai_addrlen) >= 0) { + break; + } + + close(client->fd); + } + + if(rp == NULL) { + free(client); + return NULL; + } + + client->on_packet = NULL; + client->on_disconnect = NULL; + + return client; +} + +void fishsoup_client_on_packet(fishsoup_client_t* client, void (*on_packet)(fishsoup_client_t* client, fishsoup_packet_t* packet)) { + client->on_packet = on_packet; +} + +void fishsoup_client_on_disconnect(fishsoup_client_t* client, void (*on_disconnect)(fishsoup_client_t* client)) { + client->on_disconnect = on_disconnect; +} + +int fishsoup_client_poll(fishsoup_client_t* client) { + struct pollfd pfd; + fishsoup_packet_t pkt; + int ret; + + pfd.fd = client->fd; + pfd.events = POLLIN | POLLPRI; + ret = poll(&pfd, 1, 0); + if(ret <= 0) return ret; + + ret = fishsoup_packet_recv(client->fd, &pkt); + if(ret == 0) return -1; /* connection broke, probably */ + + if(pkt.header.type == FISHSOUP_PACKET_PING) { + fishsoup_packet_t ret; + client->last_ping = time(NULL); + + ret.header.type = FISHSOUP_PACKET_PONG; + ret.header.length = 0; + ret.data = NULL; + if(fishsoup_packet_send(client->fd, &ret) == 0) { + if(pkt.data != NULL) free(pkt.data); + return -1; + } + } + if(client->on_packet != NULL) client->on_packet(client, &pkt); + + if(pkt.data != NULL) free(pkt.data); + + return 1; +} + +void fishsoup_client_destroy(fishsoup_client_t* client) { + if(client->on_disconnect != NULL) client->on_disconnect(client); + close(client->fd); + free(client); +} diff --git a/engine/net/src/server.c b/engine/net/src/server.c new file mode 100644 index 0000000..7f4995f --- /dev/null +++ b/engine/net/src/server.c @@ -0,0 +1,199 @@ +#include + +#include "stb_ds.h" + +fishsoup_server_t* fishsoup_server(int port) { + fishsoup_server_t* server = malloc(sizeof(*server)); + int val; + + if(port == 0) port = FISHSOUP_PORT; + + if((server->fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) { + free(server); + return NULL; + } + + val = 1; + setsockopt(server->fd, IPPROTO_TCP, TCP_NODELAY, (char*)&val, sizeof(val)); + + server->address.sin_family = AF_INET; + server->address.sin_addr.s_addr = INADDR_ANY; + server->address.sin_port = htons(port); + + if(bind(server->fd, (struct sockaddr*)&server->address, sizeof(server->address)) < 0) { + close(server->fd); + free(server); + return NULL; + } + + if(listen(server->fd, 128) < 0) { + close(server->fd); + free(server); + return NULL; + } + + server->clients = NULL; + server->on_client = NULL; + server->on_tick = NULL; + + return server; +} + +int fishsoup_server_clients(fishsoup_server_t* server) { + return arrlen(server->clients); +} + +void fishsoup_server_loop(fishsoup_server_t* server) { + struct pollfd* pfd; + + while(1) { + int i; + int ret; + pfd = malloc(sizeof(*pfd) * (1 + arrlen(server->clients))); + + pfd[0].fd = server->fd; + pfd[0].events = POLLIN | POLLPRI; + + for(i = 0; i < arrlen(server->clients); i++) { + pfd[1 + i].fd = server->clients[i].fd; + pfd[1 + i].events = POLLIN | POLLPRI; + } + + ret = poll(pfd, 1 + arrlen(server->clients), 50); + if(ret < 0) { + free(pfd); + break; + } + + if(server->on_tick != NULL) server->on_tick(server); + + if(pfd[0].revents & POLLIN) { + fishsoup_client_t client; + struct sockaddr_in address; + int len = sizeof(address); + + client.fd = accept(server->fd, (struct sockaddr*)&address, &len); + client.last_ping = time(NULL); + client.server.cleanup = 0; + client.server.sent_ping = 0; + client.on_packet = NULL; + + arrput(server->clients, client); + + if(server->on_client != NULL) server->on_client(server, &server->clients[arrlen(server->clients) - 1]); + +#ifdef DEBUG + fprintf(stderr, "Client %d accepted\n", server->clients[i].fd); +#endif + } + + for(i = 0; i < arrlen(server->clients); i++) { + time_t diff; + if(server->clients[i].server.cleanup) continue; + if(pfd[1 + i].revents & POLLIN) { + fishsoup_packet_t pkt; + + if(fishsoup_packet_recv(server->clients[i].fd, &pkt) == 0) { + server->clients[i].server.cleanup = 1; + } else { + if(pkt.header.type == FISHSOUP_PACKET_PONG && server->clients[i].server.sent_ping) { + server->clients[i].last_ping = time(NULL); + server->clients[i].server.sent_ping = 0; + +#ifdef DEBUG + fprintf(stderr, "Pong from client %d\n", server->clients[i].fd); +#endif + } else if(pkt.header.type == FISHSOUP_PACKET_PING) { + fishsoup_packet_t ret; + + ret.header.type = FISHSOUP_PACKET_PONG; + ret.header.length = 0; + ret.data = NULL; + + if(fishsoup_packet_send(server->clients[i].fd, &ret) == 0) { + server->clients[i].server.cleanup = 1; + } else { +#ifdef DEBUG + fprintf(stderr, "Ping from client %d, sending back pong\n", server->clients[i].fd); +#endif + } + } + + if(!server->clients[i].server.cleanup && server->clients[i].on_packet != NULL) server->clients[i].on_packet(&server->clients[i], &pkt); + + if(pkt.data != NULL) free(pkt.data); + } + } + if(server->clients[i].server.cleanup) continue; + + diff = time(NULL) - server->clients[i].last_ping; + + if(diff == FISHSOUP_PING && !server->clients[i].server.sent_ping) { + fishsoup_packet_t pkt; + + pkt.header.type = FISHSOUP_PACKET_PING; + pkt.header.length = 0; + pkt.data = NULL; + + if(fishsoup_packet_send(server->clients[i].fd, &pkt) == 0) { + server->clients[i].server.cleanup = 1; +#ifdef DEBUG + fprintf(stderr, "Failed to ping client %d\n", server->clients[i].fd); +#endif + } else { + server->clients[i].server.sent_ping = 1; +#ifdef DEBUG + fprintf(stderr, "Pinging client %d\n", server->clients[i].fd); +#endif + } + } else if(diff == FISHSOUP_PING * 2 && server->clients[i].server.sent_ping) { + server->clients[i].server.cleanup = 1; +#ifdef DEBUG + fprintf(stderr, "Ping timeout (client %d)\n", server->clients[i].fd); +#endif + } + } + + for(i = 0; i < arrlen(server->clients); i++) { + if(server->clients[i].server.cleanup) { +#ifdef DEBUG + fprintf(stderr, "Client %d cleanup\n", server->clients[i].fd); +#endif + if(server->clients[i].on_disconnect != NULL) server->clients[i].on_disconnect(&server->clients[i]); + close(server->clients[i].fd); + arrdel(server->clients, i); + i--; + } + } + + free(pfd); + } +} + +void fishsoup_server_on_client(fishsoup_server_t* server, void (*on_client)(fishsoup_server_t* server, fishsoup_client_t* client)) { + server->on_client = on_client; +} + +void fishsoup_server_on_tick(fishsoup_server_t* server, void (*on_tick)(fishsoup_server_t* server)) { + server->on_tick = on_tick; +} + +void fishsoup_server_destroy(fishsoup_server_t* server) { + int i; + for(i = 0; i < arrlen(server->clients); i++) { + if(server->clients[i].on_disconnect != NULL) server->clients[i].on_disconnect(&server->clients[i]); + close(server->clients[i].fd); + } + arrfree(server->clients); + close(server->fd); + free(server); +} + +void fishsoup_server_broadcast(fishsoup_server_t* server, fishsoup_packet_t* packet) { + int i; + for(i = 0; i < arrlen(server->clients); i++) { + if(fishsoup_packet_send(server->clients[i].fd, packet) == 0) { + server->clients[i].server.cleanup = 1; + } + } +} diff --git a/engine/net/src/socket.c b/engine/net/src/socket.c new file mode 100644 index 0000000..4f0f0e8 --- /dev/null +++ b/engine/net/src/socket.c @@ -0,0 +1,200 @@ +#include + +#include + +#define CHUNK 512 + +int fishsoup_packet_send(int fd, fishsoup_packet_t* packet) { + int r; + fishsoup_packet_header_t h; + int len = 0; + unsigned char* data = NULL; + + if(packet->header.length > 0) { + z_stream strm; + int in = packet->header.length; + int seek = 0; + int flush = 0; + unsigned char buf[CHUNK]; + + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + if(deflateInit(&strm, Z_DEFAULT_COMPRESSION) != Z_OK) return 0; + + do { + strm.avail_in = in <= CHUNK ? in : CHUNK; + strm.next_in = ((unsigned char*)packet->data) + seek; + + flush = in <= CHUNK ? Z_FINISH : Z_NO_FLUSH; + + do { + int have; + + strm.avail_out = CHUNK; + strm.next_out = buf; + + deflate(&strm, flush); + + have = CHUNK - strm.avail_out; + + if(data == NULL) { + data = malloc(have); + memcpy(data, buf, have); + } else { + unsigned char* old = data; + + data = malloc(len + have); + memcpy(data, old, len); + memcpy(data + len, buf, have); + + free(data); + } + + len += have; + } while(strm.avail_out == 0); + + seek += CHUNK; + } while(flush != Z_FINISH); + + deflateEnd(&strm); + } + + memcpy(&h, &packet->header, sizeof(h)); + h.length = htons(len); + + r = fishsoup_fd_send(fd, &h, sizeof(h)); + if(r != sizeof(h)) return 0; + + if(data != NULL) { + r = fishsoup_fd_send(fd, data, len); + if(r != sizeof(h)) { + free(data); + return 0; + } + free(data); + } + + return sizeof(h) + len; +} + +int fishsoup_packet_recv(int fd, fishsoup_packet_t* packet) { + fishsoup_packet_header_t h; + + if(fishsoup_fd_recv(fd, &h, sizeof(h), 500) != sizeof(h)) { + packet->data = NULL; + return 0; + } + + memcpy(&packet->header, &h, sizeof(h)); + packet->header.length = ntohs(packet->header.length); + packet->data = NULL; + if(packet->header.length > 0) { + unsigned char* data = malloc(packet->header.length); + z_stream strm; + int in = packet->header.length; + int ret; + int seek = 0; + int len = 0; + unsigned char buf[CHUNK]; + if(fishsoup_fd_recv(fd, data, packet->header.length, 50) != packet->header.length) { + free(data); + return 0; + } + + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + strm.avail_in = 0; + strm.next_in = Z_NULL; + + if(inflateInit(&strm) != Z_OK) { + free(data); + return 0; + } + + do { + strm.avail_in = in <= CHUNK ? in : CHUNK; + strm.next_in = data + seek; + + do { + int have; + + strm.avail_out = CHUNK; + strm.next_out = buf; + + ret = inflate(&strm, Z_NO_FLUSH); + + if(ret == Z_NEED_DICT || ret == Z_DATA_ERROR || ret == Z_MEM_ERROR) { + inflateEnd(&strm); + if(packet->data != NULL) { + free(packet->data); + packet->data = NULL; + } + free(data); + return 0; + } + + have = CHUNK - strm.avail_out; + + if(packet->data == NULL) { + packet->data = malloc(have); + memcpy(packet->data, buf, have); + } else { + unsigned char* old = packet->data; + + packet->data = malloc(len + have); + memcpy(packet->data, old, len); + memcpy(packet->data + len, buf, have); + + free(packet->data); + } + + len += have; + } while(strm.avail_out == 0); + } while(ret != Z_STREAM_END); + + inflateEnd(&strm); + + free(data); + + packet->header.length = len; + } + + return sizeof(h) + packet->header.length; +} + +int fishsoup_fd_send(int fd, const void* buf, int length) { + struct pollfd pfd; + pfd.fd = fd; + pfd.events = POLLOUT | POLLPRI; + + poll(&pfd, 1, 0); + if(!(pfd.revents & POLLOUT)) return 0; + + return send(fd, buf, length, 0); +} + +int fishsoup_fd_recv(int fd, void* buf, int length, int to) { + struct pollfd pfd; + int ret; + unsigned char* b = buf; + int i = 0; + time_t t = time(NULL); + + pfd.fd = fd; + pfd.events = POLLIN | POLLPRI; + + while(i < length) { + /* i think 500 is reasonable timeout. no? */ + ret = poll(&pfd, 1, to); + + if((time(NULL) - t) >= 5) return i; /* we waited enough! kill the connection */ + if(ret <= 0) return i; + + ret = recv(fd, &b[i], 1, 0); + if(ret < 1) return i; + i++; + } + return i; +} diff --git a/external/stb/stb_ds.c b/engine/net/src/stb_ds.c similarity index 60% rename from external/stb/stb_ds.c rename to engine/net/src/stb_ds.c index 689615c..9df32fd 100644 --- a/external/stb/stb_ds.c +++ b/engine/net/src/stb_ds.c @@ -1,2 +1,2 @@ #define STB_DS_IMPLEMENTATION -#include +#include "stb_ds.h" diff --git a/engine/net/src/stb_ds.h b/engine/net/src/stb_ds.h new file mode 100644 index 0000000..e84c82d --- /dev/null +++ b/engine/net/src/stb_ds.h @@ -0,0 +1,1895 @@ +/* stb_ds.h - v0.67 - public domain data structures - Sean Barrett 2019 + + This is a single-header-file library that provides easy-to-use + dynamic arrays and hash tables for C (also works in C++). + + For a gentle introduction: + http://nothings.org/stb_ds + + To use this library, do this in *one* C or C++ file: + #define STB_DS_IMPLEMENTATION + #include "stb_ds.h" + +TABLE OF CONTENTS + + Table of Contents + Compile-time options + License + Documentation + Notes + Notes - Dynamic arrays + Notes - Hash maps + Credits + +COMPILE-TIME OPTIONS + + #define STBDS_NO_SHORT_NAMES + + This flag needs to be set globally. + + By default stb_ds exposes shorter function names that are not qualified + with the "stbds_" prefix. If these names conflict with the names in your + code, define this flag. + + #define STBDS_SIPHASH_2_4 + + This flag only needs to be set in the file containing #define STB_DS_IMPLEMENTATION. + + By default stb_ds.h hashes using a weaker variant of SipHash and a custom hash for + 4- and 8-byte keys. On 64-bit platforms, you can define the above flag to force + stb_ds.h to use specification-compliant SipHash-2-4 for all keys. Doing so makes + hash table insertion about 20% slower on 4- and 8-byte keys, 5% slower on + 64-byte keys, and 10% slower on 256-byte keys on my test computer. + + #define STBDS_REALLOC(context,ptr,size) better_realloc + #define STBDS_FREE(context,ptr) better_free + + These defines only need to be set in the file containing #define STB_DS_IMPLEMENTATION. + + By default stb_ds uses stdlib realloc() and free() for memory management. You can + substitute your own functions instead by defining these symbols. You must either + define both, or neither. Note that at the moment, 'context' will always be NULL. + @TODO add an array/hash initialization function that takes a memory context pointer. + + #define STBDS_UNIT_TESTS + + Defines a function stbds_unit_tests() that checks the functioning of the data structures. + + Note that on older versions of gcc (e.g. 5.x.x) you may need to build with '-std=c++0x' + (or equivalentally '-std=c++11') when using anonymous structures as seen on the web + page or in STBDS_UNIT_TESTS. + +LICENSE + + Placed in the public domain and also MIT licensed. + See end of file for detailed license information. + +DOCUMENTATION + + Dynamic Arrays + + Non-function interface: + + Declare an empty dynamic array of type T + T* foo = NULL; + + Access the i'th item of a dynamic array 'foo' of type T, T* foo: + foo[i] + + Functions (actually macros) + + arrfree: + void arrfree(T*); + Frees the array. + + arrlen: + ptrdiff_t arrlen(T*); + Returns the number of elements in the array. + + arrlenu: + size_t arrlenu(T*); + Returns the number of elements in the array as an unsigned type. + + arrpop: + T arrpop(T* a) + Removes the final element of the array and returns it. + + arrput: + T arrput(T* a, T b); + Appends the item b to the end of array a. Returns b. + + arrins: + T arrins(T* a, int p, T b); + Inserts the item b into the middle of array a, into a[p], + moving the rest of the array over. Returns b. + + arrinsn: + void arrinsn(T* a, int p, int n); + Inserts n uninitialized items into array a starting at a[p], + moving the rest of the array over. + + arraddnptr: + T* arraddnptr(T* a, int n) + Appends n uninitialized items onto array at the end. + Returns a pointer to the first uninitialized item added. + + arraddnindex: + size_t arraddnindex(T* a, int n) + Appends n uninitialized items onto array at the end. + Returns the index of the first uninitialized item added. + + arrdel: + void arrdel(T* a, int p); + Deletes the element at a[p], moving the rest of the array over. + + arrdeln: + void arrdeln(T* a, int p, int n); + Deletes n elements starting at a[p], moving the rest of the array over. + + arrdelswap: + void arrdelswap(T* a, int p); + Deletes the element at a[p], replacing it with the element from + the end of the array. O(1) performance. + + arrsetlen: + void arrsetlen(T* a, int n); + Changes the length of the array to n. Allocates uninitialized + slots at the end if necessary. + + arrsetcap: + size_t arrsetcap(T* a, int n); + Sets the length of allocated storage to at least n. It will not + change the length of the array. + + arrcap: + size_t arrcap(T* a); + Returns the number of total elements the array can contain without + needing to be reallocated. + + Hash maps & String hash maps + + Given T is a structure type: struct { TK key; TV value; }. Note that some + functions do not require TV value and can have other fields. For string + hash maps, TK must be 'char *'. + + Special interface: + + stbds_rand_seed: + void stbds_rand_seed(size_t seed); + For security against adversarially chosen data, you should seed the + library with a strong random number. Or at least seed it with time(). + + stbds_hash_string: + size_t stbds_hash_string(char *str, size_t seed); + Returns a hash value for a string. + + stbds_hash_bytes: + size_t stbds_hash_bytes(void *p, size_t len, size_t seed); + These functions hash an arbitrary number of bytes. The function + uses a custom hash for 4- and 8-byte data, and a weakened version + of SipHash for everything else. On 64-bit platforms you can get + specification-compliant SipHash-2-4 on all data by defining + STBDS_SIPHASH_2_4, at a significant cost in speed. + + Non-function interface: + + Declare an empty hash map of type T + T* foo = NULL; + + Access the i'th entry in a hash table T* foo: + foo[i] + + Function interface (actually macros): + + hmfree + shfree + void hmfree(T*); + void shfree(T*); + Frees the hashmap and sets the pointer to NULL. + + hmlen + shlen + ptrdiff_t hmlen(T*) + ptrdiff_t shlen(T*) + Returns the number of elements in the hashmap. + + hmlenu + shlenu + size_t hmlenu(T*) + size_t shlenu(T*) + Returns the number of elements in the hashmap. + + hmgeti + shgeti + hmgeti_ts + ptrdiff_t hmgeti(T*, TK key) + ptrdiff_t shgeti(T*, char* key) + ptrdiff_t hmgeti_ts(T*, TK key, ptrdiff_t tempvar) + Returns the index in the hashmap which has the key 'key', or -1 + if the key is not present. + + hmget + hmget_ts + shget + TV hmget(T*, TK key) + TV shget(T*, char* key) + TV hmget_ts(T*, TK key, ptrdiff_t tempvar) + Returns the value corresponding to 'key' in the hashmap. + The structure must have a 'value' field + + hmgets + shgets + T hmgets(T*, TK key) + T shgets(T*, char* key) + Returns the structure corresponding to 'key' in the hashmap. + + hmgetp + shgetp + hmgetp_ts + hmgetp_null + shgetp_null + T* hmgetp(T*, TK key) + T* shgetp(T*, char* key) + T* hmgetp_ts(T*, TK key, ptrdiff_t tempvar) + T* hmgetp_null(T*, TK key) + T* shgetp_null(T*, char *key) + Returns a pointer to the structure corresponding to 'key' in + the hashmap. Functions ending in "_null" return NULL if the key + is not present in the hashmap; the others return a pointer to a + structure holding the default value (but not the searched-for key). + + hmdefault + shdefault + TV hmdefault(T*, TV value) + TV shdefault(T*, TV value) + Sets the default value for the hashmap, the value which will be + returned by hmget/shget if the key is not present. + + hmdefaults + shdefaults + TV hmdefaults(T*, T item) + TV shdefaults(T*, T item) + Sets the default struct for the hashmap, the contents which will be + returned by hmgets/shgets if the key is not present. + + hmput + shput + TV hmput(T*, TK key, TV value) + TV shput(T*, char* key, TV value) + Inserts a pair into the hashmap. If the key is already + present in the hashmap, updates its value. + + hmputs + shputs + T hmputs(T*, T item) + T shputs(T*, T item) + Inserts a struct with T.key into the hashmap. If the struct is already + present in the hashmap, updates it. + + hmdel + shdel + int hmdel(T*, TK key) + int shdel(T*, char* key) + If 'key' is in the hashmap, deletes its entry and returns 1. + Otherwise returns 0. + + Function interface (actually macros) for strings only: + + sh_new_strdup + void sh_new_strdup(T*); + Overwrites the existing pointer with a newly allocated + string hashmap which will automatically allocate and free + each string key using realloc/free + + sh_new_arena + void sh_new_arena(T*); + Overwrites the existing pointer with a newly allocated + string hashmap which will automatically allocate each string + key to a string arena. Every string key ever used by this + hash table remains in the arena until the arena is freed. + Additionally, any key which is deleted and reinserted will + be allocated multiple times in the string arena. + +NOTES + + * These data structures are realloc'd when they grow, and the macro + "functions" write to the provided pointer. This means: (a) the pointer + must be an lvalue, and (b) the pointer to the data structure is not + stable, and you must maintain it the same as you would a realloc'd + pointer. For example, if you pass a pointer to a dynamic array to a + function which updates it, the function must return back the new + pointer to the caller. This is the price of trying to do this in C. + + * The following are the only functions that are thread-safe on a single data + structure, i.e. can be run in multiple threads simultaneously on the same + data structure + hmlen shlen + hmlenu shlenu + hmget_ts shget_ts + hmgeti_ts shgeti_ts + hmgets_ts shgets_ts + + * You iterate over the contents of a dynamic array and a hashmap in exactly + the same way, using arrlen/hmlen/shlen: + + for (i=0; i < arrlen(foo); ++i) + ... foo[i] ... + + * All operations except arrins/arrdel are O(1) amortized, but individual + operations can be slow, so these data structures may not be suitable + for real time use. Dynamic arrays double in capacity as needed, so + elements are copied an average of once. Hash tables double/halve + their size as needed, with appropriate hysteresis to maintain O(1) + performance. + +NOTES - DYNAMIC ARRAY + + * If you know how long a dynamic array is going to be in advance, you can avoid + extra memory allocations by using arrsetlen to allocate it to that length in + advance and use foo[n] while filling it out, or arrsetcap to allocate the memory + for that length and use arrput/arrpush as normal. + + * Unlike some other versions of the dynamic array, this version should + be safe to use with strict-aliasing optimizations. + +NOTES - HASH MAP + + * For compilers other than GCC and clang (e.g. Visual Studio), for hmput/hmget/hmdel + and variants, the key must be an lvalue (so the macro can take the address of it). + Extensions are used that eliminate this requirement if you're using C99 and later + in GCC or clang, or if you're using C++ in GCC. But note that this can make your + code less portable. + + * To test for presence of a key in a hashmap, just do 'hmgeti(foo,key) >= 0'. + + * The iteration order of your data in the hashmap is determined solely by the + order of insertions and deletions. In particular, if you never delete, new + keys are always added at the end of the array. This will be consistent + across all platforms and versions of the library. However, you should not + attempt to serialize the internal hash table, as the hash is not consistent + between different platforms, and may change with future versions of the library. + + * Use sh_new_arena() for string hashmaps that you never delete from. Initialize + with NULL if you're managing the memory for your strings, or your strings are + never freed (at least until the hashmap is freed). Otherwise, use sh_new_strdup(). + @TODO: make an arena variant that garbage collects the strings with a trivial + copy collector into a new arena whenever the table shrinks / rebuilds. Since + current arena recommendation is to only use arena if it never deletes, then + this can just replace current arena implementation. + + * If adversarial input is a serious concern and you're on a 64-bit platform, + enable STBDS_SIPHASH_2_4 (see the 'Compile-time options' section), and pass + a strong random number to stbds_rand_seed. + + * The default value for the hash table is stored in foo[-1], so if you + use code like 'hmget(T,k)->value = 5' you can accidentally overwrite + the value stored by hmdefault if 'k' is not present. + +CREDITS + + Sean Barrett -- library, idea for dynamic array API/implementation + Per Vognsen -- idea for hash table API/implementation + Rafael Sachetto -- arrpop() + github:HeroicKatora -- arraddn() reworking + + Bugfixes: + Andy Durdin + Shane Liesegang + Vinh Truong + Andreas Molzer + github:hashitaku + github:srdjanstipic + Macoy Madson + Andreas Vennstrom + Tobias Mansfield-Williams +*/ + +#ifdef STBDS_UNIT_TESTS +#define _CRT_SECURE_NO_WARNINGS +#endif + +#ifndef INCLUDE_STB_DS_H +#define INCLUDE_STB_DS_H + +#include +#include + +#ifndef STBDS_NO_SHORT_NAMES +#define arrlen stbds_arrlen +#define arrlenu stbds_arrlenu +#define arrput stbds_arrput +#define arrpush stbds_arrput +#define arrpop stbds_arrpop +#define arrfree stbds_arrfree +#define arraddn stbds_arraddn // deprecated, use one of the following instead: +#define arraddnptr stbds_arraddnptr +#define arraddnindex stbds_arraddnindex +#define arrsetlen stbds_arrsetlen +#define arrlast stbds_arrlast +#define arrins stbds_arrins +#define arrinsn stbds_arrinsn +#define arrdel stbds_arrdel +#define arrdeln stbds_arrdeln +#define arrdelswap stbds_arrdelswap +#define arrcap stbds_arrcap +#define arrsetcap stbds_arrsetcap + +#define hmput stbds_hmput +#define hmputs stbds_hmputs +#define hmget stbds_hmget +#define hmget_ts stbds_hmget_ts +#define hmgets stbds_hmgets +#define hmgetp stbds_hmgetp +#define hmgetp_ts stbds_hmgetp_ts +#define hmgetp_null stbds_hmgetp_null +#define hmgeti stbds_hmgeti +#define hmgeti_ts stbds_hmgeti_ts +#define hmdel stbds_hmdel +#define hmlen stbds_hmlen +#define hmlenu stbds_hmlenu +#define hmfree stbds_hmfree +#define hmdefault stbds_hmdefault +#define hmdefaults stbds_hmdefaults + +#define shput stbds_shput +#define shputi stbds_shputi +#define shputs stbds_shputs +#define shget stbds_shget +#define shgeti stbds_shgeti +#define shgets stbds_shgets +#define shgetp stbds_shgetp +#define shgetp_null stbds_shgetp_null +#define shdel stbds_shdel +#define shlen stbds_shlen +#define shlenu stbds_shlenu +#define shfree stbds_shfree +#define shdefault stbds_shdefault +#define shdefaults stbds_shdefaults +#define sh_new_arena stbds_sh_new_arena +#define sh_new_strdup stbds_sh_new_strdup + +#define stralloc stbds_stralloc +#define strreset stbds_strreset +#endif + +#if defined(STBDS_REALLOC) && !defined(STBDS_FREE) || !defined(STBDS_REALLOC) && defined(STBDS_FREE) +#error "You must define both STBDS_REALLOC and STBDS_FREE, or neither." +#endif +#if !defined(STBDS_REALLOC) && !defined(STBDS_FREE) +#include +#define STBDS_REALLOC(c,p,s) realloc(p,s) +#define STBDS_FREE(c,p) free(p) +#endif + +#ifdef _MSC_VER +#define STBDS_NOTUSED(v) (void)(v) +#else +#define STBDS_NOTUSED(v) (void)sizeof(v) +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +// for security against attackers, seed the library with a random number, at least time() but stronger is better +extern void stbds_rand_seed(size_t seed); + +// these are the hash functions used internally if you want to test them or use them for other purposes +extern size_t stbds_hash_bytes(void *p, size_t len, size_t seed); +extern size_t stbds_hash_string(char *str, size_t seed); + +// this is a simple string arena allocator, initialize with e.g. 'stbds_string_arena my_arena={0}'. +typedef struct stbds_string_arena stbds_string_arena; +extern char * stbds_stralloc(stbds_string_arena *a, char *str); +extern void stbds_strreset(stbds_string_arena *a); + +// have to #define STBDS_UNIT_TESTS to call this +extern void stbds_unit_tests(void); + +/////////////// +// +// Everything below here is implementation details +// + +extern void * stbds_arrgrowf(void *a, size_t elemsize, size_t addlen, size_t min_cap); +extern void stbds_arrfreef(void *a); +extern void stbds_hmfree_func(void *p, size_t elemsize); +extern void * stbds_hmget_key(void *a, size_t elemsize, void *key, size_t keysize, int mode); +extern void * stbds_hmget_key_ts(void *a, size_t elemsize, void *key, size_t keysize, ptrdiff_t *temp, int mode); +extern void * stbds_hmput_default(void *a, size_t elemsize); +extern void * stbds_hmput_key(void *a, size_t elemsize, void *key, size_t keysize, int mode); +extern void * stbds_hmdel_key(void *a, size_t elemsize, void *key, size_t keysize, size_t keyoffset, int mode); +extern void * stbds_shmode_func(size_t elemsize, int mode); + +#ifdef __cplusplus +} +#endif + +#if defined(__GNUC__) || defined(__clang__) +#define STBDS_HAS_TYPEOF +#ifdef __cplusplus +//#define STBDS_HAS_LITERAL_ARRAY // this is currently broken for clang +#endif +#endif + +#if !defined(__cplusplus) +#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L +#define STBDS_HAS_LITERAL_ARRAY +#endif +#endif + +// this macro takes the address of the argument, but on gcc/clang can accept rvalues +#if defined(STBDS_HAS_LITERAL_ARRAY) && defined(STBDS_HAS_TYPEOF) + #if __clang__ + #define STBDS_ADDRESSOF(typevar, value) ((__typeof__(typevar)[1]){value}) // literal array decays to pointer to value + #else + #define STBDS_ADDRESSOF(typevar, value) ((typeof(typevar)[1]){value}) // literal array decays to pointer to value + #endif +#else +#define STBDS_ADDRESSOF(typevar, value) &(value) +#endif + +#define STBDS_OFFSETOF(var,field) ((char *) &(var)->field - (char *) (var)) + +#define stbds_header(t) ((stbds_array_header *) (t) - 1) +#define stbds_temp(t) stbds_header(t)->temp +#define stbds_temp_key(t) (*(char **) stbds_header(t)->hash_table) + +#define stbds_arrsetcap(a,n) (stbds_arrgrow(a,0,n)) +#define stbds_arrsetlen(a,n) ((stbds_arrcap(a) < (size_t) (n) ? stbds_arrsetcap((a),(size_t)(n)),0 : 0), (a) ? stbds_header(a)->length = (size_t) (n) : 0) +#define stbds_arrcap(a) ((a) ? stbds_header(a)->capacity : 0) +#define stbds_arrlen(a) ((a) ? (ptrdiff_t) stbds_header(a)->length : 0) +#define stbds_arrlenu(a) ((a) ? stbds_header(a)->length : 0) +#define stbds_arrput(a,v) (stbds_arrmaybegrow(a,1), (a)[stbds_header(a)->length++] = (v)) +#define stbds_arrpush stbds_arrput // synonym +#define stbds_arrpop(a) (stbds_header(a)->length--, (a)[stbds_header(a)->length]) +#define stbds_arraddn(a,n) ((void)(stbds_arraddnindex(a, n))) // deprecated, use one of the following instead: +#define stbds_arraddnptr(a,n) (stbds_arrmaybegrow(a,n), (n) ? (stbds_header(a)->length += (n), &(a)[stbds_header(a)->length-(n)]) : (a)) +#define stbds_arraddnindex(a,n)(stbds_arrmaybegrow(a,n), (n) ? (stbds_header(a)->length += (n), stbds_header(a)->length-(n)) : stbds_arrlen(a)) +#define stbds_arraddnoff stbds_arraddnindex +#define stbds_arrlast(a) ((a)[stbds_header(a)->length-1]) +#define stbds_arrfree(a) ((void) ((a) ? STBDS_FREE(NULL,stbds_header(a)) : (void)0), (a)=NULL) +#define stbds_arrdel(a,i) stbds_arrdeln(a,i,1) +#define stbds_arrdeln(a,i,n) (memmove(&(a)[i], &(a)[(i)+(n)], sizeof *(a) * (stbds_header(a)->length-(n)-(i))), stbds_header(a)->length -= (n)) +#define stbds_arrdelswap(a,i) ((a)[i] = stbds_arrlast(a), stbds_header(a)->length -= 1) +#define stbds_arrinsn(a,i,n) (stbds_arraddn((a),(n)), memmove(&(a)[(i)+(n)], &(a)[i], sizeof *(a) * (stbds_header(a)->length-(n)-(i)))) +#define stbds_arrins(a,i,v) (stbds_arrinsn((a),(i),1), (a)[i]=(v)) + +#define stbds_arrmaybegrow(a,n) ((!(a) || stbds_header(a)->length + (n) > stbds_header(a)->capacity) \ + ? (stbds_arrgrow(a,n,0),0) : 0) + +#define stbds_arrgrow(a,b,c) ((a) = stbds_arrgrowf_wrapper((a), sizeof *(a), (b), (c))) + +#define stbds_hmput(t, k, v) \ + ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) STBDS_ADDRESSOF((t)->key, (k)), sizeof (t)->key, 0), \ + (t)[stbds_temp((t)-1)].key = (k), \ + (t)[stbds_temp((t)-1)].value = (v)) + +#define stbds_hmputs(t, s) \ + ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), &(s).key, sizeof (s).key, STBDS_HM_BINARY), \ + (t)[stbds_temp((t)-1)] = (s)) + +#define stbds_hmgeti(t,k) \ + ((t) = stbds_hmget_key_wrapper((t), sizeof *(t), (void*) STBDS_ADDRESSOF((t)->key, (k)), sizeof (t)->key, STBDS_HM_BINARY), \ + stbds_temp((t)-1)) + +#define stbds_hmgeti_ts(t,k,temp) \ + ((t) = stbds_hmget_key_ts_wrapper((t), sizeof *(t), (void*) STBDS_ADDRESSOF((t)->key, (k)), sizeof (t)->key, &(temp), STBDS_HM_BINARY), \ + (temp)) + +#define stbds_hmgetp(t, k) \ + ((void) stbds_hmgeti(t,k), &(t)[stbds_temp((t)-1)]) + +#define stbds_hmgetp_ts(t, k, temp) \ + ((void) stbds_hmgeti_ts(t,k,temp), &(t)[temp]) + +#define stbds_hmdel(t,k) \ + (((t) = stbds_hmdel_key_wrapper((t),sizeof *(t), (void*) STBDS_ADDRESSOF((t)->key, (k)), sizeof (t)->key, STBDS_OFFSETOF((t),key), STBDS_HM_BINARY)),(t)?stbds_temp((t)-1):0) + +#define stbds_hmdefault(t, v) \ + ((t) = stbds_hmput_default_wrapper((t), sizeof *(t)), (t)[-1].value = (v)) + +#define stbds_hmdefaults(t, s) \ + ((t) = stbds_hmput_default_wrapper((t), sizeof *(t)), (t)[-1] = (s)) + +#define stbds_hmfree(p) \ + ((void) ((p) != NULL ? stbds_hmfree_func((p)-1,sizeof*(p)),0 : 0),(p)=NULL) + +#define stbds_hmgets(t, k) (*stbds_hmgetp(t,k)) +#define stbds_hmget(t, k) (stbds_hmgetp(t,k)->value) +#define stbds_hmget_ts(t, k, temp) (stbds_hmgetp_ts(t,k,temp)->value) +#define stbds_hmlen(t) ((t) ? (ptrdiff_t) stbds_header((t)-1)->length-1 : 0) +#define stbds_hmlenu(t) ((t) ? stbds_header((t)-1)->length-1 : 0) +#define stbds_hmgetp_null(t,k) (stbds_hmgeti(t,k) == -1 ? NULL : &(t)[stbds_temp((t)-1)]) + +#define stbds_shput(t, k, v) \ + ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) (k), sizeof (t)->key, STBDS_HM_STRING), \ + (t)[stbds_temp((t)-1)].value = (v)) + +#define stbds_shputi(t, k, v) \ + ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) (k), sizeof (t)->key, STBDS_HM_STRING), \ + (t)[stbds_temp((t)-1)].value = (v), stbds_temp((t)-1)) + +#define stbds_shputs(t, s) \ + ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) (s).key, sizeof (s).key, STBDS_HM_STRING), \ + (t)[stbds_temp((t)-1)] = (s), \ + (t)[stbds_temp((t)-1)].key = stbds_temp_key((t)-1)) // above line overwrites whole structure, so must rewrite key here if it was allocated internally + +#define stbds_pshput(t, p) \ + ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) (p)->key, sizeof (p)->key, STBDS_HM_PTR_TO_STRING), \ + (t)[stbds_temp((t)-1)] = (p)) + +#define stbds_shgeti(t,k) \ + ((t) = stbds_hmget_key_wrapper((t), sizeof *(t), (void*) (k), sizeof (t)->key, STBDS_HM_STRING), \ + stbds_temp((t)-1)) + +#define stbds_pshgeti(t,k) \ + ((t) = stbds_hmget_key_wrapper((t), sizeof *(t), (void*) (k), sizeof (*(t))->key, STBDS_HM_PTR_TO_STRING), \ + stbds_temp((t)-1)) + +#define stbds_shgetp(t, k) \ + ((void) stbds_shgeti(t,k), &(t)[stbds_temp((t)-1)]) + +#define stbds_pshget(t, k) \ + ((void) stbds_pshgeti(t,k), (t)[stbds_temp((t)-1)]) + +#define stbds_shdel(t,k) \ + (((t) = stbds_hmdel_key_wrapper((t),sizeof *(t), (void*) (k), sizeof (t)->key, STBDS_OFFSETOF((t),key), STBDS_HM_STRING)),(t)?stbds_temp((t)-1):0) +#define stbds_pshdel(t,k) \ + (((t) = stbds_hmdel_key_wrapper((t),sizeof *(t), (void*) (k), sizeof (*(t))->key, STBDS_OFFSETOF(*(t),key), STBDS_HM_PTR_TO_STRING)),(t)?stbds_temp((t)-1):0) + +#define stbds_sh_new_arena(t) \ + ((t) = stbds_shmode_func_wrapper(t, sizeof *(t), STBDS_SH_ARENA)) +#define stbds_sh_new_strdup(t) \ + ((t) = stbds_shmode_func_wrapper(t, sizeof *(t), STBDS_SH_STRDUP)) + +#define stbds_shdefault(t, v) stbds_hmdefault(t,v) +#define stbds_shdefaults(t, s) stbds_hmdefaults(t,s) + +#define stbds_shfree stbds_hmfree +#define stbds_shlenu stbds_hmlenu + +#define stbds_shgets(t, k) (*stbds_shgetp(t,k)) +#define stbds_shget(t, k) (stbds_shgetp(t,k)->value) +#define stbds_shgetp_null(t,k) (stbds_shgeti(t,k) == -1 ? NULL : &(t)[stbds_temp((t)-1)]) +#define stbds_shlen stbds_hmlen + +typedef struct +{ + size_t length; + size_t capacity; + void * hash_table; + ptrdiff_t temp; +} stbds_array_header; + +typedef struct stbds_string_block +{ + struct stbds_string_block *next; + char storage[8]; +} stbds_string_block; + +struct stbds_string_arena +{ + stbds_string_block *storage; + size_t remaining; + unsigned char block; + unsigned char mode; // this isn't used by the string arena itself +}; + +#define STBDS_HM_BINARY 0 +#define STBDS_HM_STRING 1 + +enum +{ + STBDS_SH_NONE, + STBDS_SH_DEFAULT, + STBDS_SH_STRDUP, + STBDS_SH_ARENA +}; + +#ifdef __cplusplus +// in C we use implicit assignment from these void*-returning functions to T*. +// in C++ these templates make the same code work +template static T * stbds_arrgrowf_wrapper(T *a, size_t elemsize, size_t addlen, size_t min_cap) { + return (T*)stbds_arrgrowf((void *)a, elemsize, addlen, min_cap); +} +template static T * stbds_hmget_key_wrapper(T *a, size_t elemsize, void *key, size_t keysize, int mode) { + return (T*)stbds_hmget_key((void*)a, elemsize, key, keysize, mode); +} +template static T * stbds_hmget_key_ts_wrapper(T *a, size_t elemsize, void *key, size_t keysize, ptrdiff_t *temp, int mode) { + return (T*)stbds_hmget_key_ts((void*)a, elemsize, key, keysize, temp, mode); +} +template static T * stbds_hmput_default_wrapper(T *a, size_t elemsize) { + return (T*)stbds_hmput_default((void *)a, elemsize); +} +template static T * stbds_hmput_key_wrapper(T *a, size_t elemsize, void *key, size_t keysize, int mode) { + return (T*)stbds_hmput_key((void*)a, elemsize, key, keysize, mode); +} +template static T * stbds_hmdel_key_wrapper(T *a, size_t elemsize, void *key, size_t keysize, size_t keyoffset, int mode){ + return (T*)stbds_hmdel_key((void*)a, elemsize, key, keysize, keyoffset, mode); +} +template static T * stbds_shmode_func_wrapper(T *, size_t elemsize, int mode) { + return (T*)stbds_shmode_func(elemsize, mode); +} +#else +#define stbds_arrgrowf_wrapper stbds_arrgrowf +#define stbds_hmget_key_wrapper stbds_hmget_key +#define stbds_hmget_key_ts_wrapper stbds_hmget_key_ts +#define stbds_hmput_default_wrapper stbds_hmput_default +#define stbds_hmput_key_wrapper stbds_hmput_key +#define stbds_hmdel_key_wrapper stbds_hmdel_key +#define stbds_shmode_func_wrapper(t,e,m) stbds_shmode_func(e,m) +#endif + +#endif // INCLUDE_STB_DS_H + + +////////////////////////////////////////////////////////////////////////////// +// +// IMPLEMENTATION +// + +#ifdef STB_DS_IMPLEMENTATION +#include +#include + +#ifndef STBDS_ASSERT +#define STBDS_ASSERT_WAS_UNDEFINED +#define STBDS_ASSERT(x) ((void) 0) +#endif + +#ifdef STBDS_STATISTICS +#define STBDS_STATS(x) x +size_t stbds_array_grow; +size_t stbds_hash_grow; +size_t stbds_hash_shrink; +size_t stbds_hash_rebuild; +size_t stbds_hash_probes; +size_t stbds_hash_alloc; +size_t stbds_rehash_probes; +size_t stbds_rehash_items; +#else +#define STBDS_STATS(x) +#endif + +// +// stbds_arr implementation +// + +//int *prev_allocs[65536]; +//int num_prev; + +void *stbds_arrgrowf(void *a, size_t elemsize, size_t addlen, size_t min_cap) +{ + stbds_array_header temp={0}; // force debugging + void *b; + size_t min_len = stbds_arrlen(a) + addlen; + (void) sizeof(temp); + + // compute the minimum capacity needed + if (min_len > min_cap) + min_cap = min_len; + + if (min_cap <= stbds_arrcap(a)) + return a; + + // increase needed capacity to guarantee O(1) amortized + if (min_cap < 2 * stbds_arrcap(a)) + min_cap = 2 * stbds_arrcap(a); + else if (min_cap < 4) + min_cap = 4; + + //if (num_prev < 65536) if (a) prev_allocs[num_prev++] = (int *) ((char *) a+1); + //if (num_prev == 2201) + // num_prev = num_prev; + b = STBDS_REALLOC(NULL, (a) ? stbds_header(a) : 0, elemsize * min_cap + sizeof(stbds_array_header)); + //if (num_prev < 65536) prev_allocs[num_prev++] = (int *) (char *) b; + b = (char *) b + sizeof(stbds_array_header); + if (a == NULL) { + stbds_header(b)->length = 0; + stbds_header(b)->hash_table = 0; + stbds_header(b)->temp = 0; + } else { + STBDS_STATS(++stbds_array_grow); + } + stbds_header(b)->capacity = min_cap; + + return b; +} + +void stbds_arrfreef(void *a) +{ + STBDS_FREE(NULL, stbds_header(a)); +} + +// +// stbds_hm hash table implementation +// + +#ifdef STBDS_INTERNAL_SMALL_BUCKET +#define STBDS_BUCKET_LENGTH 4 +#else +#define STBDS_BUCKET_LENGTH 8 +#endif + +#define STBDS_BUCKET_SHIFT (STBDS_BUCKET_LENGTH == 8 ? 3 : 2) +#define STBDS_BUCKET_MASK (STBDS_BUCKET_LENGTH-1) +#define STBDS_CACHE_LINE_SIZE 64 + +#define STBDS_ALIGN_FWD(n,a) (((n) + (a) - 1) & ~((a)-1)) + +typedef struct +{ + size_t hash [STBDS_BUCKET_LENGTH]; + ptrdiff_t index[STBDS_BUCKET_LENGTH]; +} stbds_hash_bucket; // in 32-bit, this is one 64-byte cache line; in 64-bit, each array is one 64-byte cache line + +typedef struct +{ + char * temp_key; // this MUST be the first field of the hash table + size_t slot_count; + size_t used_count; + size_t used_count_threshold; + size_t used_count_shrink_threshold; + size_t tombstone_count; + size_t tombstone_count_threshold; + size_t seed; + size_t slot_count_log2; + stbds_string_arena string; + stbds_hash_bucket *storage; // not a separate allocation, just 64-byte aligned storage after this struct +} stbds_hash_index; + +#define STBDS_INDEX_EMPTY -1 +#define STBDS_INDEX_DELETED -2 +#define STBDS_INDEX_IN_USE(x) ((x) >= 0) + +#define STBDS_HASH_EMPTY 0 +#define STBDS_HASH_DELETED 1 + +static size_t stbds_hash_seed=0x31415926; + +void stbds_rand_seed(size_t seed) +{ + stbds_hash_seed = seed; +} + +#define stbds_load_32_or_64(var, temp, v32, v64_hi, v64_lo) \ + temp = v64_lo ^ v32, temp <<= 16, temp <<= 16, temp >>= 16, temp >>= 16, /* discard if 32-bit */ \ + var = v64_hi, var <<= 16, var <<= 16, /* discard if 32-bit */ \ + var ^= temp ^ v32 + +#define STBDS_SIZE_T_BITS ((sizeof (size_t)) * 8) + +static size_t stbds_probe_position(size_t hash, size_t slot_count, size_t slot_log2) +{ + size_t pos; + STBDS_NOTUSED(slot_log2); + pos = hash & (slot_count-1); + #ifdef STBDS_INTERNAL_BUCKET_START + pos &= ~STBDS_BUCKET_MASK; + #endif + return pos; +} + +static size_t stbds_log2(size_t slot_count) +{ + size_t n=0; + while (slot_count > 1) { + slot_count >>= 1; + ++n; + } + return n; +} + +static stbds_hash_index *stbds_make_hash_index(size_t slot_count, stbds_hash_index *ot) +{ + stbds_hash_index *t; + t = (stbds_hash_index *) STBDS_REALLOC(NULL,0,(slot_count >> STBDS_BUCKET_SHIFT) * sizeof(stbds_hash_bucket) + sizeof(stbds_hash_index) + STBDS_CACHE_LINE_SIZE-1); + t->storage = (stbds_hash_bucket *) STBDS_ALIGN_FWD((size_t) (t+1), STBDS_CACHE_LINE_SIZE); + t->slot_count = slot_count; + t->slot_count_log2 = stbds_log2(slot_count); + t->tombstone_count = 0; + t->used_count = 0; + + #if 0 // A1 + t->used_count_threshold = slot_count*12/16; // if 12/16th of table is occupied, grow + t->tombstone_count_threshold = slot_count* 2/16; // if tombstones are 2/16th of table, rebuild + t->used_count_shrink_threshold = slot_count* 4/16; // if table is only 4/16th full, shrink + #elif 1 // A2 + //t->used_count_threshold = slot_count*12/16; // if 12/16th of table is occupied, grow + //t->tombstone_count_threshold = slot_count* 3/16; // if tombstones are 3/16th of table, rebuild + //t->used_count_shrink_threshold = slot_count* 4/16; // if table is only 4/16th full, shrink + + // compute without overflowing + t->used_count_threshold = slot_count - (slot_count>>2); + t->tombstone_count_threshold = (slot_count>>3) + (slot_count>>4); + t->used_count_shrink_threshold = slot_count >> 2; + + #elif 0 // B1 + t->used_count_threshold = slot_count*13/16; // if 13/16th of table is occupied, grow + t->tombstone_count_threshold = slot_count* 2/16; // if tombstones are 2/16th of table, rebuild + t->used_count_shrink_threshold = slot_count* 5/16; // if table is only 5/16th full, shrink + #else // C1 + t->used_count_threshold = slot_count*14/16; // if 14/16th of table is occupied, grow + t->tombstone_count_threshold = slot_count* 2/16; // if tombstones are 2/16th of table, rebuild + t->used_count_shrink_threshold = slot_count* 6/16; // if table is only 6/16th full, shrink + #endif + // Following statistics were measured on a Core i7-6700 @ 4.00Ghz, compiled with clang 7.0.1 -O2 + // Note that the larger tables have high variance as they were run fewer times + // A1 A2 B1 C1 + // 0.10ms : 0.10ms : 0.10ms : 0.11ms : 2,000 inserts creating 2K table + // 0.96ms : 0.95ms : 0.97ms : 1.04ms : 20,000 inserts creating 20K table + // 14.48ms : 14.46ms : 10.63ms : 11.00ms : 200,000 inserts creating 200K table + // 195.74ms : 196.35ms : 203.69ms : 214.92ms : 2,000,000 inserts creating 2M table + // 2193.88ms : 2209.22ms : 2285.54ms : 2437.17ms : 20,000,000 inserts creating 20M table + // 65.27ms : 53.77ms : 65.33ms : 65.47ms : 500,000 inserts & deletes in 2K table + // 72.78ms : 62.45ms : 71.95ms : 72.85ms : 500,000 inserts & deletes in 20K table + // 89.47ms : 77.72ms : 96.49ms : 96.75ms : 500,000 inserts & deletes in 200K table + // 97.58ms : 98.14ms : 97.18ms : 97.53ms : 500,000 inserts & deletes in 2M table + // 118.61ms : 119.62ms : 120.16ms : 118.86ms : 500,000 inserts & deletes in 20M table + // 192.11ms : 194.39ms : 196.38ms : 195.73ms : 500,000 inserts & deletes in 200M table + + if (slot_count <= STBDS_BUCKET_LENGTH) + t->used_count_shrink_threshold = 0; + // to avoid infinite loop, we need to guarantee that at least one slot is empty and will terminate probes + STBDS_ASSERT(t->used_count_threshold + t->tombstone_count_threshold < t->slot_count); + STBDS_STATS(++stbds_hash_alloc); + if (ot) { + t->string = ot->string; + // reuse old seed so we can reuse old hashes so below "copy out old data" doesn't do any hashing + t->seed = ot->seed; + } else { + size_t a,b,temp; + memset(&t->string, 0, sizeof(t->string)); + t->seed = stbds_hash_seed; + // LCG + // in 32-bit, a = 2147001325 b = 715136305 + // in 64-bit, a = 2862933555777941757 b = 3037000493 + stbds_load_32_or_64(a,temp, 2147001325, 0x27bb2ee6, 0x87b0b0fd); + stbds_load_32_or_64(b,temp, 715136305, 0, 0xb504f32d); + stbds_hash_seed = stbds_hash_seed * a + b; + } + + { + size_t i,j; + for (i=0; i < slot_count >> STBDS_BUCKET_SHIFT; ++i) { + stbds_hash_bucket *b = &t->storage[i]; + for (j=0; j < STBDS_BUCKET_LENGTH; ++j) + b->hash[j] = STBDS_HASH_EMPTY; + for (j=0; j < STBDS_BUCKET_LENGTH; ++j) + b->index[j] = STBDS_INDEX_EMPTY; + } + } + + // copy out the old data, if any + if (ot) { + size_t i,j; + t->used_count = ot->used_count; + for (i=0; i < ot->slot_count >> STBDS_BUCKET_SHIFT; ++i) { + stbds_hash_bucket *ob = &ot->storage[i]; + for (j=0; j < STBDS_BUCKET_LENGTH; ++j) { + if (STBDS_INDEX_IN_USE(ob->index[j])) { + size_t hash = ob->hash[j]; + size_t pos = stbds_probe_position(hash, t->slot_count, t->slot_count_log2); + size_t step = STBDS_BUCKET_LENGTH; + STBDS_STATS(++stbds_rehash_items); + for (;;) { + size_t limit,z; + stbds_hash_bucket *bucket; + bucket = &t->storage[pos >> STBDS_BUCKET_SHIFT]; + STBDS_STATS(++stbds_rehash_probes); + + for (z=pos & STBDS_BUCKET_MASK; z < STBDS_BUCKET_LENGTH; ++z) { + if (bucket->hash[z] == 0) { + bucket->hash[z] = hash; + bucket->index[z] = ob->index[j]; + goto done; + } + } + + limit = pos & STBDS_BUCKET_MASK; + for (z = 0; z < limit; ++z) { + if (bucket->hash[z] == 0) { + bucket->hash[z] = hash; + bucket->index[z] = ob->index[j]; + goto done; + } + } + + pos += step; // quadratic probing + step += STBDS_BUCKET_LENGTH; + pos &= (t->slot_count-1); + } + } + done: + ; + } + } + } + + return t; +} + +#define STBDS_ROTATE_LEFT(val, n) (((val) << (n)) | ((val) >> (STBDS_SIZE_T_BITS - (n)))) +#define STBDS_ROTATE_RIGHT(val, n) (((val) >> (n)) | ((val) << (STBDS_SIZE_T_BITS - (n)))) + +size_t stbds_hash_string(char *str, size_t seed) +{ + size_t hash = seed; + while (*str) + hash = STBDS_ROTATE_LEFT(hash, 9) + (unsigned char) *str++; + + // Thomas Wang 64-to-32 bit mix function, hopefully also works in 32 bits + hash ^= seed; + hash = (~hash) + (hash << 18); + hash ^= hash ^ STBDS_ROTATE_RIGHT(hash,31); + hash = hash * 21; + hash ^= hash ^ STBDS_ROTATE_RIGHT(hash,11); + hash += (hash << 6); + hash ^= STBDS_ROTATE_RIGHT(hash,22); + return hash+seed; +} + +#ifdef STBDS_SIPHASH_2_4 +#define STBDS_SIPHASH_C_ROUNDS 2 +#define STBDS_SIPHASH_D_ROUNDS 4 +typedef int STBDS_SIPHASH_2_4_can_only_be_used_in_64_bit_builds[sizeof(size_t) == 8 ? 1 : -1]; +#endif + +#ifndef STBDS_SIPHASH_C_ROUNDS +#define STBDS_SIPHASH_C_ROUNDS 1 +#endif +#ifndef STBDS_SIPHASH_D_ROUNDS +#define STBDS_SIPHASH_D_ROUNDS 1 +#endif + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable:4127) // conditional expression is constant, for do..while(0) and sizeof()== +#endif + +static size_t stbds_siphash_bytes(void *p, size_t len, size_t seed) +{ + unsigned char *d = (unsigned char *) p; + size_t i,j; + size_t v0,v1,v2,v3, data; + + // hash that works on 32- or 64-bit registers without knowing which we have + // (computes different results on 32-bit and 64-bit platform) + // derived from siphash, but on 32-bit platforms very different as it uses 4 32-bit state not 4 64-bit + v0 = ((((size_t) 0x736f6d65 << 16) << 16) + 0x70736575) ^ seed; + v1 = ((((size_t) 0x646f7261 << 16) << 16) + 0x6e646f6d) ^ ~seed; + v2 = ((((size_t) 0x6c796765 << 16) << 16) + 0x6e657261) ^ seed; + v3 = ((((size_t) 0x74656462 << 16) << 16) + 0x79746573) ^ ~seed; + + #ifdef STBDS_TEST_SIPHASH_2_4 + // hardcoded with key material in the siphash test vectors + v0 ^= 0x0706050403020100ull ^ seed; + v1 ^= 0x0f0e0d0c0b0a0908ull ^ ~seed; + v2 ^= 0x0706050403020100ull ^ seed; + v3 ^= 0x0f0e0d0c0b0a0908ull ^ ~seed; + #endif + + #define STBDS_SIPROUND() \ + do { \ + v0 += v1; v1 = STBDS_ROTATE_LEFT(v1, 13); v1 ^= v0; v0 = STBDS_ROTATE_LEFT(v0,STBDS_SIZE_T_BITS/2); \ + v2 += v3; v3 = STBDS_ROTATE_LEFT(v3, 16); v3 ^= v2; \ + v2 += v1; v1 = STBDS_ROTATE_LEFT(v1, 17); v1 ^= v2; v2 = STBDS_ROTATE_LEFT(v2,STBDS_SIZE_T_BITS/2); \ + v0 += v3; v3 = STBDS_ROTATE_LEFT(v3, 21); v3 ^= v0; \ + } while (0) + + for (i=0; i+sizeof(size_t) <= len; i += sizeof(size_t), d += sizeof(size_t)) { + data = d[0] | (d[1] << 8) | (d[2] << 16) | (d[3] << 24); + data |= (size_t) (d[4] | (d[5] << 8) | (d[6] << 16) | (d[7] << 24)) << 16 << 16; // discarded if size_t == 4 + + v3 ^= data; + for (j=0; j < STBDS_SIPHASH_C_ROUNDS; ++j) + STBDS_SIPROUND(); + v0 ^= data; + } + data = len << (STBDS_SIZE_T_BITS-8); + switch (len - i) { + case 7: data |= ((size_t) d[6] << 24) << 24; // fall through + case 6: data |= ((size_t) d[5] << 20) << 20; // fall through + case 5: data |= ((size_t) d[4] << 16) << 16; // fall through + case 4: data |= (d[3] << 24); // fall through + case 3: data |= (d[2] << 16); // fall through + case 2: data |= (d[1] << 8); // fall through + case 1: data |= d[0]; // fall through + case 0: break; + } + v3 ^= data; + for (j=0; j < STBDS_SIPHASH_C_ROUNDS; ++j) + STBDS_SIPROUND(); + v0 ^= data; + v2 ^= 0xff; + for (j=0; j < STBDS_SIPHASH_D_ROUNDS; ++j) + STBDS_SIPROUND(); + +#ifdef STBDS_SIPHASH_2_4 + return v0^v1^v2^v3; +#else + return v1^v2^v3; // slightly stronger since v0^v3 in above cancels out final round operation? I tweeted at the authors of SipHash about this but they didn't reply +#endif +} + +size_t stbds_hash_bytes(void *p, size_t len, size_t seed) +{ +#ifdef STBDS_SIPHASH_2_4 + return stbds_siphash_bytes(p,len,seed); +#else + unsigned char *d = (unsigned char *) p; + + if (len == 4) { + unsigned int hash = d[0] | (d[1] << 8) | (d[2] << 16) | (d[3] << 24); + #if 0 + // HASH32-A Bob Jenkin's hash function w/o large constants + hash ^= seed; + hash -= (hash<<6); + hash ^= (hash>>17); + hash -= (hash<<9); + hash ^= seed; + hash ^= (hash<<4); + hash -= (hash<<3); + hash ^= (hash<<10); + hash ^= (hash>>15); + #elif 1 + // HASH32-BB Bob Jenkin's presumably-accidental version of Thomas Wang hash with rotates turned into shifts. + // Note that converting these back to rotates makes it run a lot slower, presumably due to collisions, so I'm + // not really sure what's going on. + hash ^= seed; + hash = (hash ^ 61) ^ (hash >> 16); + hash = hash + (hash << 3); + hash = hash ^ (hash >> 4); + hash = hash * 0x27d4eb2d; + hash ^= seed; + hash = hash ^ (hash >> 15); + #else // HASH32-C - Murmur3 + hash ^= seed; + hash *= 0xcc9e2d51; + hash = (hash << 17) | (hash >> 15); + hash *= 0x1b873593; + hash ^= seed; + hash = (hash << 19) | (hash >> 13); + hash = hash*5 + 0xe6546b64; + hash ^= hash >> 16; + hash *= 0x85ebca6b; + hash ^= seed; + hash ^= hash >> 13; + hash *= 0xc2b2ae35; + hash ^= hash >> 16; + #endif + // Following statistics were measured on a Core i7-6700 @ 4.00Ghz, compiled with clang 7.0.1 -O2 + // Note that the larger tables have high variance as they were run fewer times + // HASH32-A // HASH32-BB // HASH32-C + // 0.10ms // 0.10ms // 0.10ms : 2,000 inserts creating 2K table + // 0.96ms // 0.95ms // 0.99ms : 20,000 inserts creating 20K table + // 14.69ms // 14.43ms // 14.97ms : 200,000 inserts creating 200K table + // 199.99ms // 195.36ms // 202.05ms : 2,000,000 inserts creating 2M table + // 2234.84ms // 2187.74ms // 2240.38ms : 20,000,000 inserts creating 20M table + // 55.68ms // 53.72ms // 57.31ms : 500,000 inserts & deletes in 2K table + // 63.43ms // 61.99ms // 65.73ms : 500,000 inserts & deletes in 20K table + // 80.04ms // 77.96ms // 81.83ms : 500,000 inserts & deletes in 200K table + // 100.42ms // 97.40ms // 102.39ms : 500,000 inserts & deletes in 2M table + // 119.71ms // 120.59ms // 121.63ms : 500,000 inserts & deletes in 20M table + // 185.28ms // 195.15ms // 187.74ms : 500,000 inserts & deletes in 200M table + // 15.58ms // 14.79ms // 15.52ms : 200,000 inserts creating 200K table with varying key spacing + + return (((size_t) hash << 16 << 16) | hash) ^ seed; + } else if (len == 8 && sizeof(size_t) == 8) { + size_t hash = d[0] | (d[1] << 8) | (d[2] << 16) | (d[3] << 24); + hash |= (size_t) (d[4] | (d[5] << 8) | (d[6] << 16) | (d[7] << 24)) << 16 << 16; // avoid warning if size_t == 4 + hash ^= seed; + hash = (~hash) + (hash << 21); + hash ^= STBDS_ROTATE_RIGHT(hash,24); + hash *= 265; + hash ^= STBDS_ROTATE_RIGHT(hash,14); + hash ^= seed; + hash *= 21; + hash ^= STBDS_ROTATE_RIGHT(hash,28); + hash += (hash << 31); + hash = (~hash) + (hash << 18); + return hash; + } else { + return stbds_siphash_bytes(p,len,seed); + } +#endif +} +#ifdef _MSC_VER +#pragma warning(pop) +#endif + + +static int stbds_is_key_equal(void *a, size_t elemsize, void *key, size_t keysize, size_t keyoffset, int mode, size_t i) +{ + if (mode >= STBDS_HM_STRING) + return 0==strcmp((char *) key, * (char **) ((char *) a + elemsize*i + keyoffset)); + else + return 0==memcmp(key, (char *) a + elemsize*i + keyoffset, keysize); +} + +#define STBDS_HASH_TO_ARR(x,elemsize) ((char*) (x) - (elemsize)) +#define STBDS_ARR_TO_HASH(x,elemsize) ((char*) (x) + (elemsize)) + +#define stbds_hash_table(a) ((stbds_hash_index *) stbds_header(a)->hash_table) + +void stbds_hmfree_func(void *a, size_t elemsize) +{ + if (a == NULL) return; + if (stbds_hash_table(a) != NULL) { + if (stbds_hash_table(a)->string.mode == STBDS_SH_STRDUP) { + size_t i; + // skip 0th element, which is default + for (i=1; i < stbds_header(a)->length; ++i) + STBDS_FREE(NULL, *(char**) ((char *) a + elemsize*i)); + } + stbds_strreset(&stbds_hash_table(a)->string); + } + STBDS_FREE(NULL, stbds_header(a)->hash_table); + STBDS_FREE(NULL, stbds_header(a)); +} + +static ptrdiff_t stbds_hm_find_slot(void *a, size_t elemsize, void *key, size_t keysize, size_t keyoffset, int mode) +{ + void *raw_a = STBDS_HASH_TO_ARR(a,elemsize); + stbds_hash_index *table = stbds_hash_table(raw_a); + size_t hash = mode >= STBDS_HM_STRING ? stbds_hash_string((char*)key,table->seed) : stbds_hash_bytes(key, keysize,table->seed); + size_t step = STBDS_BUCKET_LENGTH; + size_t limit,i; + size_t pos; + stbds_hash_bucket *bucket; + + if (hash < 2) hash += 2; // stored hash values are forbidden from being 0, so we can detect empty slots + + pos = stbds_probe_position(hash, table->slot_count, table->slot_count_log2); + + for (;;) { + STBDS_STATS(++stbds_hash_probes); + bucket = &table->storage[pos >> STBDS_BUCKET_SHIFT]; + + // start searching from pos to end of bucket, this should help performance on small hash tables that fit in cache + for (i=pos & STBDS_BUCKET_MASK; i < STBDS_BUCKET_LENGTH; ++i) { + if (bucket->hash[i] == hash) { + if (stbds_is_key_equal(a, elemsize, key, keysize, keyoffset, mode, bucket->index[i])) { + return (pos & ~STBDS_BUCKET_MASK)+i; + } + } else if (bucket->hash[i] == STBDS_HASH_EMPTY) { + return -1; + } + } + + // search from beginning of bucket to pos + limit = pos & STBDS_BUCKET_MASK; + for (i = 0; i < limit; ++i) { + if (bucket->hash[i] == hash) { + if (stbds_is_key_equal(a, elemsize, key, keysize, keyoffset, mode, bucket->index[i])) { + return (pos & ~STBDS_BUCKET_MASK)+i; + } + } else if (bucket->hash[i] == STBDS_HASH_EMPTY) { + return -1; + } + } + + // quadratic probing + pos += step; + step += STBDS_BUCKET_LENGTH; + pos &= (table->slot_count-1); + } + /* NOTREACHED */ +} + +void * stbds_hmget_key_ts(void *a, size_t elemsize, void *key, size_t keysize, ptrdiff_t *temp, int mode) +{ + size_t keyoffset = 0; + if (a == NULL) { + // make it non-empty so we can return a temp + a = stbds_arrgrowf(0, elemsize, 0, 1); + stbds_header(a)->length += 1; + memset(a, 0, elemsize); + *temp = STBDS_INDEX_EMPTY; + // adjust a to point after the default element + return STBDS_ARR_TO_HASH(a,elemsize); + } else { + stbds_hash_index *table; + void *raw_a = STBDS_HASH_TO_ARR(a,elemsize); + // adjust a to point to the default element + table = (stbds_hash_index *) stbds_header(raw_a)->hash_table; + if (table == 0) { + *temp = -1; + } else { + ptrdiff_t slot = stbds_hm_find_slot(a, elemsize, key, keysize, keyoffset, mode); + if (slot < 0) { + *temp = STBDS_INDEX_EMPTY; + } else { + stbds_hash_bucket *b = &table->storage[slot >> STBDS_BUCKET_SHIFT]; + *temp = b->index[slot & STBDS_BUCKET_MASK]; + } + } + return a; + } +} + +void * stbds_hmget_key(void *a, size_t elemsize, void *key, size_t keysize, int mode) +{ + ptrdiff_t temp; + void *p = stbds_hmget_key_ts(a, elemsize, key, keysize, &temp, mode); + stbds_temp(STBDS_HASH_TO_ARR(p,elemsize)) = temp; + return p; +} + +void * stbds_hmput_default(void *a, size_t elemsize) +{ + // three cases: + // a is NULL <- allocate + // a has a hash table but no entries, because of shmode <- grow + // a has entries <- do nothing + if (a == NULL || stbds_header(STBDS_HASH_TO_ARR(a,elemsize))->length == 0) { + a = stbds_arrgrowf(a ? STBDS_HASH_TO_ARR(a,elemsize) : NULL, elemsize, 0, 1); + stbds_header(a)->length += 1; + memset(a, 0, elemsize); + a=STBDS_ARR_TO_HASH(a,elemsize); + } + return a; +} + +static char *stbds_strdup(char *str); + +void *stbds_hmput_key(void *a, size_t elemsize, void *key, size_t keysize, int mode) +{ + size_t keyoffset=0; + void *raw_a; + stbds_hash_index *table; + + if (a == NULL) { + a = stbds_arrgrowf(0, elemsize, 0, 1); + memset(a, 0, elemsize); + stbds_header(a)->length += 1; + // adjust a to point AFTER the default element + a = STBDS_ARR_TO_HASH(a,elemsize); + } + + // adjust a to point to the default element + raw_a = a; + a = STBDS_HASH_TO_ARR(a,elemsize); + + table = (stbds_hash_index *) stbds_header(a)->hash_table; + + if (table == NULL || table->used_count >= table->used_count_threshold) { + stbds_hash_index *nt; + size_t slot_count; + + slot_count = (table == NULL) ? STBDS_BUCKET_LENGTH : table->slot_count*2; + nt = stbds_make_hash_index(slot_count, table); + if (table) + STBDS_FREE(NULL, table); + else + nt->string.mode = mode >= STBDS_HM_STRING ? STBDS_SH_DEFAULT : 0; + stbds_header(a)->hash_table = table = nt; + STBDS_STATS(++stbds_hash_grow); + } + + // we iterate hash table explicitly because we want to track if we saw a tombstone + { + size_t hash = mode >= STBDS_HM_STRING ? stbds_hash_string((char*)key,table->seed) : stbds_hash_bytes(key, keysize,table->seed); + size_t step = STBDS_BUCKET_LENGTH; + size_t pos; + ptrdiff_t tombstone = -1; + stbds_hash_bucket *bucket; + + // stored hash values are forbidden from being 0, so we can detect empty slots to early out quickly + if (hash < 2) hash += 2; + + pos = stbds_probe_position(hash, table->slot_count, table->slot_count_log2); + + for (;;) { + size_t limit, i; + STBDS_STATS(++stbds_hash_probes); + bucket = &table->storage[pos >> STBDS_BUCKET_SHIFT]; + + // start searching from pos to end of bucket + for (i=pos & STBDS_BUCKET_MASK; i < STBDS_BUCKET_LENGTH; ++i) { + if (bucket->hash[i] == hash) { + if (stbds_is_key_equal(raw_a, elemsize, key, keysize, keyoffset, mode, bucket->index[i])) { + stbds_temp(a) = bucket->index[i]; + if (mode >= STBDS_HM_STRING) + stbds_temp_key(a) = * (char **) ((char *) raw_a + elemsize*bucket->index[i] + keyoffset); + return STBDS_ARR_TO_HASH(a,elemsize); + } + } else if (bucket->hash[i] == 0) { + pos = (pos & ~STBDS_BUCKET_MASK) + i; + goto found_empty_slot; + } else if (tombstone < 0) { + if (bucket->index[i] == STBDS_INDEX_DELETED) + tombstone = (ptrdiff_t) ((pos & ~STBDS_BUCKET_MASK) + i); + } + } + + // search from beginning of bucket to pos + limit = pos & STBDS_BUCKET_MASK; + for (i = 0; i < limit; ++i) { + if (bucket->hash[i] == hash) { + if (stbds_is_key_equal(raw_a, elemsize, key, keysize, keyoffset, mode, bucket->index[i])) { + stbds_temp(a) = bucket->index[i]; + return STBDS_ARR_TO_HASH(a,elemsize); + } + } else if (bucket->hash[i] == 0) { + pos = (pos & ~STBDS_BUCKET_MASK) + i; + goto found_empty_slot; + } else if (tombstone < 0) { + if (bucket->index[i] == STBDS_INDEX_DELETED) + tombstone = (ptrdiff_t) ((pos & ~STBDS_BUCKET_MASK) + i); + } + } + + // quadratic probing + pos += step; + step += STBDS_BUCKET_LENGTH; + pos &= (table->slot_count-1); + } + found_empty_slot: + if (tombstone >= 0) { + pos = tombstone; + --table->tombstone_count; + } + ++table->used_count; + + { + ptrdiff_t i = (ptrdiff_t) stbds_arrlen(a); + // we want to do stbds_arraddn(1), but we can't use the macros since we don't have something of the right type + if ((size_t) i+1 > stbds_arrcap(a)) + *(void **) &a = stbds_arrgrowf(a, elemsize, 1, 0); + raw_a = STBDS_ARR_TO_HASH(a,elemsize); + + STBDS_ASSERT((size_t) i+1 <= stbds_arrcap(a)); + stbds_header(a)->length = i+1; + bucket = &table->storage[pos >> STBDS_BUCKET_SHIFT]; + bucket->hash[pos & STBDS_BUCKET_MASK] = hash; + bucket->index[pos & STBDS_BUCKET_MASK] = i-1; + stbds_temp(a) = i-1; + + switch (table->string.mode) { + case STBDS_SH_STRDUP: stbds_temp_key(a) = *(char **) ((char *) a + elemsize*i) = stbds_strdup((char*) key); break; + case STBDS_SH_ARENA: stbds_temp_key(a) = *(char **) ((char *) a + elemsize*i) = stbds_stralloc(&table->string, (char*)key); break; + case STBDS_SH_DEFAULT: stbds_temp_key(a) = *(char **) ((char *) a + elemsize*i) = (char *) key; break; + default: memcpy((char *) a + elemsize*i, key, keysize); break; + } + } + return STBDS_ARR_TO_HASH(a,elemsize); + } +} + +void * stbds_shmode_func(size_t elemsize, int mode) +{ + void *a = stbds_arrgrowf(0, elemsize, 0, 1); + stbds_hash_index *h; + memset(a, 0, elemsize); + stbds_header(a)->length = 1; + stbds_header(a)->hash_table = h = (stbds_hash_index *) stbds_make_hash_index(STBDS_BUCKET_LENGTH, NULL); + h->string.mode = (unsigned char) mode; + return STBDS_ARR_TO_HASH(a,elemsize); +} + +void * stbds_hmdel_key(void *a, size_t elemsize, void *key, size_t keysize, size_t keyoffset, int mode) +{ + if (a == NULL) { + return 0; + } else { + stbds_hash_index *table; + void *raw_a = STBDS_HASH_TO_ARR(a,elemsize); + table = (stbds_hash_index *) stbds_header(raw_a)->hash_table; + stbds_temp(raw_a) = 0; + if (table == 0) { + return a; + } else { + ptrdiff_t slot; + slot = stbds_hm_find_slot(a, elemsize, key, keysize, keyoffset, mode); + if (slot < 0) + return a; + else { + stbds_hash_bucket *b = &table->storage[slot >> STBDS_BUCKET_SHIFT]; + int i = slot & STBDS_BUCKET_MASK; + ptrdiff_t old_index = b->index[i]; + ptrdiff_t final_index = (ptrdiff_t) stbds_arrlen(raw_a)-1-1; // minus one for the raw_a vs a, and minus one for 'last' + STBDS_ASSERT(slot < (ptrdiff_t) table->slot_count); + --table->used_count; + ++table->tombstone_count; + stbds_temp(raw_a) = 1; + STBDS_ASSERT(table->used_count >= 0); + //STBDS_ASSERT(table->tombstone_count < table->slot_count/4); + b->hash[i] = STBDS_HASH_DELETED; + b->index[i] = STBDS_INDEX_DELETED; + + if (mode == STBDS_HM_STRING && table->string.mode == STBDS_SH_STRDUP) + STBDS_FREE(NULL, *(char**) ((char *) a+elemsize*old_index)); + + // if indices are the same, memcpy is a no-op, but back-pointer-fixup will fail, so skip + if (old_index != final_index) { + // swap delete + memmove((char*) a + elemsize*old_index, (char*) a + elemsize*final_index, elemsize); + + // now find the slot for the last element + if (mode == STBDS_HM_STRING) + slot = stbds_hm_find_slot(a, elemsize, *(char**) ((char *) a+elemsize*old_index + keyoffset), keysize, keyoffset, mode); + else + slot = stbds_hm_find_slot(a, elemsize, (char* ) a+elemsize*old_index + keyoffset, keysize, keyoffset, mode); + STBDS_ASSERT(slot >= 0); + b = &table->storage[slot >> STBDS_BUCKET_SHIFT]; + i = slot & STBDS_BUCKET_MASK; + STBDS_ASSERT(b->index[i] == final_index); + b->index[i] = old_index; + } + stbds_header(raw_a)->length -= 1; + + if (table->used_count < table->used_count_shrink_threshold && table->slot_count > STBDS_BUCKET_LENGTH) { + stbds_header(raw_a)->hash_table = stbds_make_hash_index(table->slot_count>>1, table); + STBDS_FREE(NULL, table); + STBDS_STATS(++stbds_hash_shrink); + } else if (table->tombstone_count > table->tombstone_count_threshold) { + stbds_header(raw_a)->hash_table = stbds_make_hash_index(table->slot_count , table); + STBDS_FREE(NULL, table); + STBDS_STATS(++stbds_hash_rebuild); + } + + return a; + } + } + } + /* NOTREACHED */ +} + +static char *stbds_strdup(char *str) +{ + // to keep replaceable allocator simple, we don't want to use strdup. + // rolling our own also avoids problem of strdup vs _strdup + size_t len = strlen(str)+1; + char *p = (char*) STBDS_REALLOC(NULL, 0, len); + memmove(p, str, len); + return p; +} + +#ifndef STBDS_STRING_ARENA_BLOCKSIZE_MIN +#define STBDS_STRING_ARENA_BLOCKSIZE_MIN 512u +#endif +#ifndef STBDS_STRING_ARENA_BLOCKSIZE_MAX +#define STBDS_STRING_ARENA_BLOCKSIZE_MAX (1u<<20) +#endif + +char *stbds_stralloc(stbds_string_arena *a, char *str) +{ + char *p; + size_t len = strlen(str)+1; + if (len > a->remaining) { + // compute the next blocksize + size_t blocksize = a->block; + + // size is 512, 512, 1024, 1024, 2048, 2048, 4096, 4096, etc., so that + // there are log(SIZE) allocations to free when we destroy the table + blocksize = (size_t) (STBDS_STRING_ARENA_BLOCKSIZE_MIN) << (blocksize>>1); + + // if size is under 1M, advance to next blocktype + if (blocksize < (size_t)(STBDS_STRING_ARENA_BLOCKSIZE_MAX)) + ++a->block; + + if (len > blocksize) { + // if string is larger than blocksize, then just allocate the full size. + // note that we still advance string_block so block size will continue + // increasing, so e.g. if somebody only calls this with 1000-long strings, + // eventually the arena will start doubling and handling those as well + stbds_string_block *sb = (stbds_string_block *) STBDS_REALLOC(NULL, 0, sizeof(*sb)-8 + len); + memmove(sb->storage, str, len); + if (a->storage) { + // insert it after the first element, so that we don't waste the space there + sb->next = a->storage->next; + a->storage->next = sb; + } else { + sb->next = 0; + a->storage = sb; + a->remaining = 0; // this is redundant, but good for clarity + } + return sb->storage; + } else { + stbds_string_block *sb = (stbds_string_block *) STBDS_REALLOC(NULL, 0, sizeof(*sb)-8 + blocksize); + sb->next = a->storage; + a->storage = sb; + a->remaining = blocksize; + } + } + + STBDS_ASSERT(len <= a->remaining); + p = a->storage->storage + a->remaining - len; + a->remaining -= len; + memmove(p, str, len); + return p; +} + +void stbds_strreset(stbds_string_arena *a) +{ + stbds_string_block *x,*y; + x = a->storage; + while (x) { + y = x->next; + STBDS_FREE(NULL, x); + x = y; + } + memset(a, 0, sizeof(*a)); +} + +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// UNIT TESTS +// + +#ifdef STBDS_UNIT_TESTS +#include +#ifdef STBDS_ASSERT_WAS_UNDEFINED +#undef STBDS_ASSERT +#endif +#ifndef STBDS_ASSERT +#define STBDS_ASSERT assert +#include +#endif + +typedef struct { int key,b,c,d; } stbds_struct; +typedef struct { int key[2],b,c,d; } stbds_struct2; + +static char buffer[256]; +char *strkey(int n) +{ +#if defined(_WIN32) && defined(__STDC_WANT_SECURE_LIB__) + sprintf_s(buffer, sizeof(buffer), "test_%d", n); +#else + sprintf(buffer, "test_%d", n); +#endif + return buffer; +} + +void stbds_unit_tests(void) +{ +#if defined(_MSC_VER) && _MSC_VER <= 1200 && defined(__cplusplus) + // VC6 C++ doesn't like the template<> trick on unnamed structures, so do nothing! + STBDS_ASSERT(0); +#else + const int testsize = 100000; + const int testsize2 = testsize/20; + int *arr=NULL; + struct { int key; int value; } *intmap = NULL; + struct { char *key; int value; } *strmap = NULL, s; + struct { stbds_struct key; int value; } *map = NULL; + stbds_struct *map2 = NULL; + stbds_struct2 *map3 = NULL; + stbds_string_arena sa = { 0 }; + int key3[2] = { 1,2 }; + ptrdiff_t temp; + + int i,j; + + STBDS_ASSERT(arrlen(arr)==0); + for (i=0; i < 20000; i += 50) { + for (j=0; j < i; ++j) + arrpush(arr,j); + arrfree(arr); + } + + for (i=0; i < 4; ++i) { + arrpush(arr,1); arrpush(arr,2); arrpush(arr,3); arrpush(arr,4); + arrdel(arr,i); + arrfree(arr); + arrpush(arr,1); arrpush(arr,2); arrpush(arr,3); arrpush(arr,4); + arrdelswap(arr,i); + arrfree(arr); + } + + for (i=0; i < 5; ++i) { + arrpush(arr,1); arrpush(arr,2); arrpush(arr,3); arrpush(arr,4); + stbds_arrins(arr,i,5); + STBDS_ASSERT(arr[i] == 5); + if (i < 4) + STBDS_ASSERT(arr[4] == 4); + arrfree(arr); + } + + i = 1; + STBDS_ASSERT(hmgeti(intmap,i) == -1); + hmdefault(intmap, -2); + STBDS_ASSERT(hmgeti(intmap, i) == -1); + STBDS_ASSERT(hmget (intmap, i) == -2); + for (i=0; i < testsize; i+=2) + hmput(intmap, i, i*5); + for (i=0; i < testsize; i+=1) { + if (i & 1) STBDS_ASSERT(hmget(intmap, i) == -2 ); + else STBDS_ASSERT(hmget(intmap, i) == i*5); + if (i & 1) STBDS_ASSERT(hmget_ts(intmap, i, temp) == -2 ); + else STBDS_ASSERT(hmget_ts(intmap, i, temp) == i*5); + } + for (i=0; i < testsize; i+=2) + hmput(intmap, i, i*3); + for (i=0; i < testsize; i+=1) + if (i & 1) STBDS_ASSERT(hmget(intmap, i) == -2 ); + else STBDS_ASSERT(hmget(intmap, i) == i*3); + for (i=2; i < testsize; i+=4) + hmdel(intmap, i); // delete half the entries + for (i=0; i < testsize; i+=1) + if (i & 3) STBDS_ASSERT(hmget(intmap, i) == -2 ); + else STBDS_ASSERT(hmget(intmap, i) == i*3); + for (i=0; i < testsize; i+=1) + hmdel(intmap, i); // delete the rest of the entries + for (i=0; i < testsize; i+=1) + STBDS_ASSERT(hmget(intmap, i) == -2 ); + hmfree(intmap); + for (i=0; i < testsize; i+=2) + hmput(intmap, i, i*3); + hmfree(intmap); + + #if defined(__clang__) || defined(__GNUC__) + #ifndef __cplusplus + intmap = NULL; + hmput(intmap, 15, 7); + hmput(intmap, 11, 3); + hmput(intmap, 9, 5); + STBDS_ASSERT(hmget(intmap, 9) == 5); + STBDS_ASSERT(hmget(intmap, 11) == 3); + STBDS_ASSERT(hmget(intmap, 15) == 7); + #endif + #endif + + for (i=0; i < testsize; ++i) + stralloc(&sa, strkey(i)); + strreset(&sa); + + { + s.key = "a", s.value = 1; + shputs(strmap, s); + STBDS_ASSERT(*strmap[0].key == 'a'); + STBDS_ASSERT(strmap[0].key == s.key); + STBDS_ASSERT(strmap[0].value == s.value); + shfree(strmap); + } + + { + s.key = "a", s.value = 1; + sh_new_strdup(strmap); + shputs(strmap, s); + STBDS_ASSERT(*strmap[0].key == 'a'); + STBDS_ASSERT(strmap[0].key != s.key); + STBDS_ASSERT(strmap[0].value == s.value); + shfree(strmap); + } + + { + s.key = "a", s.value = 1; + sh_new_arena(strmap); + shputs(strmap, s); + STBDS_ASSERT(*strmap[0].key == 'a'); + STBDS_ASSERT(strmap[0].key != s.key); + STBDS_ASSERT(strmap[0].value == s.value); + shfree(strmap); + } + + for (j=0; j < 2; ++j) { + STBDS_ASSERT(shgeti(strmap,"foo") == -1); + if (j == 0) + sh_new_strdup(strmap); + else + sh_new_arena(strmap); + STBDS_ASSERT(shgeti(strmap,"foo") == -1); + shdefault(strmap, -2); + STBDS_ASSERT(shgeti(strmap,"foo") == -1); + for (i=0; i < testsize; i+=2) + shput(strmap, strkey(i), i*3); + for (i=0; i < testsize; i+=1) + if (i & 1) STBDS_ASSERT(shget(strmap, strkey(i)) == -2 ); + else STBDS_ASSERT(shget(strmap, strkey(i)) == i*3); + for (i=2; i < testsize; i+=4) + shdel(strmap, strkey(i)); // delete half the entries + for (i=0; i < testsize; i+=1) + if (i & 3) STBDS_ASSERT(shget(strmap, strkey(i)) == -2 ); + else STBDS_ASSERT(shget(strmap, strkey(i)) == i*3); + for (i=0; i < testsize; i+=1) + shdel(strmap, strkey(i)); // delete the rest of the entries + for (i=0; i < testsize; i+=1) + STBDS_ASSERT(shget(strmap, strkey(i)) == -2 ); + shfree(strmap); + } + + { + struct { char *key; char value; } *hash = NULL; + char name[4] = "jen"; + shput(hash, "bob" , 'h'); + shput(hash, "sally" , 'e'); + shput(hash, "fred" , 'l'); + shput(hash, "jen" , 'x'); + shput(hash, "doug" , 'o'); + + shput(hash, name , 'l'); + shfree(hash); + } + + for (i=0; i < testsize; i += 2) { + stbds_struct s = { i,i*2,i*3,i*4 }; + hmput(map, s, i*5); + } + + for (i=0; i < testsize; i += 1) { + stbds_struct s = { i,i*2,i*3 ,i*4 }; + stbds_struct t = { i,i*2,i*3+1,i*4 }; + if (i & 1) STBDS_ASSERT(hmget(map, s) == 0); + else STBDS_ASSERT(hmget(map, s) == i*5); + if (i & 1) STBDS_ASSERT(hmget_ts(map, s, temp) == 0); + else STBDS_ASSERT(hmget_ts(map, s, temp) == i*5); + //STBDS_ASSERT(hmget(map, t.key) == 0); + } + + for (i=0; i < testsize; i += 2) { + stbds_struct s = { i,i*2,i*3,i*4 }; + hmputs(map2, s); + } + hmfree(map); + + for (i=0; i < testsize; i += 1) { + stbds_struct s = { i,i*2,i*3,i*4 }; + stbds_struct t = { i,i*2,i*3+1,i*4 }; + if (i & 1) STBDS_ASSERT(hmgets(map2, s.key).d == 0); + else STBDS_ASSERT(hmgets(map2, s.key).d == i*4); + //STBDS_ASSERT(hmgetp(map2, t.key) == 0); + } + hmfree(map2); + + for (i=0; i < testsize; i += 2) { + stbds_struct2 s = { { i,i*2 }, i*3,i*4, i*5 }; + hmputs(map3, s); + } + for (i=0; i < testsize; i += 1) { + stbds_struct2 s = { { i,i*2}, i*3, i*4, i*5 }; + stbds_struct2 t = { { i,i*2}, i*3+1, i*4, i*5 }; + if (i & 1) STBDS_ASSERT(hmgets(map3, s.key).d == 0); + else STBDS_ASSERT(hmgets(map3, s.key).d == i*5); + //STBDS_ASSERT(hmgetp(map3, t.key) == 0); + } +#endif +} +#endif + + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2019 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/engine/net/testclient.c b/engine/net/testclient.c new file mode 100644 index 0000000..fc97001 --- /dev/null +++ b/engine/net/testclient.c @@ -0,0 +1,11 @@ +#include + +void on(fishsoup_client_t* client, fishsoup_packet_t* pkt) { + if(pkt->header.type == FISHSOUP_PACKET_USER) printf("Received: %s\n", pkt->data); +} + +int main() { + fishsoup_client_t* client = fishsoup_client("127.0.0.1", 0); + fishsoup_client_on_packet(client, on); + while(fishsoup_client_poll(client) >= 0); +} diff --git a/engine/net/testserver.c b/engine/net/testserver.c new file mode 100644 index 0000000..599a4d0 --- /dev/null +++ b/engine/net/testserver.c @@ -0,0 +1,18 @@ +#include + +void on(fishsoup_server_t* server, fishsoup_client_t* client) { + fishsoup_packet_t pkt; + + pkt.data = "Hello world!"; + pkt.header.length = strlen(pkt.data) + 1; + pkt.header.flag = 0; + pkt.header.type = FISHSOUP_PACKET_USER; + + fishsoup_packet_send(client->fd, &pkt); +} + +int main() { + fishsoup_server_t* server = fishsoup_server(0); + fishsoup_server_on_client(server, on); + fishsoup_server_loop(server); +} diff --git a/engine/tools/CMakeLists.txt b/engine/tools/CMakeLists.txt new file mode 100644 index 0000000..cf0bc8f --- /dev/null +++ b/engine/tools/CMakeLists.txt @@ -0,0 +1,9 @@ +include(GNUInstallDirs) + +macro(compile_tool name) + file(GLOB ${name}_SRC ${name}/*.c) + add_executable(${name} ${${name}_SRC}) + target_include_directories(${name} PRIVATE ../../external/stb) +endmacro() + +compile_tool(gb_dumpfont) diff --git a/tools/af_dumpfont.c b/engine/tools/gb_dumpfont/gb_dumpfont.c similarity index 100% rename from tools/af_dumpfont.c rename to engine/tools/gb_dumpfont/gb_dumpfont.c diff --git a/external/stb/stb_image_write.c b/engine/tools/gb_dumpfont/stb_image_write.c similarity index 100% rename from external/stb/stb_image_write.c rename to engine/tools/gb_dumpfont/stb_image_write.c diff --git a/external/libfishsoup b/external/libfishsoup deleted file mode 160000 index b3ae87c..0000000 --- a/external/libfishsoup +++ /dev/null @@ -1 +0,0 @@ -Subproject commit b3ae87cf7e3e8244ec6d48f983af3e575ecd39bd diff --git a/external/stb/stb_image.c b/external/stb/stb_image.c deleted file mode 100644 index 9177288..0000000 --- a/external/stb/stb_image.c +++ /dev/null @@ -1,2 +0,0 @@ -#define STB_IMAGE_IMPLEMENTATION -#include diff --git a/include/af_client.h b/include/af_client.h deleted file mode 100644 index 093c8ab..0000000 --- a/include/af_client.h +++ /dev/null @@ -1,77 +0,0 @@ -#ifndef __AF_CLIENT_H__ -#define __AF_CLIENT_H__ - -#include -#include -#include - -#include - -#define AF_WAIT_MS 15 - -enum AF_SCENES { - AF_SCENE_MAIN = 0, - AF_SCENE_CONNECTING -}; - -/* main.c */ -extern MwWidget root, window, opengl; - -void scene_change(int scene); - -/* gl.c */ -typedef struct gl_scene { - void (*changed)(void); - void (*init)(void); - void (*draw)(void); - void (*after)(void); -} gl_scene_t; - -extern int gl_scene; -extern gl_scene_t gl_scenes[]; -extern double gl_cam_lr; -extern double gl_cam_ud; -extern double gl_cam_x; -extern double gl_cam_y; -extern double gl_cam_z; -extern GLuint gl_shadow; - -void gl_init(void); -void gl_render(void); -void gl_resize(int width, int height); -void gl_scene_change(void); -void gl_calc_normal3(double* out, double v00, double v01, double v02, double v10, double v11, double v12, double v20, double v21, double v22); -void gl_calc_normal4(double* out, double v00, double v01, double v02, double v10, double v11, double v12, double v20, double v21, double v22, double v30, double v31, double v32); -void gl_set_status(const char* status); - -/* gl_font.c */ -void gl_font_init(void); -void gl_font_text(const char* text, int x, int y, double scale); -int gl_font_width(const char* text); -int gl_font_height(const char* text); - -/* gl_main.c */ -void gl_main_changed(void); -void gl_main_init(void); -void gl_main_draw(void); -void gl_main_after(void); - -/* gl_util.c */ -void gl_util_begin_2d(void); -void gl_util_end_2d(void); - -/* gl_shader.c */ -int gl_shader(GLuint* r, const char* vs, const char* fs); - -/* net.c */ -void net_connect(const char* username, const char* hostname, int port); -void net_poll(void); - -/* ui.c */ -MwWidget ui_notice(int width, int height); -void ui_notice_destroy(MwWidget widget); -void ui_init(void); -void ui_scene_change(void); -MwWidget ui_message(const char* message); - -#endif diff --git a/include/af_common.h b/include/af_common.h deleted file mode 100644 index 800c17f..0000000 --- a/include/af_common.h +++ /dev/null @@ -1,59 +0,0 @@ -#ifndef __AF_COMMON_H__ -#define __AF_COMMON_H__ - -#include -#include -#include -#include -#include - -#include - -#include - -#ifdef AF_SERVER -#include -#endif - -#ifdef AF_CLIENT -#include -#endif - -#define AF_DEFAULT_PORT 5312 -#define AF_VERSION "0.0.0" -#define AF_COPYRIGHT "Copyright (C) 2025-2026 Pyrite development team" - -enum AF_USER_PACKETS { - AF_PACKET_HELLO = FISHSOUP_PACKET_USER, - AF_PACKET_OK, - AF_PACKET_ERROR, - AF_PACKET_JOIN -}; - -#pragma pack(1) -typedef struct af_packet_hello { - unsigned char major; - unsigned char minor; - unsigned char patch; -} af_packet_hello_t; - -typedef struct af_packet_ok { - char reason[256]; -} af_packet_ok_t; - -typedef struct af_packet_error { - char reason[256]; -} af_packet_error_t; - -typedef struct af_packet_join { - char username[256]; -} af_packet_join_t; -#pragma pack() - -/* version.c */ -void af_common_version(unsigned char* v_major, unsigned char* v_minor, unsigned char* v_patch); - -/* log.c */ -void af_common_log_info(const char* fmt, ...); - -#endif diff --git a/include/af_config.h.in b/include/af_config.h.in deleted file mode 100644 index fbfb5e6..0000000 --- a/include/af_config.h.in +++ /dev/null @@ -1,6 +0,0 @@ -#ifndef __AF_CONFIG_H__ -#define __AF_CONFIG_H__ - -#define DATAROOTDIR "@CMAKE_INSTALL_FULL_DATAROOTDIR@" - -#endif diff --git a/include/af_server.h b/include/af_server.h deleted file mode 100644 index 24f90d3..0000000 --- a/include/af_server.h +++ /dev/null @@ -1,8 +0,0 @@ -#ifndef __AF_SERVER_H__ -#define __AF_SERVER_H__ - -/* net.c */ -void net_start(int port); -void net_loop(void); - -#endif diff --git a/server/main.c b/server/main.c deleted file mode 100644 index d321701..0000000 --- a/server/main.c +++ /dev/null @@ -1,13 +0,0 @@ -#include - -#include - -int main(int argc, char** argv) { - int port = 0; - - signal(SIGPIPE, SIG_IGN); - - net_start(port); - - net_loop(); -} diff --git a/server/net.c b/server/net.c deleted file mode 100644 index 46a3633..0000000 --- a/server/net.c +++ /dev/null @@ -1,119 +0,0 @@ -#include - -#include - -static fishsoup_server_t* server; - -typedef struct opaque { - char username[256 + 1]; -} opaque_t; - -static void on_packet(fishsoup_client_t* self, fishsoup_packet_t* pkt) { - opaque_t* o = self->user; - - if(pkt->header.type == AF_PACKET_JOIN && pkt->header.length == sizeof(af_packet_join_t)) { - af_packet_join_t* p_join = pkt->data; - int i; - - o->username[256] = 0; - memcpy(o->username, p_join->username, 256); - - af_common_log_info("Client %d: JOIN packet: Username \"%s\"\n", self->fd, o->username); - - for(i = 0; i < arrlen(server->clients); i++) { - if(self->fd != server->clients[i].fd && strcmp(o->username, ((opaque_t*)server->clients[i].user)->username) == 0) { - break; - } - } - - if(i == arrlen(server->clients)) { - fishsoup_packet_t p; - af_packet_ok_t p_ok; - - p.header.type = AF_PACKET_OK; - p.header.length = sizeof(p_ok); - p.data = &p_ok; - - memset(p_ok.reason, 0, 256); - - fishsoup_packet_send(self->fd, &p); - - af_common_log_info("Client %d: JOIN packet: Username is unused\n", self->fd); - } else { - fishsoup_packet_t p; - af_packet_error_t p_error; - - p.header.type = AF_PACKET_ERROR; - p.header.length = sizeof(p_error); - p.data = &p_error; - - memset(p_error.reason, 0, 256); - strcpy(p_error.reason, "Username is already used"); - - fishsoup_packet_send(self->fd, &p); - - af_common_log_info("Client %d: JOIN packet: Username is already used\n", self->fd); - memset(o->username, 0, 256 + 1); - } - } -} - -static void on_disconnect(fishsoup_client_t* self) { - free(self->user); - af_common_log_info("Client %d: Disconnected\n", self->fd); -} - -static void on_client(fishsoup_server_t* self, fishsoup_client_t* client) { - opaque_t* o; - fishsoup_packet_t pkt; - af_packet_hello_t hello; - - af_common_log_info("Client %d: Connected\n", client->fd); - - o = client->user = malloc(sizeof(opaque_t)); - - o->username[0] = 0; - - fishsoup_client_on_disconnect(client, on_disconnect); - fishsoup_client_on_packet(client, on_packet); - - pkt.header.type = AF_PACKET_HELLO; - pkt.header.length = sizeof(hello); - pkt.data = &hello; - - af_common_version(&hello.major, &hello.minor, &hello.patch); - - fishsoup_packet_send(client->fd, &pkt); -} - -static struct timespec c; -static void on_tick(fishsoup_server_t* self) { - struct timespec n; - unsigned long t1, t2, d; - - clock_gettime(CLOCK_MONOTONIC, &n); - - t1 = n.tv_sec * 1000 + n.tv_nsec / 1000000; - t2 = c.tv_sec * 1000 + c.tv_nsec / 1000000; - - d = t1 - t2; /* elapsed time */ - af_common_log_info("Tick: %dms\n", d); - - c = n; -} - -void net_start(int port) { - if(port == 0) port = AF_DEFAULT_PORT; - - if((server = fishsoup_server(port)) == NULL) return; - - fishsoup_server_on_client(server, on_client); - fishsoup_server_on_tick(server, on_tick); -} - -void net_loop(void) { - if(server == NULL) return; - - clock_gettime(CLOCK_MONOTONIC, &c); - fishsoup_server_loop(server); -} diff --git a/src/log.c b/src/log.c deleted file mode 100644 index dc8b4db..0000000 --- a/src/log.c +++ /dev/null @@ -1,14 +0,0 @@ -#include - -static void af_common_log(const char* type, const char* fmt, va_list va) { - printf("[%.4s] ", type); - vprintf(fmt, va); -} - -void af_common_log_info(const char* fmt, ...) { - va_list va; - - va_start(va, fmt); - af_common_log("INFO", fmt, va); - va_end(va); -} diff --git a/src/version.c b/src/version.c deleted file mode 100644 index 9a48e41..0000000 --- a/src/version.c +++ /dev/null @@ -1,11 +0,0 @@ -#include - -void af_common_version(unsigned char* v_major, unsigned char* v_minor, unsigned char* v_patch) { - char* mj = AF_VERSION; - char* mi = strchr(mj, '.') + 1; - char* pt = strchr(mi, '.') + 1; - - *v_major = atoi(mj); - *v_minor = atoi(mi); - *v_patch = atoi(pt); -}