From 565c4e0e6f3bf02e7c0e336647aa9bbb0fac971d Mon Sep 17 00:00:00 2001 From: var Date: Sat, 4 Apr 2026 20:19:46 -0500 Subject: [PATCH] Import .obj model from Blender Add third-party hashmap library to help when importing --- res/model/human.obj | 396 ++++++++++++++ res/tex/texture.png | Bin 1170 -> 1526 bytes src/game.c | 5 +- src/hashmap/LICENSE | 20 + src/hashmap/hashmap.c | 1154 +++++++++++++++++++++++++++++++++++++++++ src/hashmap/hashmap.h | 62 +++ src/model.c | 157 ++++++ src/model.h | 3 + src/render.c | 12 +- src/shape.c | 37 ++ src/shape.h | 1 + 11 files changed, 1844 insertions(+), 3 deletions(-) create mode 100644 res/model/human.obj create mode 100644 src/hashmap/LICENSE create mode 100644 src/hashmap/hashmap.c create mode 100644 src/hashmap/hashmap.h create mode 100644 src/model.c create mode 100644 src/model.h diff --git a/res/model/human.obj b/res/model/human.obj new file mode 100644 index 0000000..1b7fbc2 --- /dev/null +++ b/res/model/human.obj @@ -0,0 +1,396 @@ +# Blender 5.1.0 +# www.blender.org +o Cube +v 0.392226 5.791883 -0.323902 +v 1.000000 2.797271 -0.500000 +v 0.375191 5.791883 0.193573 +v 1.000000 2.797271 0.410409 +v 0.000000 2.797271 -0.500000 +v 0.000000 5.791883 0.313965 +v 0.000000 2.797271 0.410409 +v 0.000000 5.791883 -0.431258 +v 0.973876 5.462687 -0.577779 +v 1.000000 4.791883 -0.500000 +v 0.921580 3.413690 -0.372389 +v 0.973876 5.462687 0.615027 +v 1.000000 4.791883 0.533735 +v 0.815817 3.413690 0.422394 +v 0.000000 5.291883 -0.500000 +v 0.000000 4.791883 -0.500000 +v 0.000000 3.413690 -0.431220 +v 0.000000 5.291883 0.533735 +v 0.000000 4.791883 0.533735 +v 0.000000 3.413690 0.410409 +v 1.309145 5.351887 -0.469410 +v 1.226283 4.706231 -0.312568 +v 1.309145 5.351887 0.467801 +v 1.226283 4.706231 0.309653 +v 0.292031 0.000000 0.293679 +v 0.879388 0.000000 0.293679 +v 0.879388 0.000000 -0.293679 +v 0.292031 0.000000 -0.293679 +v 0.500000 6.791883 -0.500000 +v 0.500000 6.791883 0.500000 +v 0.000000 6.791883 -0.500000 +v 0.000000 6.791883 0.500000 +v 0.500000 5.899879 0.500000 +v 0.000000 5.899879 0.500000 +v 0.000000 5.899879 -0.500000 +v 0.500000 5.899879 -0.500000 +v 0.665692 5.541883 -0.391726 +v 0.525278 5.541883 0.301663 +v 0.000000 5.541883 -0.500000 +v 0.000000 5.541883 0.465368 +v 0.064081 2.647533 -0.468467 +v 0.064081 2.647533 0.392291 +v 0.995814 2.647533 0.392291 +v 0.995814 2.647533 -0.468467 +v 0.170786 1.383736 -0.403152 +v 0.973834 1.383736 0.355442 +v 0.170786 1.383736 0.355442 +v 0.973834 1.383736 -0.403152 +v 0.960790 4.541883 -0.436195 +v 0.907909 4.541883 0.461197 +v 0.000000 4.541883 -0.465610 +v 0.000000 4.541883 0.455205 +v 2.256556 4.848390 -0.244752 +v 2.192387 4.348389 -0.123293 +v 2.256556 4.848390 0.481030 +v 2.192387 4.348389 0.358559 +v 3.076406 4.246439 -0.125417 +v 3.033353 3.910976 -0.043927 +v 3.076406 4.246439 0.361528 +v 3.033353 3.910976 0.279360 +v -0.392226 5.791883 -0.323902 +v -1.000000 2.797271 -0.500000 +v -0.375191 5.791883 0.193573 +v -1.000000 2.797271 0.410409 +v -0.973876 5.462687 -0.577779 +v -1.000000 4.791883 -0.500000 +v -0.921580 3.413690 -0.372389 +v -0.973876 5.462687 0.615027 +v -1.000000 4.791883 0.533735 +v -0.815817 3.413690 0.422394 +v -1.309145 5.351887 -0.469410 +v -1.226283 4.706231 -0.312568 +v -1.309145 5.351887 0.467801 +v -1.226283 4.706231 0.309653 +v -0.292031 0.000000 0.293679 +v -0.879388 0.000000 0.293679 +v -0.879388 0.000000 -0.293679 +v -0.292031 0.000000 -0.293679 +v -0.500000 6.791883 -0.500000 +v -0.500000 6.791883 0.500000 +v -0.500000 5.899879 0.500000 +v -0.500000 5.899879 -0.500000 +v -0.665692 5.541883 -0.391726 +v -0.525278 5.541883 0.301663 +v -0.064081 2.647533 -0.468467 +v -0.064081 2.647533 0.392291 +v -0.995814 2.647533 0.392291 +v -0.995814 2.647533 -0.468467 +v -0.170786 1.383736 -0.403152 +v -0.973834 1.383736 0.355442 +v -0.170786 1.383736 0.355442 +v -0.973834 1.383736 -0.403152 +v -0.960790 4.541883 -0.436195 +v -0.907909 4.541883 0.461197 +v -2.256556 4.848390 -0.244752 +v -2.192387 4.348389 -0.123293 +v -2.256556 4.848390 0.481030 +v -2.192387 4.348389 0.358559 +v -3.076406 4.246439 -0.125417 +v -3.033353 3.910976 -0.043927 +v -3.076406 4.246439 0.361528 +v -3.033353 3.910976 0.279360 +vt 0.062500 0.812500 +vt 0.000000 0.750000 +vt 0.062500 0.750000 +vt 0.375000 0.500000 +vt 0.437500 0.562500 +vt 0.375000 0.562500 +vt 0.375000 0.437500 +vt 0.437500 0.500000 +vt 0.375000 0.625000 +vt 0.500000 0.562500 +vt 0.625000 0.625000 +vt 0.500000 0.625000 +vt 0.375000 0.875000 +vt 0.312500 0.937500 +vt 0.312500 0.875000 +vt 0.437500 0.875000 +vt 0.500000 0.937500 +vt 0.437500 0.937500 +vt 0.750000 0.937500 +vt 0.812500 1.000000 +vt 0.750000 1.000000 +vt 0.312500 0.750000 +vt 0.375000 0.812500 +vt 0.312500 0.812500 +vt 0.500000 0.750000 +vt 0.437500 0.812500 +vt 0.437500 0.750000 +vt 0.812500 0.437500 +vt 0.750000 0.500000 +vt 0.750000 0.437500 +vt 0.500000 0.812500 +vt 0.562500 0.875000 +vt 0.500000 0.875000 +vt 0.625000 0.812500 +vt 0.625000 0.875000 +vt 0.812500 0.875000 +vt 0.812500 0.937500 +vt 0.312500 0.500000 +vt 0.250000 0.562500 +vt 0.250000 0.500000 +vt 0.812500 0.562500 +vt 0.750000 0.562500 +vt 0.125000 0.812500 +vt 0.062500 0.875000 +vt 0.187500 0.875000 +vt 0.187500 0.812500 +vt 0.125000 0.937500 +vt 0.125000 0.875000 +vt 0.125000 0.750000 +vt 0.187500 0.750000 +vt 0.687500 0.500000 +vt 0.625000 0.562500 +vt 0.625000 0.500000 +vt 0.500000 0.437500 +vt 0.500000 0.500000 +vt 0.437500 0.625000 +vt 0.312500 0.625000 +vt 0.312500 0.562500 +vt 0.312500 0.437500 +vt 0.250000 0.750000 +vt 0.250000 0.937500 +vt 0.250000 0.875000 +vt 0.250000 0.812500 +vt 0.062500 0.937500 +vt 0.000000 0.875000 +vt 0.187500 0.937500 +vt 0.000000 0.812500 +vt 0.375000 0.750000 +vt 0.375000 0.937500 +vt 0.187500 0.500000 +vt 0.125000 0.562500 +vt 0.125000 0.500000 +vt 0.812500 0.625000 +vt 0.750000 0.625000 +vt 0.750000 0.875000 +vt 0.812500 0.812500 +vt 0.812500 0.687500 +vt 0.750000 0.750000 +vt 0.750000 0.687500 +vt 0.750000 0.812500 +vt 0.812500 0.750000 +vt 0.687500 0.812500 +vt 0.687500 0.875000 +vt 0.437500 0.437500 +vt 0.562500 0.812500 +vt 0.812500 0.500000 +vt 0.000000 0.937500 +vt 0.187500 0.562500 +vt 0.687500 0.562500 +vt 0.625000 0.437500 +s 0 +f 47/1 28/2 25/3 +f 37/4 3/5 38/6 +f 39/7 1/8 37/4 +f 3/5 40/9 38/6 +f 33/10 32/11 34/12 +f 14/13 7/14 4/15 +f 50/16 19/17 52/18 +f 13/19 18/20 19/21 +f 5/22 11/23 2/24 +f 16/25 49/26 51/27 +f 15/28 10/29 16/30 +f 11/23 4/15 2/24 +f 10/31 50/16 49/26 +f 10/31 24/32 13/33 +f 24/32 54/34 56/35 +f 13/19 23/36 12/37 +f 9/38 23/39 21/40 +f 10/29 21/41 22/42 +f 27/43 25/44 28/1 +f 46/45 27/43 48/46 +f 46/45 25/47 26/48 +f 48/46 28/49 45/50 +f 31/51 30/52 29/53 +f 35/54 29/53 36/55 +f 36/55 30/52 33/10 +f 3/5 36/55 33/10 +f 1/8 35/54 36/55 +f 3/5 34/12 6/56 +f 38/6 18/57 12/58 +f 15/59 37/4 9/38 +f 37/4 12/58 9/38 +f 2/24 41/60 5/22 +f 4/15 42/61 43/62 +f 4/15 44/63 2/24 +f 7/64 41/65 42/44 +f 44/63 45/50 41/60 +f 43/62 47/66 46/45 +f 43/62 48/46 44/63 +f 42/44 45/67 47/1 +f 11/23 50/16 14/13 +f 51/27 11/23 17/68 +f 14/13 52/18 20/69 +f 53/70 59/71 57/72 +f 22/42 53/73 54/74 +f 23/39 53/70 21/40 +f 24/75 55/76 23/36 +f 57/77 60/78 58/79 +f 56/80 59/81 55/76 +f 56/35 58/82 60/83 +f 54/74 57/77 58/79 +f 91/1 78/2 89/67 +f 83/4 63/5 61/8 +f 39/7 61/8 8/84 +f 40/9 63/5 84/6 +f 32/11 81/10 34/12 +f 7/14 70/13 64/15 +f 94/16 19/17 69/33 +f 69/19 18/20 68/37 +f 5/22 67/23 17/68 +f 93/26 16/25 51/27 +f 66/29 15/28 16/30 +f 64/15 67/23 62/24 +f 94/16 66/31 93/26 +f 74/32 66/31 69/33 +f 74/32 96/34 72/85 +f 73/36 69/19 68/37 +f 65/38 73/39 68/58 +f 66/29 71/41 65/86 +f 75/44 77/43 78/1 +f 77/43 90/45 92/46 +f 90/45 75/47 91/66 +f 78/49 92/46 89/50 +f 80/52 31/51 79/53 +f 79/53 35/54 82/55 +f 80/52 82/55 81/10 +f 63/5 82/55 61/8 +f 61/8 35/54 8/84 +f 34/12 63/5 6/56 +f 18/57 84/6 68/58 +f 15/59 83/4 39/7 +f 68/58 83/4 65/38 +f 85/60 62/24 5/22 +f 64/15 86/61 7/14 +f 88/63 64/15 62/24 +f 7/64 85/65 5/87 +f 89/50 88/63 85/60 +f 87/62 91/66 86/61 +f 92/46 87/62 88/63 +f 86/44 89/67 85/65 +f 67/23 94/16 93/26 +f 67/23 51/27 17/68 +f 70/13 52/18 94/16 +f 95/70 101/71 97/88 +f 72/42 95/73 71/41 +f 95/70 73/39 71/40 +f 97/76 74/75 73/36 +f 102/78 99/77 100/79 +f 101/81 98/80 97/76 +f 98/35 100/82 96/34 +f 96/74 99/77 95/73 +f 47/1 45/67 28/2 +f 37/4 1/8 3/5 +f 39/7 8/84 1/8 +f 3/5 6/56 40/9 +f 33/10 30/52 32/11 +f 14/13 20/69 7/14 +f 50/16 13/33 19/17 +f 13/19 12/37 18/20 +f 5/22 17/68 11/23 +f 16/25 10/31 49/26 +f 15/28 9/86 10/29 +f 11/23 14/13 4/15 +f 10/31 13/33 50/16 +f 10/31 22/85 24/32 +f 24/32 22/85 54/34 +f 13/19 24/75 23/36 +f 9/38 12/58 23/39 +f 10/29 9/86 21/41 +f 27/43 26/48 25/44 +f 46/45 26/48 27/43 +f 46/45 47/66 25/47 +f 48/46 27/43 28/49 +f 31/51 32/89 30/52 +f 35/54 31/90 29/53 +f 36/55 29/53 30/52 +f 3/5 1/8 36/55 +f 1/8 8/84 35/54 +f 3/5 33/10 34/12 +f 38/6 40/9 18/57 +f 15/59 39/7 37/4 +f 37/4 38/6 12/58 +f 2/24 44/63 41/60 +f 4/15 7/14 42/61 +f 4/15 43/62 44/63 +f 7/64 5/87 41/65 +f 44/63 48/46 45/50 +f 43/62 42/61 47/66 +f 43/62 46/45 48/46 +f 42/44 41/65 45/67 +f 11/23 49/26 50/16 +f 51/27 49/26 11/23 +f 14/13 50/16 52/18 +f 53/70 55/88 59/71 +f 22/42 21/41 53/73 +f 23/39 55/88 53/70 +f 24/75 56/80 55/76 +f 57/77 59/81 60/78 +f 56/80 60/78 59/81 +f 56/35 54/34 58/82 +f 54/74 53/73 57/77 +f 91/1 75/3 78/2 +f 83/4 84/6 63/5 +f 39/7 83/4 61/8 +f 40/9 6/56 63/5 +f 32/11 80/52 81/10 +f 7/14 20/69 70/13 +f 94/16 52/18 19/17 +f 69/19 19/21 18/20 +f 5/22 62/24 67/23 +f 93/26 66/31 16/25 +f 66/29 65/86 15/28 +f 64/15 70/13 67/23 +f 94/16 69/33 66/31 +f 74/32 72/85 66/31 +f 74/32 98/35 96/34 +f 73/36 74/75 69/19 +f 65/38 71/40 73/39 +f 66/29 72/42 71/41 +f 75/44 76/48 77/43 +f 77/43 76/48 90/45 +f 90/45 76/48 75/47 +f 78/49 77/43 92/46 +f 80/52 32/89 31/51 +f 79/53 31/90 35/54 +f 80/52 79/53 82/55 +f 63/5 81/10 82/55 +f 61/8 82/55 35/54 +f 34/12 81/10 63/5 +f 18/57 40/9 84/6 +f 15/59 65/38 83/4 +f 68/58 84/6 83/4 +f 85/60 88/63 62/24 +f 64/15 87/62 86/61 +f 88/63 87/62 64/15 +f 7/64 86/44 85/65 +f 89/50 92/46 88/63 +f 87/62 90/45 91/66 +f 92/46 90/45 87/62 +f 86/44 91/1 89/67 +f 67/23 70/13 94/16 +f 67/23 93/26 51/27 +f 70/13 20/69 52/18 +f 95/70 99/72 101/71 +f 72/42 96/74 95/73 +f 95/70 97/88 73/39 +f 97/76 98/80 74/75 +f 102/78 101/81 99/77 +f 101/81 102/78 98/80 +f 98/35 102/83 100/82 +f 96/74 100/79 99/77 diff --git a/res/tex/texture.png b/res/tex/texture.png index 5ece0b538b0585f21f3b3673506d11beade6478f..4996be98c7935d69b6d98aa95168919995ffd73c 100644 GIT binary patch literal 1526 zcmeAS@N?(olHy`uVBq!ia0y~yU;;83893O0)X@p&(m}l9Aa^ImnE)e-c@Ne1&9>AYTTCDm4a%h86~fUqGRT7Yq!g1`G_Z5*Qe)W-u^_7tGleXakhs zEbxddW?;}$24TjErS@e&LG}_)Usv{5EUb*&ssibYc^Md3eLP(pLn>~)y=#~q%q?;J zp?Q$tk*hmf_?|KE5NS^ld>O8BWa;eLnptMD3dn z7g`;*Y`JWf8}#V6P4t_+aZ=1bzuy0T?cD0T?L9~2?){$n^K$>|_w$$k`=fsTTX6lq z{IYk;{{6APQztGcIQ#C}jt+}o|8%cDo__gy_TT5%U%$O7Cw|nzVz2f*CWaH~hl3eX zrhe7_|H@E|f#CoX!vyDR>;J4i-h6Pux0*9)n{Pe#TP`dp__wZp;q&i5Dpr4eye~bU zk%2*nfnoK;zibJQZ}I9gaWgO^Ffw>3{N_E-KYxGrdH1ij|3fqX)&KGBFZ+K@j^ABa zKMJBIqCuL$oB?W6qjbYBUWdqQ|FdSK-e;Nf{W*h4KFbDRAc`?G{M>Y(L8j){BUhlo zAj>#Y_A~yd__6cr&&+!)P_fsVzj*GJiK~o2K0wf5i`(J3_x>J9xJjlia9SJtDfFuJOLab6Fr_Eynxd4=#1b~4IWC1;>0HlC!=U`xP0J3mMk*FNi zB!Z@+Do3~phhuOlM|c5Y1VW4`FOaBQfdLkFM466Ij_QSx6+j>4f3bfLj-S<%#|{b* N22WQ%mvv4FO#o6+T(tlI delta 353 zcmeyyJ&AKdS3MJxF`voRgfIpM<`tFcYUiRC<@B8zw zFZ^D=R?hWv?yIh6#~1#8`TP0lwMy$+g&7(c7#LU>7!-gMBg5o-EJBmzS-B?PXJHq( zZ{o+`a&_6dj5?WLk^B4QC-bvP0VM>13fKf2JbQ&10(+g~P3|wtyY~Ju`{YDsL&1cg z$1DyZ5Ig37{r!V+vLUM>=Zui&b2b|@G$5Pc%qTFqj#YMY1FJNn0+PuftJx+WW)+<5 rz^XL4fQ5bX2bLld)H;j*yTE?+tJ+`bw=KVbeq!)+^>bP0l+XkKN3Dye diff --git a/src/game.c b/src/game.c index d0b4edb..6bca48f 100644 --- a/src/game.c +++ b/src/game.c @@ -28,9 +28,10 @@ void Game_Update(GameState *gs) if (gs->input.q) move[2] = speed; else if (gs->input.e) move[2] = -speed; - ShapeInstance *target = &(gs->shapes[0]->instances[2]); + ShapeInstance *target = &(gs->shapes[2]->instances[0]); vec3 displacement; - glm_vec3_scale(gs->camera.front, -10.0, displacement); + glm_vec3_scale(gs->camera.front, -20.0f, displacement); + displacement[1] += 16.0f; glm_vec3_add(target->position, move, target->position); glm_vec3_add(target->position, displacement, gs->camera.position); diff --git a/src/hashmap/LICENSE b/src/hashmap/LICENSE new file mode 100644 index 0000000..541ae74 --- /dev/null +++ b/src/hashmap/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2020 Joshua J Baker + +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. \ No newline at end of file diff --git a/src/hashmap/hashmap.c b/src/hashmap/hashmap.c new file mode 100644 index 0000000..86df8f4 --- /dev/null +++ b/src/hashmap/hashmap.c @@ -0,0 +1,1154 @@ +// Copyright 2020 Joshua J Baker. All rights reserved. +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file. + +#include +#include +#include +#include +#include +#include "hashmap.h" + +#define GROW_AT 0.60 /* 60% */ +#define SHRINK_AT 0.10 /* 10% */ + +#ifndef HASHMAP_LOAD_FACTOR +#define HASHMAP_LOAD_FACTOR GROW_AT +#endif + +static void *(*__malloc)(size_t) = NULL; +static void *(*__realloc)(void *, size_t) = NULL; +static void (*__free)(void *) = NULL; + +// hashmap_set_allocator allows for configuring a custom allocator for +// all hashmap library operations. This function, if needed, should be called +// only once at startup and a prior to calling hashmap_new(). +void hashmap_set_allocator(void *(*malloc)(size_t), void (*free)(void*)) { + __malloc = malloc; + __free = free; +} + +struct bucket { + uint64_t hash:48; + uint64_t dib:16; +}; + +// hashmap is an open addressed hash map using robinhood hashing. +struct hashmap { + void *(*malloc)(size_t); + void *(*realloc)(void *, size_t); + void (*free)(void *); + size_t elsize; + size_t cap; + uint64_t seed0; + uint64_t seed1; + uint64_t (*hash)(const void *item, uint64_t seed0, uint64_t seed1); + int (*compare)(const void *a, const void *b, void *udata); + void (*elfree)(void *item); + void *udata; + size_t bucketsz; + size_t nbuckets; + size_t count; + size_t mask; + size_t growat; + size_t shrinkat; + uint8_t loadfactor; + uint8_t growpower; + bool oom; + void *buckets; + void *spare; + void *edata; +}; + +void hashmap_set_grow_by_power(struct hashmap *map, size_t power) { + map->growpower = power < 1 ? 1 : power > 16 ? 16 : power; +} + +static double clamp_load_factor(double factor, double default_factor) { + // Check for NaN and clamp between 50% and 90% + return factor != factor ? default_factor : + factor < 0.50 ? 0.50 : + factor > 0.95 ? 0.95 : + factor; +} + +void hashmap_set_load_factor(struct hashmap *map, double factor) { + factor = clamp_load_factor(factor, map->loadfactor / 100.0); + map->loadfactor = factor * 100; + map->growat = map->nbuckets * (map->loadfactor / 100.0); +} + +static struct bucket *bucket_at0(void *buckets, size_t bucketsz, size_t i) { + return (struct bucket*)(((char*)buckets)+(bucketsz*i)); +} + +static struct bucket *bucket_at(const struct hashmap *map, size_t index) { + return bucket_at0(map->buckets, map->bucketsz, index); +} + +static void *bucket_item(struct bucket *entry) { + return ((char*)entry)+sizeof(struct bucket); +} + +static uint64_t clip_hash(uint64_t hash) { + return hash & 0xFFFFFFFFFFFF; +} + +static uint64_t get_hash(const struct hashmap *map, const void *key) { + return clip_hash(map->hash(key, map->seed0, map->seed1)); +} + + +// hashmap_new_with_allocator returns a new hash map using a custom allocator. +// See hashmap_new for more information information +struct hashmap *hashmap_new_with_allocator(void *(*_malloc)(size_t), + void *(*_realloc)(void*, size_t), void (*_free)(void*), + size_t elsize, size_t cap, uint64_t seed0, uint64_t seed1, + uint64_t (*hash)(const void *item, uint64_t seed0, uint64_t seed1), + int (*compare)(const void *a, const void *b, void *udata), + void (*elfree)(void *item), + void *udata) +{ + _malloc = _malloc ? _malloc : __malloc ? __malloc : malloc; + _realloc = _realloc ? _realloc : __realloc ? __realloc : realloc; + _free = _free ? _free : __free ? __free : free; + size_t ncap = 16; + if (cap < ncap) { + cap = ncap; + } else { + while (ncap < cap) { + ncap *= 2; + } + cap = ncap; + } + size_t bucketsz = sizeof(struct bucket) + elsize; + while (bucketsz & (sizeof(uintptr_t)-1)) { + bucketsz++; + } + // hashmap + spare + edata + size_t size = sizeof(struct hashmap)+bucketsz*2; + struct hashmap *map = _malloc(size); + if (!map) { + return NULL; + } + memset(map, 0, sizeof(struct hashmap)); + map->elsize = elsize; + map->bucketsz = bucketsz; + map->seed0 = seed0; + map->seed1 = seed1; + map->hash = hash; + map->compare = compare; + map->elfree = elfree; + map->udata = udata; + map->spare = ((char*)map)+sizeof(struct hashmap); + map->edata = (char*)map->spare+bucketsz; + map->cap = cap; + map->nbuckets = cap; + map->mask = map->nbuckets-1; + map->buckets = _malloc(map->bucketsz*map->nbuckets); + if (!map->buckets) { + _free(map); + return NULL; + } + memset(map->buckets, 0, map->bucketsz*map->nbuckets); + map->growpower = 1; + map->loadfactor = clamp_load_factor(HASHMAP_LOAD_FACTOR, GROW_AT) * 100; + map->growat = map->nbuckets * (map->loadfactor / 100.0); + map->shrinkat = map->nbuckets * SHRINK_AT; + map->malloc = _malloc; + map->realloc = _realloc; + map->free = _free; + return map; +} + +// hashmap_new returns a new hash map. +// Param `elsize` is the size of each element in the tree. Every element that +// is inserted, deleted, or retrieved will be this size. +// Param `cap` is the default lower capacity of the hashmap. Setting this to +// zero will default to 16. +// Params `seed0` and `seed1` are optional seed values that are passed to the +// following `hash` function. These can be any value you wish but it's often +// best to use randomly generated values. +// Param `hash` is a function that generates a hash value for an item. It's +// important that you provide a good hash function, otherwise it will perform +// poorly or be vulnerable to Denial-of-service attacks. This implementation +// comes with two helper functions `hashmap_sip()` and `hashmap_murmur()`. +// Param `compare` is a function that compares items in the tree. See the +// qsort stdlib function for an example of how this function works. +// The hashmap must be freed with hashmap_free(). +// Param `elfree` is a function that frees a specific item. This should be NULL +// unless you're storing some kind of reference data in the hash. +struct hashmap *hashmap_new(size_t elsize, size_t cap, uint64_t seed0, + uint64_t seed1, + uint64_t (*hash)(const void *item, uint64_t seed0, uint64_t seed1), + int (*compare)(const void *a, const void *b, void *udata), + void (*elfree)(void *item), + void *udata) +{ + return hashmap_new_with_allocator(NULL, NULL, NULL, elsize, cap, seed0, + seed1, hash, compare, elfree, udata); +} + +static void free_elements(struct hashmap *map) { + if (map->elfree) { + for (size_t i = 0; i < map->nbuckets; i++) { + struct bucket *bucket = bucket_at(map, i); + if (bucket->dib) map->elfree(bucket_item(bucket)); + } + } +} + +// hashmap_clear quickly clears the map. +// Every item is called with the element-freeing function given in hashmap_new, +// if present, to free any data referenced in the elements of the hashmap. +// When the update_cap is provided, the map's capacity will be updated to match +// the currently number of allocated buckets. This is an optimization to ensure +// that this operation does not perform any allocations. +void hashmap_clear(struct hashmap *map, bool update_cap) { + map->count = 0; + free_elements(map); + if (update_cap) { + map->cap = map->nbuckets; + } else if (map->nbuckets != map->cap) { + void *new_buckets = map->malloc(map->bucketsz*map->cap); + if (new_buckets) { + map->free(map->buckets); + map->buckets = new_buckets; + } + map->nbuckets = map->cap; + } + memset(map->buckets, 0, map->bucketsz*map->nbuckets); + map->mask = map->nbuckets-1; + map->growat = map->nbuckets * (map->loadfactor / 100.0) ; + map->shrinkat = map->nbuckets * SHRINK_AT; +} + +static bool resize0(struct hashmap *map, size_t new_cap) { + struct hashmap *map2 = hashmap_new_with_allocator(map->malloc, map->realloc, + map->free, map->elsize, new_cap, map->seed0, map->seed1, map->hash, + map->compare, map->elfree, map->udata); + if (!map2) return false; + for (size_t i = 0; i < map->nbuckets; i++) { + struct bucket *entry = bucket_at(map, i); + if (!entry->dib) { + continue; + } + entry->dib = 1; + size_t j = entry->hash & map2->mask; + while(1) { + struct bucket *bucket = bucket_at(map2, j); + if (bucket->dib == 0) { + memcpy(bucket, entry, map->bucketsz); + break; + } + if (bucket->dib < entry->dib) { + memcpy(map2->spare, bucket, map->bucketsz); + memcpy(bucket, entry, map->bucketsz); + memcpy(entry, map2->spare, map->bucketsz); + } + j = (j + 1) & map2->mask; + entry->dib += 1; + } + } + map->free(map->buckets); + map->buckets = map2->buckets; + map->nbuckets = map2->nbuckets; + map->mask = map2->mask; + map->growat = map2->growat; + map->shrinkat = map2->shrinkat; + map->free(map2); + return true; +} + +static bool resize(struct hashmap *map, size_t new_cap) { + return resize0(map, new_cap); +} + +// hashmap_set_with_hash works like hashmap_set but you provide your +// own hash. The 'hash' callback provided to the hashmap_new function +// will not be called +const void *hashmap_set_with_hash(struct hashmap *map, const void *item, + uint64_t hash) +{ + hash = clip_hash(hash); + map->oom = false; + if (map->count >= map->growat) { + if (!resize(map, map->nbuckets*(1<growpower))) { + map->oom = true; + return NULL; + } + } + + struct bucket *entry = map->edata; + entry->hash = hash; + entry->dib = 1; + void *eitem = bucket_item(entry); + memcpy(eitem, item, map->elsize); + + void *bitem; + size_t i = entry->hash & map->mask; + while(1) { + struct bucket *bucket = bucket_at(map, i); + if (bucket->dib == 0) { + memcpy(bucket, entry, map->bucketsz); + map->count++; + return NULL; + } + bitem = bucket_item(bucket); + if (entry->hash == bucket->hash && (!map->compare || + map->compare(eitem, bitem, map->udata) == 0)) + { + memcpy(map->spare, bitem, map->elsize); + memcpy(bitem, eitem, map->elsize); + return map->spare; + } + if (bucket->dib < entry->dib) { + memcpy(map->spare, bucket, map->bucketsz); + memcpy(bucket, entry, map->bucketsz); + memcpy(entry, map->spare, map->bucketsz); + eitem = bucket_item(entry); + } + i = (i + 1) & map->mask; + entry->dib += 1; + } +} + +// hashmap_set inserts or replaces an item in the hash map. If an item is +// replaced then it is returned otherwise NULL is returned. This operation +// may allocate memory. If the system is unable to allocate additional +// memory then NULL is returned and hashmap_oom() returns true. +const void *hashmap_set(struct hashmap *map, const void *item) { + return hashmap_set_with_hash(map, item, get_hash(map, item)); +} + +// hashmap_get_with_hash works like hashmap_get but you provide your +// own hash. The 'hash' callback provided to the hashmap_new function +// will not be called +const void *hashmap_get_with_hash(const struct hashmap *map, const void *key, + uint64_t hash) +{ + hash = clip_hash(hash); + size_t i = hash & map->mask; + while(1) { + struct bucket *bucket = bucket_at(map, i); + if (!bucket->dib) return NULL; + if (bucket->hash == hash) { + void *bitem = bucket_item(bucket); + if (!map->compare || map->compare(key, bitem, map->udata) == 0) { + return bitem; + } + } + i = (i + 1) & map->mask; + } +} + +// hashmap_get returns the item based on the provided key. If the item is not +// found then NULL is returned. +const void *hashmap_get(const struct hashmap *map, const void *key) { + return hashmap_get_with_hash(map, key, get_hash(map, key)); +} + +// hashmap_probe returns the item in the bucket at position or NULL if an item +// is not set for that bucket. The position is 'moduloed' by the number of +// buckets in the hashmap. +const void *hashmap_probe(struct hashmap *map, uint64_t position) { + size_t i = position & map->mask; + struct bucket *bucket = bucket_at(map, i); + if (!bucket->dib) { + return NULL; + } + return bucket_item(bucket); +} + +// hashmap_delete_with_hash works like hashmap_delete but you provide your +// own hash. The 'hash' callback provided to the hashmap_new function +// will not be called +const void *hashmap_delete_with_hash(struct hashmap *map, const void *key, + uint64_t hash) +{ + hash = clip_hash(hash); + map->oom = false; + size_t i = hash & map->mask; + while(1) { + struct bucket *bucket = bucket_at(map, i); + if (!bucket->dib) { + return NULL; + } + void *bitem = bucket_item(bucket); + if (bucket->hash == hash && (!map->compare || + map->compare(key, bitem, map->udata) == 0)) + { + memcpy(map->spare, bitem, map->elsize); + bucket->dib = 0; + while(1) { + struct bucket *prev = bucket; + i = (i + 1) & map->mask; + bucket = bucket_at(map, i); + if (bucket->dib <= 1) { + prev->dib = 0; + break; + } + memcpy(prev, bucket, map->bucketsz); + prev->dib--; + } + map->count--; + if (map->nbuckets > map->cap && map->count <= map->shrinkat) { + // Ignore the return value. It's ok for the resize operation to + // fail to allocate enough memory because a shrink operation + // does not change the integrity of the data. + resize(map, map->nbuckets/2); + } + return map->spare; + } + i = (i + 1) & map->mask; + } +} + +// hashmap_delete removes an item from the hash map and returns it. If the +// item is not found then NULL is returned. +const void *hashmap_delete(struct hashmap *map, const void *key) { + return hashmap_delete_with_hash(map, key, get_hash(map, key)); +} + +// hashmap_count returns the number of items in the hash map. +size_t hashmap_count(const struct hashmap *map) { + return map->count; +} + +// hashmap_free frees the hash map +// Every item is called with the element-freeing function given in hashmap_new, +// if present, to free any data referenced in the elements of the hashmap. +void hashmap_free(struct hashmap *map) { + if (!map) return; + free_elements(map); + map->free(map->buckets); + map->free(map); +} + +// hashmap_oom returns true if the last hashmap_set() call failed due to the +// system being out of memory. +bool hashmap_oom(struct hashmap *map) { + return map->oom; +} + +// hashmap_scan iterates over all items in the hash map +// Param `iter` can return false to stop iteration early. +// Returns false if the iteration has been stopped early. +bool hashmap_scan(struct hashmap *map, + bool (*iter)(const void *item, void *udata), void *udata) +{ + for (size_t i = 0; i < map->nbuckets; i++) { + struct bucket *bucket = bucket_at(map, i); + if (bucket->dib && !iter(bucket_item(bucket), udata)) { + return false; + } + } + return true; +} + +// hashmap_iter iterates one key at a time yielding a reference to an +// entry at each iteration. Useful to write simple loops and avoid writing +// dedicated callbacks and udata structures, as in hashmap_scan. +// +// map is a hash map handle. i is a pointer to a size_t cursor that +// should be initialized to 0 at the beginning of the loop. item is a void +// pointer pointer that is populated with the retrieved item. Note that this +// is NOT a copy of the item stored in the hash map and can be directly +// modified. +// +// Note that if hashmap_delete() is called on the hashmap being iterated, +// the buckets are rearranged and the iterator must be reset to 0, otherwise +// unexpected results may be returned after deletion. +// +// This function has not been tested for thread safety. +// +// The function returns true if an item was retrieved; false if the end of the +// iteration has been reached. +bool hashmap_iter(struct hashmap *map, size_t *i, void **item) { + struct bucket *bucket; + do { + if (*i >= map->nbuckets) return false; + bucket = bucket_at(map, *i); + (*i)++; + } while (!bucket->dib); + *item = bucket_item(bucket); + return true; +} + + +//----------------------------------------------------------------------------- +// SipHash reference C implementation +// +// Copyright (c) 2012-2016 Jean-Philippe Aumasson +// +// Copyright (c) 2012-2014 Daniel J. Bernstein +// +// To the extent possible under law, the author(s) have dedicated all copyright +// and related and neighboring rights to this software to the public domain +// worldwide. This software is distributed without any warranty. +// +// You should have received a copy of the CC0 Public Domain Dedication along +// with this software. If not, see +// . +// +// default: SipHash-2-4 +//----------------------------------------------------------------------------- +static uint64_t SIP64(const uint8_t *in, const size_t inlen, uint64_t seed0, + uint64_t seed1) +{ +#define U8TO64_LE(p) \ + { (((uint64_t)((p)[0])) | ((uint64_t)((p)[1]) << 8) | \ + ((uint64_t)((p)[2]) << 16) | ((uint64_t)((p)[3]) << 24) | \ + ((uint64_t)((p)[4]) << 32) | ((uint64_t)((p)[5]) << 40) | \ + ((uint64_t)((p)[6]) << 48) | ((uint64_t)((p)[7]) << 56)) } +#define U64TO8_LE(p, v) \ + { U32TO8_LE((p), (uint32_t)((v))); \ + U32TO8_LE((p) + 4, (uint32_t)((v) >> 32)); } +#define U32TO8_LE(p, v) \ + { (p)[0] = (uint8_t)((v)); \ + (p)[1] = (uint8_t)((v) >> 8); \ + (p)[2] = (uint8_t)((v) >> 16); \ + (p)[3] = (uint8_t)((v) >> 24); } +#define ROTL(x, b) (uint64_t)(((x) << (b)) | ((x) >> (64 - (b)))) +#define SIPROUND \ + { v0 += v1; v1 = ROTL(v1, 13); \ + v1 ^= v0; v0 = ROTL(v0, 32); \ + v2 += v3; v3 = ROTL(v3, 16); \ + v3 ^= v2; \ + v0 += v3; v3 = ROTL(v3, 21); \ + v3 ^= v0; \ + v2 += v1; v1 = ROTL(v1, 17); \ + v1 ^= v2; v2 = ROTL(v2, 32); } + uint64_t k0 = U8TO64_LE((uint8_t*)&seed0); + uint64_t k1 = U8TO64_LE((uint8_t*)&seed1); + uint64_t v3 = UINT64_C(0x7465646279746573) ^ k1; + uint64_t v2 = UINT64_C(0x6c7967656e657261) ^ k0; + uint64_t v1 = UINT64_C(0x646f72616e646f6d) ^ k1; + uint64_t v0 = UINT64_C(0x736f6d6570736575) ^ k0; + const uint8_t *end = in + inlen - (inlen % sizeof(uint64_t)); + for (; in != end; in += 8) { + uint64_t m = U8TO64_LE(in); + v3 ^= m; + SIPROUND; SIPROUND; + v0 ^= m; + } + const int left = inlen & 7; + uint64_t b = ((uint64_t)inlen) << 56; + switch (left) { + case 7: b |= ((uint64_t)in[6]) << 48; /* fall through */ + case 6: b |= ((uint64_t)in[5]) << 40; /* fall through */ + case 5: b |= ((uint64_t)in[4]) << 32; /* fall through */ + case 4: b |= ((uint64_t)in[3]) << 24; /* fall through */ + case 3: b |= ((uint64_t)in[2]) << 16; /* fall through */ + case 2: b |= ((uint64_t)in[1]) << 8; /* fall through */ + case 1: b |= ((uint64_t)in[0]); break; + case 0: break; + } + v3 ^= b; + SIPROUND; SIPROUND; + v0 ^= b; + v2 ^= 0xff; + SIPROUND; SIPROUND; SIPROUND; SIPROUND; + b = v0 ^ v1 ^ v2 ^ v3; + uint64_t out = 0; + U64TO8_LE((uint8_t*)&out, b); + return out; +} + +//----------------------------------------------------------------------------- +// MurmurHash3 was written by Austin Appleby, and is placed in the public +// domain. The author hereby disclaims copyright to this source code. +// +// Murmur3_86_128 +//----------------------------------------------------------------------------- +static uint64_t MM86128(const void *key, const int len, uint32_t seed) { +#define ROTL32(x, r) ((x << r) | (x >> (32 - r))) +#define FMIX32(h) h^=h>>16; h*=0x85ebca6b; h^=h>>13; h*=0xc2b2ae35; h^=h>>16; + const uint8_t * data = (const uint8_t*)key; + const int nblocks = len / 16; + uint32_t h1 = seed; + uint32_t h2 = seed; + uint32_t h3 = seed; + uint32_t h4 = seed; + uint32_t c1 = 0x239b961b; + uint32_t c2 = 0xab0e9789; + uint32_t c3 = 0x38b34ae5; + uint32_t c4 = 0xa1e38b93; + const uint32_t * blocks = (const uint32_t *)(data + nblocks*16); + for (int i = -nblocks; i; i++) { + uint32_t k1 = blocks[i*4+0]; + uint32_t k2 = blocks[i*4+1]; + uint32_t k3 = blocks[i*4+2]; + uint32_t k4 = blocks[i*4+3]; + k1 *= c1; k1 = ROTL32(k1,15); k1 *= c2; h1 ^= k1; + h1 = ROTL32(h1,19); h1 += h2; h1 = h1*5+0x561ccd1b; + k2 *= c2; k2 = ROTL32(k2,16); k2 *= c3; h2 ^= k2; + h2 = ROTL32(h2,17); h2 += h3; h2 = h2*5+0x0bcaa747; + k3 *= c3; k3 = ROTL32(k3,17); k3 *= c4; h3 ^= k3; + h3 = ROTL32(h3,15); h3 += h4; h3 = h3*5+0x96cd1c35; + k4 *= c4; k4 = ROTL32(k4,18); k4 *= c1; h4 ^= k4; + h4 = ROTL32(h4,13); h4 += h1; h4 = h4*5+0x32ac3b17; + } + const uint8_t * tail = (const uint8_t*)(data + nblocks*16); + uint32_t k1 = 0; + uint32_t k2 = 0; + uint32_t k3 = 0; + uint32_t k4 = 0; + switch(len & 15) { + case 15: k4 ^= tail[14] << 16; /* fall through */ + case 14: k4 ^= tail[13] << 8; /* fall through */ + case 13: k4 ^= tail[12] << 0; + k4 *= c4; k4 = ROTL32(k4,18); k4 *= c1; h4 ^= k4; + /* fall through */ + case 12: k3 ^= tail[11] << 24; /* fall through */ + case 11: k3 ^= tail[10] << 16; /* fall through */ + case 10: k3 ^= tail[ 9] << 8; /* fall through */ + case 9: k3 ^= tail[ 8] << 0; + k3 *= c3; k3 = ROTL32(k3,17); k3 *= c4; h3 ^= k3; + /* fall through */ + case 8: k2 ^= tail[ 7] << 24; /* fall through */ + case 7: k2 ^= tail[ 6] << 16; /* fall through */ + case 6: k2 ^= tail[ 5] << 8; /* fall through */ + case 5: k2 ^= tail[ 4] << 0; + k2 *= c2; k2 = ROTL32(k2,16); k2 *= c3; h2 ^= k2; + /* fall through */ + case 4: k1 ^= tail[ 3] << 24; /* fall through */ + case 3: k1 ^= tail[ 2] << 16; /* fall through */ + case 2: k1 ^= tail[ 1] << 8; /* fall through */ + case 1: k1 ^= tail[ 0] << 0; + k1 *= c1; k1 = ROTL32(k1,15); k1 *= c2; h1 ^= k1; + /* fall through */ + }; + h1 ^= len; h2 ^= len; h3 ^= len; h4 ^= len; + h1 += h2; h1 += h3; h1 += h4; + h2 += h1; h3 += h1; h4 += h1; + FMIX32(h1); FMIX32(h2); FMIX32(h3); FMIX32(h4); + h1 += h2; h1 += h3; h1 += h4; + h2 += h1; h3 += h1; h4 += h1; + return (((uint64_t)h2)<<32)|h1; +} + +//----------------------------------------------------------------------------- +// xxHash Library +// Copyright (c) 2012-2021 Yann Collet +// All rights reserved. +// +// BSD 2-Clause License (https://www.opensource.org/licenses/bsd-license.php) +// +// xxHash3 +//----------------------------------------------------------------------------- +#define XXH_PRIME_1 11400714785074694791ULL +#define XXH_PRIME_2 14029467366897019727ULL +#define XXH_PRIME_3 1609587929392839161ULL +#define XXH_PRIME_4 9650029242287828579ULL +#define XXH_PRIME_5 2870177450012600261ULL + +static uint64_t XXH_read64(const void* memptr) { + uint64_t val; + memcpy(&val, memptr, sizeof(val)); + return val; +} + +static uint32_t XXH_read32(const void* memptr) { + uint32_t val; + memcpy(&val, memptr, sizeof(val)); + return val; +} + +static uint64_t XXH_rotl64(uint64_t x, int r) { + return (x << r) | (x >> (64 - r)); +} + +static uint64_t xxh3(const void* data, size_t len, uint64_t seed) { + const uint8_t* p = (const uint8_t*)data; + const uint8_t* const end = p + len; + uint64_t h64; + + if (len >= 32) { + const uint8_t* const limit = end - 32; + uint64_t v1 = seed + XXH_PRIME_1 + XXH_PRIME_2; + uint64_t v2 = seed + XXH_PRIME_2; + uint64_t v3 = seed + 0; + uint64_t v4 = seed - XXH_PRIME_1; + + do { + v1 += XXH_read64(p) * XXH_PRIME_2; + v1 = XXH_rotl64(v1, 31); + v1 *= XXH_PRIME_1; + + v2 += XXH_read64(p + 8) * XXH_PRIME_2; + v2 = XXH_rotl64(v2, 31); + v2 *= XXH_PRIME_1; + + v3 += XXH_read64(p + 16) * XXH_PRIME_2; + v3 = XXH_rotl64(v3, 31); + v3 *= XXH_PRIME_1; + + v4 += XXH_read64(p + 24) * XXH_PRIME_2; + v4 = XXH_rotl64(v4, 31); + v4 *= XXH_PRIME_1; + + p += 32; + } while (p <= limit); + + h64 = XXH_rotl64(v1, 1) + XXH_rotl64(v2, 7) + XXH_rotl64(v3, 12) + + XXH_rotl64(v4, 18); + + v1 *= XXH_PRIME_2; + v1 = XXH_rotl64(v1, 31); + v1 *= XXH_PRIME_1; + h64 ^= v1; + h64 = h64 * XXH_PRIME_1 + XXH_PRIME_4; + + v2 *= XXH_PRIME_2; + v2 = XXH_rotl64(v2, 31); + v2 *= XXH_PRIME_1; + h64 ^= v2; + h64 = h64 * XXH_PRIME_1 + XXH_PRIME_4; + + v3 *= XXH_PRIME_2; + v3 = XXH_rotl64(v3, 31); + v3 *= XXH_PRIME_1; + h64 ^= v3; + h64 = h64 * XXH_PRIME_1 + XXH_PRIME_4; + + v4 *= XXH_PRIME_2; + v4 = XXH_rotl64(v4, 31); + v4 *= XXH_PRIME_1; + h64 ^= v4; + h64 = h64 * XXH_PRIME_1 + XXH_PRIME_4; + } + else { + h64 = seed + XXH_PRIME_5; + } + + h64 += (uint64_t)len; + + while (p + 8 <= end) { + uint64_t k1 = XXH_read64(p); + k1 *= XXH_PRIME_2; + k1 = XXH_rotl64(k1, 31); + k1 *= XXH_PRIME_1; + h64 ^= k1; + h64 = XXH_rotl64(h64, 27) * XXH_PRIME_1 + XXH_PRIME_4; + p += 8; + } + + if (p + 4 <= end) { + h64 ^= (uint64_t)(XXH_read32(p)) * XXH_PRIME_1; + h64 = XXH_rotl64(h64, 23) * XXH_PRIME_2 + XXH_PRIME_3; + p += 4; + } + + while (p < end) { + h64 ^= (*p) * XXH_PRIME_5; + h64 = XXH_rotl64(h64, 11) * XXH_PRIME_1; + p++; + } + + h64 ^= h64 >> 33; + h64 *= XXH_PRIME_2; + h64 ^= h64 >> 29; + h64 *= XXH_PRIME_3; + h64 ^= h64 >> 32; + + return h64; +} + +// hashmap_sip returns a hash value for `data` using SipHash-2-4. +uint64_t hashmap_sip(const void *data, size_t len, uint64_t seed0, + uint64_t seed1) +{ + return SIP64((uint8_t*)data, len, seed0, seed1); +} + +// hashmap_murmur returns a hash value for `data` using Murmur3_86_128. +uint64_t hashmap_murmur(const void *data, size_t len, uint64_t seed0, + uint64_t seed1) +{ + (void)seed1; + return MM86128(data, len, seed0); +} + +uint64_t hashmap_xxhash3(const void *data, size_t len, uint64_t seed0, + uint64_t seed1) +{ + (void)seed1; + return xxh3(data, len ,seed0); +} + +//============================================================================== +// TESTS AND BENCHMARKS +// $ cc -DHASHMAP_TEST hashmap.c && ./a.out # run tests +// $ cc -DHASHMAP_TEST -O3 hashmap.c && BENCH=1 ./a.out # run benchmarks +//============================================================================== +#ifdef HASHMAP_TEST + +static size_t deepcount(struct hashmap *map) { + size_t count = 0; + for (size_t i = 0; i < map->nbuckets; i++) { + if (bucket_at(map, i)->dib) { + count++; + } + } + return count; +} + +#ifdef __GNUC__ +#pragma GCC diagnostic ignored "-Wpedantic" +#endif +#ifdef __clang__ +#pragma GCC diagnostic ignored "-Wunknown-warning-option" +#pragma GCC diagnostic ignored "-Wcompound-token-split-by-macro" +#pragma GCC diagnostic ignored "-Wgnu-statement-expression-from-macro-expansion" +#endif +#ifdef __GNUC__ +#pragma GCC diagnostic ignored "-Wunused-parameter" +#endif + +#include +#include +#include +#include +#include +#include "hashmap.h" + +static bool rand_alloc_fail = false; +static int rand_alloc_fail_odds = 3; // 1 in 3 chance malloc will fail. +static uintptr_t total_allocs = 0; +static uintptr_t total_mem = 0; + +static void *xmalloc(size_t size) { + if (rand_alloc_fail && rand()%rand_alloc_fail_odds == 0) { + return NULL; + } + void *mem = malloc(sizeof(uintptr_t)+size); + assert(mem); + *(uintptr_t*)mem = size; + total_allocs++; + total_mem += size; + return (char*)mem+sizeof(uintptr_t); +} + +static void xfree(void *ptr) { + if (ptr) { + total_mem -= *(uintptr_t*)((char*)ptr-sizeof(uintptr_t)); + free((char*)ptr-sizeof(uintptr_t)); + total_allocs--; + } +} + +static void shuffle(void *array, size_t numels, size_t elsize) { + char tmp[elsize]; + char *arr = array; + for (size_t i = 0; i < numels - 1; i++) { + int j = i + rand() / (RAND_MAX / (numels - i) + 1); + memcpy(tmp, arr + j * elsize, elsize); + memcpy(arr + j * elsize, arr + i * elsize, elsize); + memcpy(arr + i * elsize, tmp, elsize); + } +} + +static bool iter_ints(const void *item, void *udata) { + int *vals = *(int**)udata; + vals[*(int*)item] = 1; + return true; +} + +static int compare_ints_udata(const void *a, const void *b, void *udata) { + return *(int*)a - *(int*)b; +} + +static int compare_strs(const void *a, const void *b, void *udata) { + return strcmp(*(char**)a, *(char**)b); +} + +static uint64_t hash_int(const void *item, uint64_t seed0, uint64_t seed1) { + return hashmap_xxhash3(item, sizeof(int), seed0, seed1); + // return hashmap_sip(item, sizeof(int), seed0, seed1); + // return hashmap_murmur(item, sizeof(int), seed0, seed1); +} + +static uint64_t hash_str(const void *item, uint64_t seed0, uint64_t seed1) { + return hashmap_xxhash3(*(char**)item, strlen(*(char**)item), seed0, seed1); + // return hashmap_sip(*(char**)item, strlen(*(char**)item), seed0, seed1); + // return hashmap_murmur(*(char**)item, strlen(*(char**)item), seed0, seed1); +} + +static void free_str(void *item) { + xfree(*(char**)item); +} + +static void all(void) { + int seed = getenv("SEED")?atoi(getenv("SEED")):time(NULL); + int N = getenv("N")?atoi(getenv("N")):2000; + printf("seed=%d, count=%d, item_size=%zu\n", seed, N, sizeof(int)); + srand(seed); + + rand_alloc_fail = true; + + // test sip and murmur hashes + assert(hashmap_sip("hello", 5, 1, 2) == 2957200328589801622); + assert(hashmap_murmur("hello", 5, 1, 2) == 1682575153221130884); + assert(hashmap_xxhash3("hello", 5, 1, 2) == 2584346877953614258); + + int *vals; + while (!(vals = xmalloc(N * sizeof(int)))) {} + for (int i = 0; i < N; i++) { + vals[i] = i; + } + + struct hashmap *map; + + while (!(map = hashmap_new(sizeof(int), 0, seed, seed, + hash_int, compare_ints_udata, NULL, NULL))) {} + shuffle(vals, N, sizeof(int)); + for (int i = 0; i < N; i++) { + // // printf("== %d ==\n", vals[i]); + assert(map->count == (size_t)i); + assert(map->count == hashmap_count(map)); + assert(map->count == deepcount(map)); + const int *v; + assert(!hashmap_get(map, &vals[i])); + assert(!hashmap_delete(map, &vals[i])); + while (true) { + assert(!hashmap_set(map, &vals[i])); + if (!hashmap_oom(map)) { + break; + } + } + + for (int j = 0; j < i; j++) { + v = hashmap_get(map, &vals[j]); + assert(v && *v == vals[j]); + } + while (true) { + v = hashmap_set(map, &vals[i]); + if (!v) { + assert(hashmap_oom(map)); + continue; + } else { + assert(!hashmap_oom(map)); + assert(v && *v == vals[i]); + break; + } + } + v = hashmap_get(map, &vals[i]); + assert(v && *v == vals[i]); + v = hashmap_delete(map, &vals[i]); + assert(v && *v == vals[i]); + assert(!hashmap_get(map, &vals[i])); + assert(!hashmap_delete(map, &vals[i])); + assert(!hashmap_set(map, &vals[i])); + assert(map->count == (size_t)(i+1)); + assert(map->count == hashmap_count(map)); + assert(map->count == deepcount(map)); + } + + int *vals2; + while (!(vals2 = xmalloc(N * sizeof(int)))) {} + memset(vals2, 0, N * sizeof(int)); + assert(hashmap_scan(map, iter_ints, &vals2)); + + // Test hashmap_iter. This does the same as hashmap_scan above. + size_t iter = 0; + void *iter_val; + while (hashmap_iter (map, &iter, &iter_val)) { + assert (iter_ints(iter_val, &vals2)); + } + for (int i = 0; i < N; i++) { + assert(vals2[i] == 1); + } + xfree(vals2); + + shuffle(vals, N, sizeof(int)); + for (int i = 0; i < N; i++) { + const int *v; + v = hashmap_delete(map, &vals[i]); + assert(v && *v == vals[i]); + assert(!hashmap_get(map, &vals[i])); + assert(map->count == (size_t)(N-i-1)); + assert(map->count == hashmap_count(map)); + assert(map->count == deepcount(map)); + for (int j = N-1; j > i; j--) { + v = hashmap_get(map, &vals[j]); + assert(v && *v == vals[j]); + } + } + + for (int i = 0; i < N; i++) { + while (true) { + assert(!hashmap_set(map, &vals[i])); + if (!hashmap_oom(map)) { + break; + } + } + } + + assert(map->count != 0); + size_t prev_cap = map->cap; + hashmap_clear(map, true); + assert(prev_cap < map->cap); + assert(map->count == 0); + + + for (int i = 0; i < N; i++) { + while (true) { + assert(!hashmap_set(map, &vals[i])); + if (!hashmap_oom(map)) { + break; + } + } + } + + prev_cap = map->cap; + hashmap_clear(map, false); + assert(prev_cap == map->cap); + + hashmap_free(map); + + xfree(vals); + + + while (!(map = hashmap_new(sizeof(char*), 0, seed, seed, + hash_str, compare_strs, free_str, NULL))); + + for (int i = 0; i < N; i++) { + char *str; + while (!(str = xmalloc(16))); + snprintf(str, 16, "s%i", i); + while(!hashmap_set(map, &str)); + } + + hashmap_clear(map, false); + assert(hashmap_count(map) == 0); + + for (int i = 0; i < N; i++) { + char *str; + while (!(str = xmalloc(16))); + snprintf(str, 16, "s%i", i); + while(!hashmap_set(map, &str)); + } + + hashmap_free(map); + + if (total_allocs != 0) { + fprintf(stderr, "total_allocs: expected 0, got %lu\n", total_allocs); + exit(1); + } +} + +#define bench(name, N, code) {{ \ + if (strlen(name) > 0) { \ + printf("%-14s ", name); \ + } \ + size_t tmem = total_mem; \ + size_t tallocs = total_allocs; \ + uint64_t bytes = 0; \ + clock_t begin = clock(); \ + for (int i = 0; i < N; i++) { \ + (code); \ + } \ + clock_t end = clock(); \ + double elapsed_secs = (double)(end - begin) / CLOCKS_PER_SEC; \ + double bytes_sec = (double)bytes/elapsed_secs; \ + printf("%d ops in %.3f secs, %.0f ns/op, %.0f op/sec", \ + N, elapsed_secs, \ + elapsed_secs/(double)N*1e9, \ + (double)N/elapsed_secs \ + ); \ + if (bytes > 0) { \ + printf(", %.1f GB/sec", bytes_sec/1024/1024/1024); \ + } \ + if (total_mem > tmem) { \ + size_t used_mem = total_mem-tmem; \ + printf(", %.2f bytes/op", (double)used_mem/N); \ + } \ + if (total_allocs > tallocs) { \ + size_t used_allocs = total_allocs-tallocs; \ + printf(", %.2f allocs/op", (double)used_allocs/N); \ + } \ + printf("\n"); \ +}} + +static void benchmarks(void) { + int seed = getenv("SEED")?atoi(getenv("SEED")):time(NULL); + int N = getenv("N")?atoi(getenv("N")):5000000; + printf("seed=%d, count=%d, item_size=%zu\n", seed, N, sizeof(int)); + srand(seed); + + + int *vals = xmalloc(N * sizeof(int)); + for (int i = 0; i < N; i++) { + vals[i] = i; + } + + shuffle(vals, N, sizeof(int)); + + struct hashmap *map; + shuffle(vals, N, sizeof(int)); + + map = hashmap_new(sizeof(int), 0, seed, seed, hash_int, compare_ints_udata, + NULL, NULL); + bench("set", N, { + const int *v = hashmap_set(map, &vals[i]); + assert(!v); + }) + shuffle(vals, N, sizeof(int)); + bench("get", N, { + const int *v = hashmap_get(map, &vals[i]); + assert(v && *v == vals[i]); + }) + shuffle(vals, N, sizeof(int)); + bench("delete", N, { + const int *v = hashmap_delete(map, &vals[i]); + assert(v && *v == vals[i]); + }) + hashmap_free(map); + + map = hashmap_new(sizeof(int), N, seed, seed, hash_int, compare_ints_udata, + NULL, NULL); + bench("set (cap)", N, { + const int *v = hashmap_set(map, &vals[i]); + assert(!v); + }) + shuffle(vals, N, sizeof(int)); + bench("get (cap)", N, { + const int *v = hashmap_get(map, &vals[i]); + assert(v && *v == vals[i]); + }) + shuffle(vals, N, sizeof(int)); + bench("delete (cap)" , N, { + const int *v = hashmap_delete(map, &vals[i]); + assert(v && *v == vals[i]); + }) + + hashmap_free(map); + + + xfree(vals); + + if (total_allocs != 0) { + fprintf(stderr, "total_allocs: expected 0, got %lu\n", total_allocs); + exit(1); + } +} + +int main(void) { + hashmap_set_allocator(xmalloc, xfree); + + if (getenv("BENCH")) { + printf("Running hashmap.c benchmarks...\n"); + benchmarks(); + } else { + printf("Running hashmap.c tests...\n"); + all(); + printf("PASSED\n"); + } +} + + +#endif + + + diff --git a/src/hashmap/hashmap.h b/src/hashmap/hashmap.h new file mode 100644 index 0000000..4898ab0 --- /dev/null +++ b/src/hashmap/hashmap.h @@ -0,0 +1,62 @@ +// Copyright 2020 Joshua J Baker. All rights reserved. +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file. + +#ifndef HASHMAP_H +#define HASHMAP_H + +#include +#include +#include + +#if defined(__cplusplus) +extern "C" { +#endif // __cplusplus + +struct hashmap; + +struct hashmap *hashmap_new(size_t elsize, size_t cap, uint64_t seed0, + uint64_t seed1, + uint64_t (*hash)(const void *item, uint64_t seed0, uint64_t seed1), + int (*compare)(const void *a, const void *b, void *udata), + void (*elfree)(void *item), + void *udata); + +struct hashmap *hashmap_new_with_allocator(void *(*malloc)(size_t), + void *(*realloc)(void *, size_t), void (*free)(void*), size_t elsize, + size_t cap, uint64_t seed0, uint64_t seed1, + uint64_t (*hash)(const void *item, uint64_t seed0, uint64_t seed1), + int (*compare)(const void *a, const void *b, void *udata), + void (*elfree)(void *item), + void *udata); + +void hashmap_free(struct hashmap *map); +void hashmap_clear(struct hashmap *map, bool update_cap); +size_t hashmap_count(const struct hashmap *map); +bool hashmap_oom(struct hashmap *map); +const void *hashmap_get(const struct hashmap *map, const void *item); +const void *hashmap_set(struct hashmap *map, const void *item); +const void *hashmap_delete(struct hashmap *map, const void *item); +const void *hashmap_probe(struct hashmap *map, uint64_t position); +bool hashmap_scan(struct hashmap *map, bool (*iter)(const void *item, void *udata), void *udata); +bool hashmap_iter(struct hashmap *map, size_t *i, void **item); + +uint64_t hashmap_sip(const void *data, size_t len, uint64_t seed0, uint64_t seed1); +uint64_t hashmap_murmur(const void *data, size_t len, uint64_t seed0, uint64_t seed1); +uint64_t hashmap_xxhash3(const void *data, size_t len, uint64_t seed0, uint64_t seed1); + +const void *hashmap_get_with_hash(const struct hashmap *map, const void *key, uint64_t hash); +const void *hashmap_delete_with_hash(struct hashmap *map, const void *key, uint64_t hash); +const void *hashmap_set_with_hash(struct hashmap *map, const void *item, uint64_t hash); +void hashmap_set_grow_by_power(struct hashmap *map, size_t power); +void hashmap_set_load_factor(struct hashmap *map, double load_factor); + + +// DEPRECATED: use `hashmap_new_with_allocator` +void hashmap_set_allocator(void *(*malloc)(size_t), void (*free)(void*)); + +#if defined(__cplusplus) +} +#endif // __cplusplus + +#endif // HASHMAP_H diff --git a/src/model.c b/src/model.c new file mode 100644 index 0000000..a286a76 --- /dev/null +++ b/src/model.c @@ -0,0 +1,157 @@ +#include +#include +#include +#include +#include +#include "GL/glew.h" +#include "cglm/cglm.h" +#include "hashmap/hashmap.h" +#include "shape.h" + +static enum +{ + OBJ_MAX_LINE_LENGTH = 256, + OBJ_MAX_ELEMENTS = 1024, + SHAPE_MAX_COORDS = 4096, + SHAPE_MAX_INDICES = 1024, +}; + +static struct VertRef +{ + uint16_t xyzIndex; // refers to a vertex in the obj file + uint16_t uvIndex; // refers to a UV texture coord in the obj file + uint16_t outIndex; // index of the combined vertex (xyzuv) in the output array +}; + +static int VertRef_Compare(const void *itemA, const void *itemB, void *udata) +{ + const struct VertRef *a = itemA; + const struct VertRef *b = itemB; + + if (a->xyzIndex < b->xyzIndex) return -1; + else if (a->xyzIndex > b->xyzIndex) return 1; + else return a->uvIndex < b->uvIndex ? -1 + : a->uvIndex > b->uvIndex ? 1 + : 0; +} + +static uint64_t VertRef_Hash(const void *item, uint64_t seed0, uint64_t seed1) +{ + return hashmap_sip(item, 2 * sizeof(uint16_t), seed0, seed1); +} + +static void ReadCoords(const char *str, float *coords, int n) +{ + char *end = NULL; + + for (int i = 0; i < n; i++) + { + coords[i] = strtof(str, &end); + if (end == str) break; + str = end; + } +} + +static int ReadIndex(const char **str) +{ + const int maxLen = 15; + char buffer[maxLen + 1]; + int i; + + buffer[maxLen] = '\0'; + + for (i = 0; i < maxLen; i++) + { + char c = str[0][i]; + + if (c == '/' || c == '\0' || c == EOF || isspace(c)) + { + buffer[i++] = '\0'; + break; + } + + buffer[i] = c; + } + + *str += i; + return atoi(buffer); +} + +Shape *Model_ReadObjFile(const char *path) +{ + struct hashmap *map = hashmap_new(sizeof(struct VertRef), 256, 0, 0, VertRef_Hash, VertRef_Compare, NULL, NULL); + FILE *file = fopen(path, "r"); + + char line[OBJ_MAX_LINE_LENGTH]; + vec3 xyzVertices[OBJ_MAX_ELEMENTS]; + vec2 uvVertices[OBJ_MAX_ELEMENTS]; + int xyzNum = 0; + int uvNum = 0; + + GLfloat outVertices[SHAPE_MAX_COORDS]; + GLushort outIndices[SHAPE_MAX_INDICES]; + int numVertices = 0; + int numIndices = 0; + int numTriangles = 0; + + while (fgets(&(line[0]), OBJ_MAX_LINE_LENGTH, file) != NULL) + { + switch (line[0]) + { + case '#': // comment + break; + case 'o': // named object + xyzNum = 0; + uvNum = 0; + break; + case 'v': // vertex or texture coord + if (line[1] == 't') + ReadCoords(&(line[3]), (void*)&(uvVertices[uvNum++]), 2); + else + ReadCoords(&(line[2]), (void*)&(xyzVertices[xyzNum++]), 3); + break; + case 's': // smooth shading option + break; + case 'f': // face (assume it's a triangle) + { + const char *str = &(line[2]); + + for (int i = 0; i < 3; i++) + { + struct VertRef vertRef; + vertRef.xyzIndex = ReadIndex(&str) - 1; + vertRef.uvIndex = ReadIndex(&str) - 1; + struct VertRef *existing = hashmap_get(map, &vertRef); + + if (existing == NULL) + { + vertRef.outIndex = numVertices++; + outIndices[numIndices++] = vertRef.outIndex; + GLfloat *vOut = &(outVertices[vertRef.outIndex * 5]); + vOut[0] = xyzVertices[vertRef.xyzIndex][0]; + vOut[1] = xyzVertices[vertRef.xyzIndex][1]; + vOut[2] = xyzVertices[vertRef.xyzIndex][2]; + vOut[3] = 1.0f - uvVertices[vertRef.uvIndex][0]; + vOut[4] = uvVertices[vertRef.uvIndex][1]; + hashmap_set(map, &vertRef); + } + else + { + outIndices[numIndices++] = existing->outIndex; + } + } + + numTriangles++; + } + break; + default: + break; + } + } + + fclose(file); + hashmap_free(map); + + printf("Imported model with %d triangles.\n", numTriangles); + return Shape_New(outVertices, numVertices * 5, outIndices, numIndices, 1); +} diff --git a/src/model.h b/src/model.h new file mode 100644 index 0000000..1bbff92 --- /dev/null +++ b/src/model.h @@ -0,0 +1,3 @@ +#pragma once + +Shape *Model_ReadObjFile(const char *path); diff --git a/src/render.c b/src/render.c index d9c6a30..1f63167 100644 --- a/src/render.c +++ b/src/render.c @@ -11,6 +11,7 @@ #include "camera.h" #include "game.h" #include "shape.h" +#include "model.h" static void LoadTextureArray(GLuint texture, const char* filePath, int nCols, int nRows) { @@ -237,7 +238,7 @@ bool Render_Init(GameState *gs) glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); printf("Initialized OpenGL.\n"); - gs->numShapes = 2; + gs->numShapes = 3; Shape *shape = Shape_MakePyramid(5); gs->shapes[0] = shape; @@ -255,12 +256,21 @@ bool Render_Init(GameState *gs) glGenBuffers(1, &shape->EBO); printf("Created shape 1.\n"); + shape = Model_ReadObjFile("res/model/human.obj"); + gs->shapes[2] = shape; + glGenVertexArrays(1, &shape->VAO); + glGenBuffers(1, &shape->VBO); + glGenBuffers(1, &shape->IBO); + glGenBuffers(1, &shape->EBO); + printf("Created shape 2.\n"); + LoadTexture(gs); printf("Loaded textures.\n"); Camera_Init(&gs->camera); InitShapeBuffers(gs->shapes[0]); InitShapeBuffers(gs->shapes[1]); + InitShapeBuffers(gs->shapes[2]); printf("Initialized buffers.\n"); return true; } diff --git a/src/shape.c b/src/shape.c index 10c5a50..4f8024a 100644 --- a/src/shape.c +++ b/src/shape.c @@ -4,6 +4,43 @@ #include "cglm/cglm.h" #include "shape.h" +Shape *Shape_New(GLfloat *vertices, int numVertices, GLushort *indices, int numIndices, int numInstances) +{ + size_t numBytesVertices = numVertices * sizeof(GLfloat); + size_t numBytesIndices = numIndices * sizeof(GLushort); + Shape *shape = malloc(sizeof(Shape) + numBytesVertices + numBytesIndices); + + void *tempPtr = (void*)(shape + 1); + shape->vertices = tempPtr; + shape->numVertices = numBytesVertices / sizeof(vertices[0]); + shape->indices = tempPtr + numBytesVertices; + shape->numIndices = numBytesIndices / sizeof(indices[0]); + + memcpy(shape->vertices, vertices, numBytesVertices); + memcpy(shape->indices, indices, numBytesIndices); + + shape->instances = calloc(numInstances, sizeof(ShapeInstance) + SHAPE_INSTANCE_SIZE); + shape->instanceData = (void*)(shape->instances + numInstances); + shape->numInstances = numInstances; + + for (int i = 0; i < numInstances; i++) + { + ShapeInstance *instance = shape->instances + i; + instance->position[0] = 0.0f; + instance->position[1] = 20.0f; + instance->position[2] = (5.0f * i) - 10.0f; + instance->scale = 2.0f; + instance->collisionRadius = 1.0f; + glm_vec3_zero(instance->velocity); + glm_vec3_zero(instance->rotation); + float *textureId = shape->instanceData + (i * 17); + *textureId = 6; + mat4 *matrix = (void*)(textureId + 1); + } + + return shape; +} + Shape *Shape_MakePyramid(int numInstances) { GLfloat vertices[] = diff --git a/src/shape.h b/src/shape.h index e9e09fc..c7dd1d4 100644 --- a/src/shape.h +++ b/src/shape.h @@ -33,5 +33,6 @@ typedef struct GLuint EBO; } Shape; +Shape *Shape_New(GLfloat *vertices, int numVertices, GLushort *indices, int numIndices, int numInstances); Shape *Shape_MakePyramid(int numInstances); Shape *Shape_MakePlane();