From eaaf524f99286f40ed51cf8a74d3e52690ab3ffe Mon Sep 17 00:00:00 2001 From: IntegratedQuantum <43880493+IntegratedQuantum@users.noreply.github.com> Date: Sat, 31 May 2025 10:49:50 +0200 Subject: [PATCH] Signs (#1446) - [x] Rotation (already merged) - [x] basic GUI - [x] sign models and textures - [x] sign blocks - [x] update the text on the client - [x] Figure out block entity rendering - [x] Render the text to a texture on update - [x] Render the texture in the world in the location of the sign - [x] Use varint instead of u32 for storing the block data lengths, (now, while we can still change it) - [x] Sync the text with the server and all clients - [x] Figure out block entity storage on the server - [x] Send the entity data of the initial chunk - [x] Store the text on the server - [x] Set the chunk as changed whenever a block entity data update happens, so we actually store it - [x] Disable or figure out optimized local chunk transmission - [x] fix memory leak - [x] Rethink some of the API (do we need onPlace/onBreak, when there is unload and updateData?) - [x] Remove the background shadow from text, it produces too much aliasing - [x] Figure out if the default should be black or white - [x] Correctly center the text - [x] Why are newlines not working? - [x] Check if a deadlock is possible on deinit --- it would be possible only if another thread has a reference to it, which should not be the case when unload is called. - [x] Set the text margin and sizes reasonably - [x] Make sure the GUI fits with the sign width - [x] Create an issue for configurable sign texture size and configurable default color fixes #367 --------- Co-authored-by: Carrie <122191047+careeoki@users.noreply.github.com> Co-authored-by: OneAvargeCoder193 <85588535+OneAvargeCoder193@users.noreply.github.com> --- assets/cubyz/blocks/sign/_defaults.zig.zon | 18 + assets/cubyz/blocks/sign/baobab.zig.zon | 6 + assets/cubyz/blocks/sign/birch.zig.zon | 6 + assets/cubyz/blocks/sign/mahogany.zig.zon | 6 + assets/cubyz/blocks/sign/oak.zig.zon | 6 + assets/cubyz/blocks/sign/pine.zig.zon | 6 + assets/cubyz/blocks/sign/willow.zig.zon | 6 + assets/cubyz/blocks/textures/sign/baobab.png | Bin 0 -> 317 bytes assets/cubyz/blocks/textures/sign/birch.png | Bin 0 -> 309 bytes .../cubyz/blocks/textures/sign/mahogany.png | Bin 0 -> 306 bytes assets/cubyz/blocks/textures/sign/oak.png | Bin 0 -> 303 bytes assets/cubyz/blocks/textures/sign/pine.png | Bin 0 -> 296 bytes assets/cubyz/blocks/textures/sign/willow.png | Bin 0 -> 306 bytes assets/cubyz/items/textures/sign/baobab.png | Bin 0 -> 332 bytes assets/cubyz/items/textures/sign/birch.png | Bin 0 -> 332 bytes assets/cubyz/items/textures/sign/mahogany.png | Bin 0 -> 327 bytes assets/cubyz/items/textures/sign/oak.png | Bin 0 -> 333 bytes assets/cubyz/items/textures/sign/pine.png | Bin 0 -> 324 bytes assets/cubyz/items/textures/sign/willow.png | Bin 0 -> 322 bytes assets/cubyz/models/sign/ceiling.obj | 123 ++++++ assets/cubyz/models/sign/floor.obj | 84 ++++ assets/cubyz/models/sign/side.obj | 45 ++ assets/cubyz/shaders/block_entity/sign.frag | 26 ++ assets/cubyz/shaders/block_entity/sign.vert | 69 +++ src/Inventory.zig | 15 +- src/block_entity.zig | 401 +++++++++++++++--- src/chunk.zig | 20 +- src/graphics.zig | 48 ++- src/gui/windows/_windowlist.zig | 1 + src/gui/windows/gpu_performance_measuring.zig | 2 + src/gui/windows/sign_editor.zig | 68 +++ src/network.zig | 84 +++- src/renderer.zig | 6 +- src/renderer/chunk_meshing.zig | 23 +- src/renderer/mesh_storage.zig | 27 +- src/rotation/sign.zig | 11 +- src/server/storage.zig | 142 +++++-- src/server/world.zig | 27 +- src/utils.zig | 71 +++- 39 files changed, 1202 insertions(+), 145 deletions(-) create mode 100644 assets/cubyz/blocks/sign/_defaults.zig.zon create mode 100644 assets/cubyz/blocks/sign/baobab.zig.zon create mode 100644 assets/cubyz/blocks/sign/birch.zig.zon create mode 100644 assets/cubyz/blocks/sign/mahogany.zig.zon create mode 100644 assets/cubyz/blocks/sign/oak.zig.zon create mode 100644 assets/cubyz/blocks/sign/pine.zig.zon create mode 100644 assets/cubyz/blocks/sign/willow.zig.zon create mode 100644 assets/cubyz/blocks/textures/sign/baobab.png create mode 100644 assets/cubyz/blocks/textures/sign/birch.png create mode 100644 assets/cubyz/blocks/textures/sign/mahogany.png create mode 100644 assets/cubyz/blocks/textures/sign/oak.png create mode 100644 assets/cubyz/blocks/textures/sign/pine.png create mode 100644 assets/cubyz/blocks/textures/sign/willow.png create mode 100644 assets/cubyz/items/textures/sign/baobab.png create mode 100644 assets/cubyz/items/textures/sign/birch.png create mode 100644 assets/cubyz/items/textures/sign/mahogany.png create mode 100644 assets/cubyz/items/textures/sign/oak.png create mode 100644 assets/cubyz/items/textures/sign/pine.png create mode 100644 assets/cubyz/items/textures/sign/willow.png create mode 100644 assets/cubyz/models/sign/ceiling.obj create mode 100644 assets/cubyz/models/sign/floor.obj create mode 100644 assets/cubyz/models/sign/side.obj create mode 100644 assets/cubyz/shaders/block_entity/sign.frag create mode 100644 assets/cubyz/shaders/block_entity/sign.vert create mode 100644 src/gui/windows/sign_editor.zig diff --git a/assets/cubyz/blocks/sign/_defaults.zig.zon b/assets/cubyz/blocks/sign/_defaults.zig.zon new file mode 100644 index 00000000..cebedde5 --- /dev/null +++ b/assets/cubyz/blocks/sign/_defaults.zig.zon @@ -0,0 +1,18 @@ +.{ + .tags = .{.wood}, + .blockHealth = 2, + .drops = .{ + .{.items = .{.auto}}, + }, + .viewThrough = true, + .alwaysViewThrough = true, + .collide = false, + .rotation = .sign, + .model = .{ + .side = "cubyz:sign/side", + .ceiling = "cubyz:sign/ceiling", + .floor = "cubyz:sign/floor", + }, + .blockEntity = .sign, + .lodReplacement = "cubyz:air", +} diff --git a/assets/cubyz/blocks/sign/baobab.zig.zon b/assets/cubyz/blocks/sign/baobab.zig.zon new file mode 100644 index 00000000..be7382b0 --- /dev/null +++ b/assets/cubyz/blocks/sign/baobab.zig.zon @@ -0,0 +1,6 @@ +.{ + .texture = "cubyz:sign/baobab", + .item = .{ + .texture = "sign/baobab.png", + }, +} diff --git a/assets/cubyz/blocks/sign/birch.zig.zon b/assets/cubyz/blocks/sign/birch.zig.zon new file mode 100644 index 00000000..cebf1227 --- /dev/null +++ b/assets/cubyz/blocks/sign/birch.zig.zon @@ -0,0 +1,6 @@ +.{ + .texture = "cubyz:sign/birch", + .item = .{ + .texture = "sign/birch.png", + }, +} diff --git a/assets/cubyz/blocks/sign/mahogany.zig.zon b/assets/cubyz/blocks/sign/mahogany.zig.zon new file mode 100644 index 00000000..ffdedbdb --- /dev/null +++ b/assets/cubyz/blocks/sign/mahogany.zig.zon @@ -0,0 +1,6 @@ +.{ + .texture = "cubyz:sign/mahogany", + .item = .{ + .texture = "sign/mahogany.png", + }, +} diff --git a/assets/cubyz/blocks/sign/oak.zig.zon b/assets/cubyz/blocks/sign/oak.zig.zon new file mode 100644 index 00000000..dbe85bd5 --- /dev/null +++ b/assets/cubyz/blocks/sign/oak.zig.zon @@ -0,0 +1,6 @@ +.{ + .texture = "cubyz:sign/oak", + .item = .{ + .texture = "sign/oak.png", + }, +} diff --git a/assets/cubyz/blocks/sign/pine.zig.zon b/assets/cubyz/blocks/sign/pine.zig.zon new file mode 100644 index 00000000..b35d08bf --- /dev/null +++ b/assets/cubyz/blocks/sign/pine.zig.zon @@ -0,0 +1,6 @@ +.{ + .texture = "cubyz:sign/pine", + .item = .{ + .texture = "sign/pine.png", + }, +} diff --git a/assets/cubyz/blocks/sign/willow.zig.zon b/assets/cubyz/blocks/sign/willow.zig.zon new file mode 100644 index 00000000..2af754fb --- /dev/null +++ b/assets/cubyz/blocks/sign/willow.zig.zon @@ -0,0 +1,6 @@ +.{ + .texture = "cubyz:sign/willow", + .item = .{ + .texture = "sign/willow.png", + }, +} diff --git a/assets/cubyz/blocks/textures/sign/baobab.png b/assets/cubyz/blocks/textures/sign/baobab.png new file mode 100644 index 0000000000000000000000000000000000000000..26a19bf707a3ce1bdca2c45ae404a583c2de070d GIT binary patch literal 317 zcmV-D0mA-?P)TCqL1uDO+OOkql}QdVQ-J-s zPRGM?Iv$oSO|(Vc7B{;?HVw)ozsx`u*OwUp`)lkPyqNd@8$3R@d;a(YL{gZzc-*|A P00000NkvXXu0mjfuU?8P literal 0 HcmV?d00001 diff --git a/assets/cubyz/blocks/textures/sign/birch.png b/assets/cubyz/blocks/textures/sign/birch.png new file mode 100644 index 0000000000000000000000000000000000000000..38953697fecb15f460c48cdab2594893ad009263 GIT binary patch literal 309 zcmV-50m}Y~P)LX>Xk6qdBor!Z?d@mZ~ms z)=XGBsG1bpD3Si6LKR7fQ?gX=Wj0`0?l;kLj1S(^b z79ONbs4+6aTPR2&0@ISpsYL3*YfA`2vUWg{#{-jLNerimbGXEpx+`_G^GxO|VR z>N)_Tb4V}s8;i~X5SEA!iCT3X(K*5r0hp`@VCYT8q{_E$6<0e`387nsZWTjsFuKWD z`B5|_RtZr~5uu2DNI6-l_aRYBd>;}T4kcnq^oj$hC+qRc?UBvSnaq=x+Xj2P&f{r4kEeA{Q|*yo zo!X<14P^Jf8?>MQGl1hY4h?=>+~s!-u6921=MJnHn$t5PZXW;u002ovPDHLkV1oV_ Bd_e#J literal 0 HcmV?d00001 diff --git a/assets/cubyz/blocks/textures/sign/pine.png b/assets/cubyz/blocks/textures/sign/pine.png new file mode 100644 index 0000000000000000000000000000000000000000..af5768afe19788ed4c9d44025abc590680554876 GIT binary patch literal 296 zcmV+@0oVSCP)bvMukD)wtLGa1jW{r2_wF>T@+ zfL=zl*Sf`e839OXKytxt;+kGYQW^kgI{{#~jTO_f-yt|oXTd2Ef~!PIL&a>b8pQxs z`jb6H#VV3qYBH;rRB#$!av?T;W@dXeQH^p=7Zie{?c~l#Q|z|!ZXYrD`3_WMXS^Lx zasePV{$~VxlAT|2sfm8$09@M%mu#-<{Jk8l7(ACe%&@2Hc-T+J!+u)Qtd_|4#x2p0 u4Px-G8N8g}8Iy+wvm5%a!O6V;zv>DEc%f!EWT__r0000v5JX>LZJeTfLGC&Am_O)O^#A`L2r#yR9?$}=DyW({Su?Za-Ff@^{FpCR0O&(P z--@4H9})m@nD8kf7c2B35r+wYrtJW@)^_D!8&a66;MtE9DwwLkR0Y@C;zU+%Kd7EP zSE!)ahpEY@MAL@scK{zpMA!Ez;Y6~q?JaAFdQ_98poFO`O&jouEJ(Mw);23Q4inCl z$bM}##bL_s-Vgb-6zW8_6Mid7&AQR1*(^w!HW1>t?ba{lD4Zz?am=_s<8i;ukNa&t z@)SqnyECVs90oX3z7}9#4S@4AP6Ik0{~z$|!jTNXH}$cYR2j-#=>Px#07*qoM6N<$ Ef@++IC;$Ke literal 0 HcmV?d00001 diff --git a/assets/cubyz/items/textures/sign/baobab.png b/assets/cubyz/items/textures/sign/baobab.png new file mode 100644 index 0000000000000000000000000000000000000000..7f3e5dbbcdebb7bff691ad60517b024ec156b311 GIT binary patch literal 332 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4(FKU=;RraSXBOO-@)KU|?imU|^EY z^8fhK1!c#g+-lf19FSmKSH@%0bmzvE1XGzbgF6gzj(KhR@AoHZ^l*N5d01chf9k_; zXWe;HC*J$Nds=>KisQ698-5GiF+Cx5;o$A_^B?4>m7HVHTextqQhYNlgHP3Dbp+PH;9{koz#N=ZMilgCG0% zOwl+zQ@dQUOn!gQeTF0kGj9or4KB(?4Xw;}4cCDJam*Kz*`yChva$ZnmWa~N^%LF^o=Kd3xLx-z-l`9 z`Dr3e!vMSEweVQ%^(-YcL704Sd%UUvbtEeSaJL{+*+JF@)KMiMe={;w{xoYbVIQgN Z48C!OWXDSvw#)zk002ovPDHLkV1oIqk7ED; literal 0 HcmV?d00001 diff --git a/assets/cubyz/items/textures/sign/oak.png b/assets/cubyz/items/textures/sign/oak.png new file mode 100644 index 0000000000000000000000000000000000000000..d2c8a48dc13409ad184456e8a391406ccf2f8c74 GIT binary patch literal 333 zcmV-T0kZyyP)rC|NxTa5S&V1Y4AHN_c(`B<^3!D=|nV7LLoe5?#_KmEo~=wU8{ z!hieeH^YNx9~qpDKb7_P7FC1z?nz_ZPos3$O6Vm(M=CQZY_zL!jx znI|)u1o)>c0GRmwyewcAE-e$l=7v9Pqvck0FrL1%s?#5~!O{!DXeU-#EWMDu0K`d#rc)ycEIEnFIP7c9 z2!OQE>om|qgS4@a&LpAf)JO{gpqA!26#|m&&!1-%HUOlj4D;m{s@`88)p%~b;NvQN zBon`%4>}$;hhJ=2s*Nd((WGFp(jHbAqbz<8CK&;LkrALyMknJ>8;`o;BbD939m5c2 UIoxI_4gdfE07*qoM6N<$g2MBOR{#J2 literal 0 HcmV?d00001 diff --git a/assets/cubyz/models/sign/ceiling.obj b/assets/cubyz/models/sign/ceiling.obj new file mode 100644 index 00000000..4507b9c1 --- /dev/null +++ b/assets/cubyz/models/sign/ceiling.obj @@ -0,0 +1,123 @@ +o unknown +v 0.4687500000000001 0 0.5625 +v 0.5312500000000001 0 0.5625 +v 0.5312499999999999 1 0.5625 +v 0.4687499999999999 1 0.5625 +v 0.4687499999999999 1 0 +v 0.5312499999999999 1 0 +v 0.5312500000000001 0 0 +v 0.4687500000000001 0 0 +vt 0 0.25 +vt 0 0.234375 +vt 0.25 0.234375 +vt 0.25 0.25 +vt 0.25 0.125 +vt 0.25 0.109375 +vt 0.5 0.109375 +vt 0.5 0.125 +vt 0.5 0.25 +vt 0.5 0.109375 +vt 0.515625 0.109375 +vt 0.515625 0.25 +vt 0.984375 0.25 +vt 0.984375 0.109375 +vt 1 0.109375 +vt 1 0.25 +vt 0 0.5 +vt 0 0.359375 +vt 0.25 0.359375 +vt 0.25 0.5 +vt 0.25 0.5 +vt 0.25 0.359375 +vt 0.5 0.359375 +vt 0.5 0.5 +vn 0 0 1 +vn 0 0 -1 +vn 2.220446049250313e-16 -1 0 +vn -2.220446049250313e-16 1 0 +vn 1 2.220446049250313e-16 0 +vn -1 -2.220446049250313e-16 0 +f 5/5/2 6/6/2 7/7/2 8/8/2 +f 1/9/3 8/10/3 7/11/3 2/12/3 +f 3/13/4 6/14/4 5/15/4 4/16/4 +f 2/17/5 7/18/5 6/19/5 3/20/5 +f 4/21/6 5/22/6 8/23/6 1/24/6 +f 1/1/1 2/2/1 3/3/1 4/4/1 +o unknown +v 0.46875 0.125 1 +v 0.53125 0.125 1 +v 0.53125 0.25 1 +v 0.46875 0.25 1 +v 0.46875 0.25 0.5625 +v 0.53125 0.25 0.5625 +v 0.53125 0.125 0.5625 +v 0.46875 0.125 0.5625 +vt 0.109375 0.109375 +vt 0.109375 0.09375 +vt 0.140625 0.09375 +vt 0.140625 0.109375 +vt 0.609375 0.109375 +vt 0.609375 0 +vt 0.625 0 +vt 0.625 0.109375 +vt 0.875 0.109375 +vt 0.875 0 +vt 0.890625 0 +vt 0.890625 0.109375 +vt 0.109375 0.359375 +vt 0.109375 0.25 +vt 0.140625 0.25 +vt 0.140625 0.359375 +vt 0.359375 0.359375 +vt 0.359375 0.25 +vt 0.390625 0.25 +vt 0.390625 0.359375 +vn 0 0 1 +vn 2.220446049250313e-16 -1 0 +vn -2.220446049250313e-16 1 0 +vn 1 2.220446049250313e-16 0 +vn -1 -2.220446049250313e-16 0 +f 9/25/7 10/26/7 11/27/7 12/28/7 +f 9/29/8 16/30/8 15/31/8 10/32/8 +f 11/33/9 14/34/9 13/35/9 12/36/9 +f 10/37/10 15/38/10 14/39/10 11/40/10 +f 12/41/11 13/42/11 16/43/11 9/44/11 +o unknown +v 0.46875 0.75 1 +v 0.53125 0.75 1 +v 0.53125 0.875 1 +v 0.46875 0.875 1 +v 0.46875 0.875 0.5625 +v 0.53125 0.875 0.5625 +v 0.53125 0.75 0.5625 +v 0.46875 0.75 0.5625 +vt 0.109375 0.109375 +vt 0.109375 0.09375 +vt 0.140625 0.09375 +vt 0.140625 0.109375 +vt 0.609375 0.109375 +vt 0.609375 0 +vt 0.625 0 +vt 0.625 0.109375 +vt 0.875 0.109375 +vt 0.875 0 +vt 0.890625 0 +vt 0.890625 0.109375 +vt 0.109375 0.359375 +vt 0.109375 0.25 +vt 0.140625 0.25 +vt 0.140625 0.359375 +vt 0.359375 0.359375 +vt 0.359375 0.25 +vt 0.390625 0.25 +vt 0.390625 0.359375 +vn 0 0 1 +vn 2.220446049250313e-16 -1 0 +vn -2.220446049250313e-16 1 0 +vn 1 2.220446049250313e-16 0 +vn -1 -2.220446049250313e-16 0 +f 17/45/12 18/46/12 19/47/12 20/48/12 +f 17/49/13 24/50/13 23/51/13 18/52/13 +f 19/53/14 22/54/14 21/55/14 20/56/14 +f 18/57/15 23/58/15 22/59/15 19/60/15 +f 20/61/16 21/62/16 24/63/16 17/64/16 \ No newline at end of file diff --git a/assets/cubyz/models/sign/floor.obj b/assets/cubyz/models/sign/floor.obj new file mode 100644 index 00000000..72e5b716 --- /dev/null +++ b/assets/cubyz/models/sign/floor.obj @@ -0,0 +1,84 @@ +o unknown +v 0.4687500000000001 0 1 +v 0.5312500000000001 0 1 +v 0.5312499999999999 1 1 +v 0.4687499999999999 1 1 +v 0.4687499999999999 1 0.4375 +v 0.5312499999999999 1 0.4375 +v 0.5312500000000001 0 0.4375 +v 0.4687500000000001 0 0.4375 +vt 0 0.25 +vt 0 0.234375 +vt 0.25 0.234375 +vt 0.25 0.25 +vt 0.25 0.125 +vt 0.25 0.109375 +vt 0.5 0.109375 +vt 0.5 0.125 +vt 0.5 0.25 +vt 0.5 0.109375 +vt 0.515625 0.109375 +vt 0.515625 0.25 +vt 0.984375 0.25 +vt 0.984375 0.109375 +vt 1 0.109375 +vt 1 0.25 +vt 0 0.5 +vt 0 0.359375 +vt 0.25 0.359375 +vt 0.25 0.5 +vt 0.25 0.5 +vt 0.25 0.359375 +vt 0.5 0.359375 +vt 0.5 0.5 +vn 0 0 1 +vn 0 0 -1 +vn 2.220446049250313e-16 -1 0 +vn -2.220446049250313e-16 1 0 +vn 1 2.220446049250313e-16 0 +vn -1 -2.220446049250313e-16 0 +f 1/9/3 8/10/3 7/11/3 2/12/3 +f 3/13/4 6/14/4 5/15/4 4/16/4 +f 2/17/5 7/18/5 6/19/5 3/20/5 +f 4/21/6 5/22/6 8/23/6 1/24/6 +f 1/1/1 2/2/1 3/3/1 4/4/1 +f 5/5/2 6/6/2 7/7/2 8/8/2 +o unknown +v 0.46875 0.4375 0.4375 +v 0.53125 0.4375 0.4375 +v 0.53125 0.5625 0.4375 +v 0.46875 0.5625 0.4375 +v 0.46875 0.5625 0 +v 0.53125 0.5625 0 +v 0.53125 0.4375 0 +v 0.46875 0.4375 0 +vt 0.359375 0.015625 +vt 0.359375 0 +vt 0.390625 0 +vt 0.390625 0.015625 +vt 0.609375 0.109375 +vt 0.609375 0 +vt 0.625 0 +vt 0.625 0.109375 +vt 0.875 0.109375 +vt 0.875 0 +vt 0.890625 0 +vt 0.890625 0.109375 +vt 0.109375 0.359375 +vt 0.109375 0.25 +vt 0.140625 0.25 +vt 0.140625 0.359375 +vt 0.359375 0.359375 +vt 0.359375 0.25 +vt 0.390625 0.25 +vt 0.390625 0.359375 +vn 0 0 -1 +vn 2.220446049250313e-16 -1 0 +vn -2.220446049250313e-16 1 0 +vn 1 2.220446049250313e-16 0 +vn -1 -2.220446049250313e-16 0 +f 13/25/7 14/26/7 15/27/7 16/28/7 +f 9/29/8 16/30/8 15/31/8 10/32/8 +f 11/33/9 14/34/9 13/35/9 12/36/9 +f 10/37/10 15/38/10 14/39/10 11/40/10 +f 12/41/11 13/42/11 16/43/11 9/44/11 \ No newline at end of file diff --git a/assets/cubyz/models/sign/side.obj b/assets/cubyz/models/sign/side.obj new file mode 100644 index 00000000..2b34755d --- /dev/null +++ b/assets/cubyz/models/sign/side.obj @@ -0,0 +1,45 @@ +o unknown +v 1.1102230246251565e-16 0 0.8125 +v 0.06250000000000011 0 0.8125 +v 0.06249999999999989 1 0.8125 +v -1.1102230246251565e-16 1 0.8125 +v -1.1102230246251565e-16 1 0.25 +v 0.06249999999999989 1 0.25 +v 0.06250000000000011 0 0.25 +v 1.1102230246251565e-16 0 0.25 +vt 0 0.25 +vt 0 0.234375 +vt 0.25 0.234375 +vt 0.25 0.25 +vt 0.25 0.125 +vt 0.25 0.109375 +vt 0.5 0.109375 +vt 0.5 0.125 +vt 0.5 0.25 +vt 0.5 0.109375 +vt 0.515625 0.109375 +vt 0.515625 0.25 +vt 0.984375 0.25 +vt 0.984375 0.109375 +vt 1 0.109375 +vt 1 0.25 +vt 0 0.5 +vt 0 0.359375 +vt 0.25 0.359375 +vt 0.25 0.5 +vt 0.25 0.5 +vt 0.25 0.359375 +vt 0.5 0.359375 +vt 0.5 0.5 +vn 0 0 1 +vn 0 0 -1 +vn 2.220446049250313e-16 -1 0 +vn -2.220446049250313e-16 1 0 +vn 1 2.220446049250313e-16 0 +vn -1 -2.220446049250313e-16 0 +f 1/9/3 8/10/3 7/11/3 2/12/3 +f 3/13/4 6/14/4 5/15/4 4/16/4 +f 2/17/5 7/18/5 6/19/5 3/20/5 +f 4/21/6 5/22/6 8/23/6 1/24/6 +f 1/1/1 2/2/1 3/3/1 4/4/1 +f 5/5/2 6/6/2 7/7/2 8/8/2 \ No newline at end of file diff --git a/assets/cubyz/shaders/block_entity/sign.frag b/assets/cubyz/shaders/block_entity/sign.frag new file mode 100644 index 00000000..e2526093 --- /dev/null +++ b/assets/cubyz/shaders/block_entity/sign.frag @@ -0,0 +1,26 @@ +#version 460 + +layout(location = 0) in vec3 mvVertexPos; +layout(location = 1) in vec3 direction; +layout(location = 2) in vec3 light; +layout(location = 3) in vec2 uv; +layout(location = 4) flat in vec3 normal; + +layout(location = 0) out vec4 fragColor; + +layout(binding = 0) uniform sampler2D textureSampler; + +layout(location = 9) uniform float contrast; + +float lightVariation(vec3 normal) { + const vec3 directionalPart = vec3(0, contrast/2, contrast); + const float baseLighting = 1 - contrast; + return baseLighting + dot(normal, directionalPart); +} + +void main() { + float normalVariation = lightVariation(normal); + + vec3 pixelLight = light*normalVariation; + fragColor = texture(textureSampler, uv)*vec4(pixelLight, 1); +} diff --git a/assets/cubyz/shaders/block_entity/sign.vert b/assets/cubyz/shaders/block_entity/sign.vert new file mode 100644 index 00000000..d9fc1fc5 --- /dev/null +++ b/assets/cubyz/shaders/block_entity/sign.vert @@ -0,0 +1,69 @@ +#version 460 + +layout(location = 0) out vec3 mvVertexPos; +layout(location = 1) out vec3 direction; +layout(location = 2) out vec3 light; +layout(location = 3) out vec2 uv; +layout(location = 4) flat out vec3 normal; + +layout(location = 0) uniform vec3 ambientLight; +layout(location = 1) uniform mat4 projectionMatrix; +layout(location = 2) uniform mat4 viewMatrix; +layout(location = 3) uniform ivec3 playerPositionInteger; +layout(location = 4) uniform vec3 playerPositionFraction; +layout(location = 5) uniform int quadIndex; +layout(location = 6) uniform uvec4 lightData; +layout(location = 7) uniform ivec3 chunkPos; +layout(location = 8) uniform ivec3 blockPos; + +struct QuadInfo { + vec3 normal; + vec3 corners[4]; + vec2 cornerUV[4]; + uint textureSlot; + int opaqueInLod; +}; + +layout(std430, binding = 4) buffer _quads +{ + QuadInfo quads[]; +}; + +void main() { + int faceID = gl_VertexID >> 2; + int vertexID = gl_VertexID & 3; + uint fullLight = lightData[vertexID]; + vec3 sunLight = vec3( + fullLight >> 25 & 31u, + fullLight >> 20 & 31u, + fullLight >> 15 & 31u + ); + vec3 blockLight = vec3( + fullLight >> 10 & 31u, + fullLight >> 5 & 31u, + fullLight >> 0 & 31u + ); + light = max(sunLight*ambientLight, blockLight)/31; + + vec3 position = vec3(blockPos); + + normal = quads[quadIndex].normal; + + position += quads[quadIndex].corners[vertexID]; + position += vec3(chunkPos - playerPositionInteger); + position -= playerPositionFraction; + + direction = position; + + vec4 mvPos = viewMatrix*vec4(position, 1); + gl_Position = projectionMatrix*mvPos; + mvVertexPos = mvPos.xyz; + vec2 maxUv = quads[quadIndex].cornerUV[0]; + vec2 minUv = quads[quadIndex].cornerUV[0]; + for(int i = 1; i < 4; i++) { + maxUv = max(maxUv, quads[quadIndex].cornerUV[i]); + minUv = min(minUv, quads[quadIndex].cornerUV[i]); + } + uv.x = (quads[quadIndex].cornerUV[vertexID].x == maxUv.x) ? 1 : 0; + uv.y = (quads[quadIndex].cornerUV[vertexID].y == maxUv.y) ? 1 : 0; +} diff --git a/src/Inventory.zig b/src/Inventory.zig index 8b93cfc4..ec5c26d2 100644 --- a/src/Inventory.zig +++ b/src/Inventory.zig @@ -1658,16 +1658,23 @@ pub const Command = struct { // MARK: Command }) { if(side == .server) { // Inform the client of the actual block: - const actualBlock = main.server.world.?.getBlock(self.pos[0], self.pos[1], self.pos[2]) orelse return; - main.network.Protocols.blockUpdate.send(user.?.conn, &.{.init(self.pos, actualBlock)}); + var writer = main.utils.BinaryWriter.init(main.stackAllocator); + defer writer.deinit(); + + const actualBlock = main.server.world.?.getBlockAndBlockEntityData(self.pos[0], self.pos[1], self.pos[2], &writer) orelse return; + main.network.Protocols.blockUpdate.send(user.?.conn, &.{.init(self.pos, actualBlock, writer.data.items)}); } return; } if(side == .server) { - if(main.server.world.?.cmpxchgBlock(self.pos[0], self.pos[1], self.pos[2], self.oldBlock, self.newBlock)) |actualBlock| { + if(main.server.world.?.cmpxchgBlock(self.pos[0], self.pos[1], self.pos[2], self.oldBlock, self.newBlock) != null) { // Inform the client of the actual block: - main.network.Protocols.blockUpdate.send(user.?.conn, &.{.init(self.pos, actualBlock)}); + var writer = main.utils.BinaryWriter.init(main.stackAllocator); + defer writer.deinit(); + + const actualBlock = main.server.world.?.getBlockAndBlockEntityData(self.pos[0], self.pos[1], self.pos[2], &writer) orelse return; + main.network.Protocols.blockUpdate.send(user.?.conn, &.{.init(self.pos, actualBlock, writer.data.items)}); return error.serverFailure; } } diff --git a/src/block_entity.zig b/src/block_entity.zig index bc9b41c7..a2f4aa42 100644 --- a/src/block_entity.zig +++ b/src/block_entity.zig @@ -1,31 +1,46 @@ const std = @import("std"); const main = @import("main.zig"); -const Vec3i = main.vec.Vec3i; const Block = main.blocks.Block; const Chunk = main.chunk.Chunk; const ChunkPosition = main.chunk.ChunkPosition; const getIndex = main.chunk.getIndex; +const graphics = main.graphics; +const c = graphics.c; const server = main.server; const User = server.User; const mesh_storage = main.renderer.mesh_storage; +const BinaryReader = main.utils.BinaryReader; +const BinaryWriter = main.utils.BinaryWriter; +const vec = main.vec; +const Mat4f = vec.Mat4f; +const Vec3d = vec.Vec3d; +const Vec3f = vec.Vec3f; +const Vec3i = vec.Vec3i; pub const BlockEntityIndex = main.utils.DenseId(u32); +const UpdateEvent = union(enum) { + remove: void, + createOrUpdate: *BinaryReader, +}; + pub const BlockEntityType = struct { id: []const u8, vtable: VTable, const VTable = struct { - onLoadClient: *const fn(pos: Vec3i, chunk: *Chunk) void, + onLoadClient: *const fn(pos: Vec3i, chunk: *Chunk, reader: *BinaryReader) BinaryReader.AllErrors!void, onUnloadClient: *const fn(dataIndex: BlockEntityIndex) void, - onLoadServer: *const fn(pos: Vec3i, chunk: *Chunk) void, + onLoadServer: *const fn(pos: Vec3i, chunk: *Chunk, reader: *BinaryReader) BinaryReader.AllErrors!void, onUnloadServer: *const fn(dataIndex: BlockEntityIndex) void, - onPlaceClient: *const fn(pos: Vec3i, chunk: *Chunk) void, - onBreakClient: *const fn(pos: Vec3i, chunk: *Chunk) void, - onPlaceServer: *const fn(pos: Vec3i, chunk: *Chunk) void, - onBreakServer: *const fn(pos: Vec3i, chunk: *Chunk) void, + onStoreServerToDisk: *const fn(dataIndex: BlockEntityIndex, writer: *BinaryWriter) void, + onStoreServerToClient: *const fn(dataIndex: BlockEntityIndex, writer: *BinaryWriter) void, onInteract: *const fn(pos: Vec3i, chunk: *Chunk) EventStatus, + updateClientData: *const fn(pos: Vec3i, chunk: *Chunk, event: UpdateEvent) BinaryReader.AllErrors!void, + updateServerData: *const fn(pos: Vec3i, chunk: *Chunk, event: UpdateEvent) BinaryReader.AllErrors!void, + getServerToClientData: *const fn(pos: Vec3i, chunk: *Chunk, writer: *BinaryWriter) void, + getClientToServerData: *const fn(pos: Vec3i, chunk: *Chunk, writer: *BinaryWriter) void, }; pub fn init(comptime BlockEntityTypeT: type) BlockEntityType { BlockEntityTypeT.init(); @@ -42,33 +57,39 @@ pub const BlockEntityType = struct { } return class; } - pub inline fn onLoadClient(self: *BlockEntityType, pos: Vec3i, chunk: *Chunk) void { - return self.vtable.onLoadClient(pos, chunk); + pub inline fn onLoadClient(self: *BlockEntityType, pos: Vec3i, chunk: *Chunk, reader: *BinaryReader) BinaryReader.AllErrors!void { + return self.vtable.onLoadClient(pos, chunk, reader); } pub inline fn onUnloadClient(self: *BlockEntityType, dataIndex: BlockEntityIndex) void { return self.vtable.onUnloadClient(dataIndex); } - pub inline fn onLoadServer(self: *BlockEntityType, pos: Vec3i, chunk: *Chunk) void { - return self.vtable.onLoadServer(pos, chunk); + pub inline fn onLoadServer(self: *BlockEntityType, pos: Vec3i, chunk: *Chunk, reader: *BinaryReader) BinaryReader.AllErrors!void { + return self.vtable.onLoadServer(pos, chunk, reader); } pub inline fn onUnloadServer(self: *BlockEntityType, dataIndex: BlockEntityIndex) void { return self.vtable.onUnloadServer(dataIndex); } - pub inline fn onPlaceClient(self: *BlockEntityType, pos: Vec3i, chunk: *Chunk) void { - return self.vtable.onPlaceClient(pos, chunk); + pub inline fn onStoreServerToDisk(self: *BlockEntityType, dataIndex: BlockEntityIndex, writer: *BinaryWriter) void { + return self.vtable.onStoreServerToDisk(dataIndex, writer); } - pub inline fn onBreakClient(self: *BlockEntityType, pos: Vec3i, chunk: *Chunk) void { - return self.vtable.onBreakClient(pos, chunk); - } - pub inline fn onPlaceServer(self: *BlockEntityType, pos: Vec3i, chunk: *Chunk) void { - return self.vtable.onPlaceServer(pos, chunk); - } - pub inline fn onBreakServer(self: *BlockEntityType, pos: Vec3i, chunk: *Chunk) void { - return self.vtable.onBreakServer(pos, chunk); + pub inline fn onStoreServerToClient(self: *BlockEntityType, dataIndex: BlockEntityIndex, writer: *BinaryWriter) void { + return self.vtable.onStoreServerToClient(dataIndex, writer); } pub inline fn onInteract(self: *BlockEntityType, pos: Vec3i, chunk: *Chunk) EventStatus { return self.vtable.onInteract(pos, chunk); } + pub inline fn updateClientData(self: *BlockEntityType, pos: Vec3i, chunk: *Chunk, event: UpdateEvent) BinaryReader.AllErrors!void { + return try self.vtable.updateClientData(pos, chunk, event); + } + pub inline fn updateServerData(self: *BlockEntityType, pos: Vec3i, chunk: *Chunk, event: UpdateEvent) BinaryReader.AllErrors!void { + return try self.vtable.updateServerData(pos, chunk, event); + } + pub inline fn getServerToClientData(self: *BlockEntityType, pos: Vec3i, chunk: *Chunk, writer: *BinaryWriter) void { + return self.vtable.getServerToClientData(pos, chunk, writer); + } + pub inline fn getClientToServerData(self: *BlockEntityType, pos: Vec3i, chunk: *Chunk, writer: *BinaryWriter) void { + return self.vtable.getClientToServerData(pos, chunk, writer); + } }; pub const EventStatus = enum { @@ -79,13 +100,9 @@ pub const EventStatus = enum { fn BlockEntityDataStorage(T: type) type { return struct { pub const DataT = T; - pub const EntryT = struct { - absoluteBlockPosition: Vec3i, - data: DataT, - }; var freeIndexList: main.ListUnmanaged(BlockEntityIndex) = .{}; var nextIndex: BlockEntityIndex = @enumFromInt(0); - var storage: main.utils.SparseSet(EntryT, BlockEntityIndex) = .{}; + var storage: main.utils.SparseSet(DataT, BlockEntityIndex) = .{}; pub var mutex: std.Thread.Mutex = .{}; pub fn init() void { @@ -101,30 +118,32 @@ fn BlockEntityDataStorage(T: type) type { storage.clear(); freeIndexList.clearRetainingCapacity(); } - pub fn add(pos: Vec3i, value: DataT, chunk: *Chunk) void { - mutex.lock(); - defer mutex.unlock(); - + fn createEntry(pos: Vec3i, chunk: *Chunk) BlockEntityIndex { + main.utils.assertLocked(&mutex); const dataIndex: BlockEntityIndex = freeIndexList.popOrNull() orelse blk: { defer nextIndex = @enumFromInt(@intFromEnum(nextIndex) + 1); break :blk nextIndex; }; - storage.set(main.globalAllocator, dataIndex, value); - const blockIndex = chunk.getLocalBlockIndex(pos); chunk.blockPosToEntityDataMapMutex.lock(); - chunk.blockPosToEntityDataMap.put(main.globalAllocator.allocator, blockIndex, @intCast(dataIndex)) catch unreachable; + chunk.blockPosToEntityDataMap.put(main.globalAllocator.allocator, blockIndex, dataIndex) catch unreachable; chunk.blockPosToEntityDataMapMutex.unlock(); + return dataIndex; } - pub fn removeAtIndex(dataIndex: BlockEntityIndex) void { + pub fn add(pos: Vec3i, value: DataT, chunk: *Chunk) void { + mutex.lock(); + defer mutex.unlock(); + + const dataIndex = createEntry(pos, chunk); + storage.set(main.globalAllocator, dataIndex, value); + } + pub fn removeAtIndex(dataIndex: BlockEntityIndex) ?DataT { main.utils.assertLocked(&mutex); freeIndexList.append(main.globalAllocator, dataIndex); - storage.remove(dataIndex) catch |err| { - std.log.err("Error while removing block entity: {s}", .{@errorName(err)}); - }; + return storage.fetchRemove(dataIndex) catch null; } - pub fn remove(pos: Vec3i, chunk: *Chunk) void { + pub fn remove(pos: Vec3i, chunk: *Chunk) ?DataT { mutex.lock(); defer mutex.unlock(); @@ -134,13 +153,15 @@ fn BlockEntityDataStorage(T: type) type { const entityNullable = chunk.blockPosToEntityDataMap.fetchRemove(blockIndex); chunk.blockPosToEntityDataMapMutex.unlock(); - const entry = entityNullable orelse { - std.log.err("Couldn't remove entity data of block at position {}", .{pos}); - return; - }; + const entry = entityNullable orelse return null; const dataIndex = entry.value; - removeAtIndex(dataIndex); + return removeAtIndex(dataIndex); + } + pub fn getByIndex(dataIndex: BlockEntityIndex) ?*DataT { + main.utils.assertLocked(&mutex); + + return storage.get(dataIndex); } pub fn get(pos: Vec3i, chunk: *Chunk) ?*DataT { main.utils.assertLocked(&mutex); @@ -154,7 +175,18 @@ fn BlockEntityDataStorage(T: type) type { std.log.warn("Couldn't get entity data of block at position {}", .{pos}); return null; }; - return &storage.items[dataIndex].data; + return storage.get(dataIndex); + } + pub const GetOrPutResult = struct { + valuePtr: *DataT, + foundExisting: bool, + }; + pub fn getOrPut(pos: Vec3i, chunk: *Chunk) GetOrPutResult { + main.utils.assertLocked(&mutex); + if(get(pos, chunk)) |result| return .{.valuePtr = result, .foundExisting = true}; + + const dataIndex = createEntry(pos, chunk); + return .{.valuePtr = storage.add(main.globalAllocator, dataIndex), .foundExisting = false}; } }; } @@ -178,20 +210,16 @@ pub const BlockEntityTypes = struct { StorageServer.reset(); } - pub fn onLoadClient(_: Vec3i, _: *Chunk) void {} + pub fn onLoadClient(_: Vec3i, _: *Chunk, _: *BinaryReader) BinaryReader.AllErrors!void {} pub fn onUnloadClient(_: BlockEntityIndex) void {} - pub fn onLoadServer(_: Vec3i, _: *Chunk) void {} + pub fn onLoadServer(_: Vec3i, _: *Chunk, _: *BinaryReader) BinaryReader.AllErrors!void {} pub fn onUnloadServer(dataIndex: BlockEntityIndex) void { StorageServer.mutex.lock(); defer StorageServer.mutex.unlock(); - StorageServer.removeAtIndex(dataIndex); - } - pub fn onPlaceClient(_: Vec3i, _: *Chunk) void {} - pub fn onBreakClient(_: Vec3i, _: *Chunk) void {} - pub fn onPlaceServer(_: Vec3i, _: *Chunk) void {} - pub fn onBreakServer(pos: Vec3i, chunk: *Chunk) void { - StorageServer.remove(pos, chunk); + _ = StorageServer.removeAtIndex(dataIndex) orelse unreachable; } + pub fn onStoreServerToDisk(_: BlockEntityIndex, _: *BinaryWriter) void {} + pub fn onStoreServerToClient(_: BlockEntityIndex, _: *BinaryWriter) void {} pub fn onInteract(pos: Vec3i, _: *Chunk) EventStatus { if(main.KeyBoard.key("shift").pressed) return .ignored; @@ -203,6 +231,267 @@ pub const BlockEntityTypes = struct { return .handled; } + + pub fn updateClientData(_: Vec3i, _: *Chunk, _: UpdateEvent) BinaryReader.AllErrors!void {} + pub fn updateServerData(_: Vec3i, _: *Chunk, _: UpdateEvent) BinaryReader.AllErrors!void {} + pub fn getServerToClientData(_: Vec3i, _: *Chunk, _: *BinaryWriter) void {} + pub fn getClientToServerData(_: Vec3i, _: *Chunk, _: *BinaryWriter) void {} + + pub fn renderAll(_: Mat4f, _: Vec3f, _: Vec3d) void {} + }; + + pub const Sign = struct { + const StorageServer = BlockEntityDataStorage(struct { + text: []const u8, + }); + const StorageClient = BlockEntityDataStorage(struct { + text: []const u8, + renderedTexture: ?main.graphics.Texture = null, + blockPos: Vec3i, + block: main.blocks.Block, + + fn deinit(self: @This()) void { + main.globalAllocator.free(self.text); + if(self.renderedTexture) |texture| { + textureDeinitLock.lock(); + defer textureDeinitLock.unlock(); + textureDeinitList.append(texture); + } + } + }); + var textureDeinitList: main.List(graphics.Texture) = undefined; + var textureDeinitLock: std.Thread.Mutex = .{}; + var pipeline: graphics.Pipeline = undefined; + var uniforms: struct { + ambientLight: c_int, + projectionMatrix: c_int, + viewMatrix: c_int, + playerPositionInteger: c_int, + playerPositionFraction: c_int, + quadIndex: c_int, + lightData: c_int, + chunkPos: c_int, + blockPos: c_int, + } = undefined; + + // TODO: Load these from some per-block settings + const textureWidth = 128; + const textureHeight = 72; + const textureMargin = 4; + + pub const id = "sign"; + pub fn init() void { + StorageServer.init(); + StorageClient.init(); + textureDeinitList = .init(main.globalAllocator); + + pipeline = graphics.Pipeline.init( + "assets/cubyz/shaders/block_entity/sign.vert", + "assets/cubyz/shaders/block_entity/sign.frag", + "", + &uniforms, + .{}, + .{.depthTest = true, .depthCompare = .equal, .depthWrite = false}, + .{.attachments = &.{.alphaBlending}}, + ); + } + pub fn deinit() void { + while(textureDeinitList.popOrNull()) |texture| { + texture.deinit(); + } + textureDeinitList.deinit(); + pipeline.deinit(); + StorageServer.deinit(); + StorageClient.deinit(); + } + pub fn reset() void { + StorageServer.reset(); + StorageClient.reset(); + } + + pub fn onUnloadClient(dataIndex: BlockEntityIndex) void { + StorageClient.mutex.lock(); + defer StorageClient.mutex.unlock(); + const entry = StorageClient.removeAtIndex(dataIndex) orelse unreachable; + entry.deinit(); + } + pub fn onUnloadServer(dataIndex: BlockEntityIndex) void { + StorageServer.mutex.lock(); + defer StorageServer.mutex.unlock(); + const entry = StorageServer.removeAtIndex(dataIndex) orelse unreachable; + main.globalAllocator.free(entry.text); + } + pub fn onInteract(pos: Vec3i, chunk: *Chunk) EventStatus { + if(main.KeyBoard.key("shift").pressed) return .ignored; + + StorageClient.mutex.lock(); + defer StorageClient.mutex.unlock(); + const data = StorageClient.get(pos, chunk); + main.gui.windowlist.sign_editor.openFromSignData(pos, if(data) |_data| _data.text else ""); + + return .handled; + } + + pub fn onLoadClient(pos: Vec3i, chunk: *Chunk, reader: *BinaryReader) BinaryReader.AllErrors!void { + return updateClientData(pos, chunk, .{.createOrUpdate = reader}); + } + pub fn updateClientData(pos: Vec3i, chunk: *Chunk, event: UpdateEvent) BinaryReader.AllErrors!void { + if(event == .remove or event.createOrUpdate.remaining.len == 0) { + const entry = StorageClient.remove(pos, chunk) orelse return; + entry.deinit(); + return; + } + + StorageClient.mutex.lock(); + defer StorageClient.mutex.unlock(); + + const data = StorageClient.getOrPut(pos, chunk); + if(data.foundExisting) { + data.valuePtr.deinit(); + } + data.valuePtr.* = .{ + .blockPos = pos, + .block = chunk.data.getValue(chunk.getLocalBlockIndex(pos)), + .renderedTexture = null, + .text = main.globalAllocator.dupe(u8, event.createOrUpdate.remaining), + }; + } + + pub fn onLoadServer(pos: Vec3i, chunk: *Chunk, reader: *BinaryReader) BinaryReader.AllErrors!void { + return updateServerData(pos, chunk, .{.createOrUpdate = reader}); + } + pub fn updateServerData(pos: Vec3i, chunk: *Chunk, event: UpdateEvent) BinaryReader.AllErrors!void { + if(event == .remove or event.createOrUpdate.remaining.len == 0) { + const entry = StorageServer.remove(pos, chunk) orelse return; + main.globalAllocator.free(entry.text); + return; + } + + StorageServer.mutex.lock(); + defer StorageServer.mutex.unlock(); + + const data = StorageServer.getOrPut(pos, chunk); + if(data.foundExisting) main.globalAllocator.free(data.valuePtr.text); + data.valuePtr.text = main.globalAllocator.dupe(u8, event.createOrUpdate.remaining); + } + + pub const onStoreServerToClient = onStoreServerToDisk; + pub fn onStoreServerToDisk(dataIndex: BlockEntityIndex, writer: *BinaryWriter) void { + StorageServer.mutex.lock(); + defer StorageServer.mutex.unlock(); + + const data = StorageServer.getByIndex(dataIndex) orelse return; + writer.writeSlice(data.text); + } + pub fn getServerToClientData(pos: Vec3i, chunk: *Chunk, writer: *BinaryWriter) void { + StorageServer.mutex.lock(); + defer StorageServer.mutex.unlock(); + + const data = StorageServer.get(pos, chunk) orelse return; + writer.writeSlice(data.text); + } + + pub fn getClientToServerData(pos: Vec3i, chunk: *Chunk, writer: *BinaryWriter) void { + StorageClient.mutex.lock(); + defer StorageClient.mutex.unlock(); + + const data = StorageClient.get(pos, chunk) orelse return; + writer.writeSlice(data.text); + } + + pub fn updateTextFromClient(pos: Vec3i, newText: []const u8) void { + { + const mesh = main.renderer.mesh_storage.getMeshAndIncreaseRefCount(.initFromWorldPos(pos, 1)) orelse return; + defer mesh.decreaseRefCount(); + mesh.mutex.lock(); + defer mesh.mutex.unlock(); + const index = mesh.chunk.getLocalBlockIndex(pos); + const block = mesh.chunk.data.getValue(index); + const blockEntity = block.blockEntity() orelse return; + if(!std.mem.eql(u8, blockEntity.id, id)) return; + + StorageClient.mutex.lock(); + defer StorageClient.mutex.unlock(); + + const data = StorageClient.getOrPut(pos, mesh.chunk); + if(data.foundExisting) { + data.valuePtr.deinit(); + } + data.valuePtr.* = .{ + .blockPos = pos, + .block = mesh.chunk.data.getValue(mesh.chunk.getLocalBlockIndex(pos)), + .renderedTexture = null, + .text = main.globalAllocator.dupe(u8, newText), + }; + } + + main.network.Protocols.blockEntityUpdate.sendClientDataUpdateToServer(main.game.world.?.conn, pos); + } + + pub fn renderAll(projectionMatrix: Mat4f, ambientLight: Vec3f, playerPos: Vec3d) void { + var oldFramebufferBinding: c_int = undefined; + c.glGetIntegerv(c.GL_DRAW_FRAMEBUFFER_BINDING, &oldFramebufferBinding); + + StorageClient.mutex.lock(); + defer StorageClient.mutex.unlock(); + + for(StorageClient.storage.dense.items) |*signData| { + if(signData.renderedTexture != null) continue; + + c.glViewport(0, 0, textureWidth, textureHeight); + defer c.glViewport(0, 0, main.Window.width, main.Window.height); + + var finalFrameBuffer: graphics.FrameBuffer = undefined; + finalFrameBuffer.init(false, c.GL_NEAREST, c.GL_REPEAT); + finalFrameBuffer.updateSize(textureWidth, textureHeight, c.GL_RGBA8); + finalFrameBuffer.bind(); + finalFrameBuffer.clear(.{0, 0, 0, 0}); + signData.renderedTexture = .{.textureID = finalFrameBuffer.texture}; + defer c.glDeleteFramebuffers(1, &finalFrameBuffer.frameBuffer); + + const oldTranslation = graphics.draw.setTranslation(.{textureMargin, textureMargin}); + defer graphics.draw.restoreTranslation(oldTranslation); + const oldClip = graphics.draw.setClip(.{textureWidth - 2*textureMargin, textureHeight - 2*textureMargin}); + defer graphics.draw.restoreClip(oldClip); + + var textBuffer = graphics.TextBuffer.init(main.stackAllocator, signData.text, .{.color = 0x000000}, false, .center); // TODO: Make the color configurable in the zon + defer textBuffer.deinit(); + _ = textBuffer.calculateLineBreaks(16, textureWidth - 2*textureMargin); + textBuffer.renderTextWithoutShadow(0, 0, 16); + } + + c.glBindFramebuffer(c.GL_FRAMEBUFFER, @bitCast(oldFramebufferBinding)); + + pipeline.bind(null); + c.glBindVertexArray(main.renderer.chunk_meshing.vao); + + c.glUniform3f(uniforms.ambientLight, ambientLight[0], ambientLight[1], ambientLight[2]); + c.glUniformMatrix4fv(uniforms.projectionMatrix, 1, c.GL_TRUE, @ptrCast(&projectionMatrix)); + c.glUniformMatrix4fv(uniforms.viewMatrix, 1, c.GL_TRUE, @ptrCast(&main.game.camera.viewMatrix)); + c.glUniform3i(uniforms.playerPositionInteger, @intFromFloat(@floor(playerPos[0])), @intFromFloat(@floor(playerPos[1])), @intFromFloat(@floor(playerPos[2]))); + c.glUniform3f(uniforms.playerPositionFraction, @floatCast(@mod(playerPos[0], 1)), @floatCast(@mod(playerPos[1], 1)), @floatCast(@mod(playerPos[2], 1))); + + outer: for(StorageClient.storage.dense.items) |signData| { + if(main.blocks.meshes.model(signData.block).model().internalQuads.len == 0) continue; + const quad = main.blocks.meshes.model(signData.block).model().internalQuads[0]; + + signData.renderedTexture.?.bindTo(0); + + c.glUniform1i(uniforms.quadIndex, quad.index); + const mesh = main.renderer.mesh_storage.getMeshAndIncreaseRefCount(main.chunk.ChunkPosition.initFromWorldPos(signData.blockPos, 1)) orelse continue :outer; + defer mesh.decreaseRefCount(); + mesh.lightingData[0].lock.lockRead(); + defer mesh.lightingData[0].lock.unlockRead(); + mesh.lightingData[1].lock.lockRead(); + defer mesh.lightingData[1].lock.unlockRead(); + const light: [4]u32 = main.renderer.chunk_meshing.PrimitiveMesh.getLight(mesh, signData.blockPos -% Vec3i{mesh.pos.wx, mesh.pos.wy, mesh.pos.wz}, 0, quad); + c.glUniform4ui(uniforms.lightData, light[0], light[1], light[2], light[3]); + c.glUniform3i(uniforms.chunkPos, signData.blockPos[0] & ~main.chunk.chunkMask, signData.blockPos[1] & ~main.chunk.chunkMask, signData.blockPos[2] & ~main.chunk.chunkMask); + c.glUniform3i(uniforms.blockPos, signData.blockPos[0] & main.chunk.chunkMask, signData.blockPos[1] & main.chunk.chunkMask, signData.blockPos[2] & main.chunk.chunkMask); + + c.glDrawElements(c.GL_TRIANGLES, 6, c.GL_UNSIGNED_INT, null); + } + } }; }; @@ -235,3 +524,9 @@ pub fn getByID(_id: ?[]const u8) ?*BlockEntityType { std.log.err("BlockEntityType with id '{s}' not found", .{id}); return null; } + +pub fn renderAll(projectionMatrix: Mat4f, ambientLight: Vec3f, playerPos: Vec3d) void { + inline for(@typeInfo(BlockEntityTypes).@"struct".decls) |declaration| { + @field(BlockEntityTypes, declaration.name).renderAll(projectionMatrix, ambientLight, playerPos); + } +} diff --git a/src/chunk.zig b/src/chunk.zig index 2665e7b9..9f54a754 100644 --- a/src/chunk.zig +++ b/src/chunk.zig @@ -277,11 +277,14 @@ pub const Chunk = struct { // MARK: Chunk } pub fn deinit(self: *Chunk) void { - // TODO: We should either unload this data here or make sure it was unloaded before. + self.deinitContent(); + memoryPool.destroy(@alignCast(self)); + } + + fn deinitContent(self: *Chunk) void { std.debug.assert(self.blockPosToEntityDataMap.count() == 0); self.blockPosToEntityDataMap.deinit(main.globalAllocator.allocator); self.data.deinit(); - memoryPool.destroy(@alignCast(self)); } pub fn unloadBlockEntities(self: *Chunk, comptime side: main.utils.Side) void { @@ -337,6 +340,14 @@ pub const Chunk = struct { // MARK: Chunk (worldPos[2] - self.pos.wz) >> self.voxelSizeShift, ); } + + pub fn getGlobalBlockPosFromIndex(self: *const Chunk, index: u16) Vec3i { + return .{ + (extractXFromIndex(index) << self.voxelSizeShift) + self.pos.wx, + (extractYFromIndex(index) << self.voxelSizeShift) + self.pos.wy, + (extractZFromIndex(index) << self.voxelSizeShift) + self.pos.wz, + }; + } }; pub const ServerChunk = struct { // MARK: ServerChunk @@ -375,7 +386,8 @@ pub const ServerChunk = struct { // MARK: ServerChunk if(self.wasChanged) { self.save(main.server.world.?); } - self.super.data.deinit(); + self.super.unloadBlockEntities(.server); + self.super.deinitContent(); serverPool.destroy(@alignCast(self)); } @@ -603,7 +615,7 @@ pub const ServerChunk = struct { // MARK: ServerChunk const regionMask: i32 = regionSize - 1; const region = main.server.storage.loadRegionFileAndIncreaseRefCount(pos.wx & ~regionMask, pos.wy & ~regionMask, pos.wz & ~regionMask, pos.voxelSize); defer region.decreaseRefCount(); - const data = main.server.storage.ChunkCompression.compressChunk(main.stackAllocator, &self.super, false); + const data = main.server.storage.ChunkCompression.storeChunk(main.stackAllocator, &self.super, .toDisk, false); defer main.stackAllocator.free(data); region.storeChunk( data, diff --git a/src/graphics.zig b/src/graphics.zig index 4ab8107e..a4fe3c1c 100644 --- a/src/graphics.zig +++ b/src/graphics.zig @@ -80,9 +80,11 @@ pub const draw = struct { // MARK: draw /// Returns the previous clip. pub fn setClip(clipRect: Vec2f) ?Vec4i { std.debug.assert(@reduce(.And, clipRect >= Vec2f{0, 0})); + var viewport: [4]c_int = undefined; + c.glGetIntegerv(c.GL_VIEWPORT, &viewport); var newClip = Vec4i{ std.math.lossyCast(i32, translation[0]), - main.Window.height - std.math.lossyCast(i32, translation[1] + clipRect[1]*scale), + viewport[3] - std.math.lossyCast(i32, translation[1] + clipRect[1]*scale), std.math.lossyCast(i32, clipRect[0]*scale), std.math.lossyCast(i32, clipRect[1]*scale), }; @@ -181,7 +183,9 @@ pub const draw = struct { // MARK: draw rectPipeline.bind(getScissor()); - c.glUniform2f(rectUniforms.screen, @floatFromInt(Window.width), @floatFromInt(Window.height)); + var viewport: [4]c_int = undefined; + c.glGetIntegerv(c.GL_VIEWPORT, &viewport); + c.glUniform2f(rectUniforms.screen, @floatFromInt(viewport[2]), @floatFromInt(viewport[3])); c.glUniform2f(rectUniforms.start, pos[0], pos[1]); c.glUniform2f(rectUniforms.size, dim[0], dim[1]); c.glUniform1i(rectUniforms.rectColor, @bitCast(color)); @@ -252,7 +256,9 @@ pub const draw = struct { // MARK: draw rectBorderPipeline.bind(getScissor()); - c.glUniform2f(rectBorderUniforms.screen, @floatFromInt(Window.width), @floatFromInt(Window.height)); + var viewport: [4]c_int = undefined; + c.glGetIntegerv(c.GL_VIEWPORT, &viewport); + c.glUniform2f(rectBorderUniforms.screen, @floatFromInt(viewport[2]), @floatFromInt(viewport[3])); c.glUniform2f(rectBorderUniforms.start, pos[0], pos[1]); c.glUniform2f(rectBorderUniforms.size, dim[0], dim[1]); c.glUniform1i(rectBorderUniforms.rectColor, @bitCast(color)); @@ -314,7 +320,9 @@ pub const draw = struct { // MARK: draw linePipeline.bind(getScissor()); - c.glUniform2f(lineUniforms.screen, @floatFromInt(Window.width), @floatFromInt(Window.height)); + var viewport: [4]c_int = undefined; + c.glGetIntegerv(c.GL_VIEWPORT, &viewport); + c.glUniform2f(lineUniforms.screen, @floatFromInt(viewport[2]), @floatFromInt(viewport[3])); c.glUniform2f(lineUniforms.start, pos1[0], pos1[1]); c.glUniform2f(lineUniforms.direction, pos2[0] - pos1[0], pos2[1] - pos1[1]); c.glUniform1i(lineUniforms.lineColor, @bitCast(color)); @@ -360,7 +368,9 @@ pub const draw = struct { // MARK: draw linePipeline.bind(getScissor()); - c.glUniform2f(lineUniforms.screen, @floatFromInt(Window.width), @floatFromInt(Window.height)); + var viewport: [4]c_int = undefined; + c.glGetIntegerv(c.GL_VIEWPORT, &viewport); + c.glUniform2f(lineUniforms.screen, @floatFromInt(viewport[2]), @floatFromInt(viewport[3])); c.glUniform2f(lineUniforms.start, pos[0], pos[1]); // Move the coordinates, so they are in the center of a pixel. c.glUniform2f(lineUniforms.direction, dim[0] - 1, dim[1] - 1); // The height is a lot smaller because the inner edge of the rect is drawn. c.glUniform1i(lineUniforms.lineColor, @bitCast(color)); @@ -421,7 +431,9 @@ pub const draw = struct { // MARK: draw radius *= scale; circlePipeline.bind(getScissor()); - c.glUniform2f(circleUniforms.screen, @floatFromInt(Window.width), @floatFromInt(Window.height)); + var viewport: [4]c_int = undefined; + c.glGetIntegerv(c.GL_VIEWPORT, &viewport); + c.glUniform2f(circleUniforms.screen, @floatFromInt(viewport[2]), @floatFromInt(viewport[3])); c.glUniform2f(circleUniforms.center, center[0], center[1]); // Move the coordinates, so they are in the center of a pixel. c.glUniform1f(circleUniforms.radius, radius); // The height is a lot smaller because the inner edge of the rect is drawn. c.glUniform1i(circleUniforms.circleColor, @bitCast(color)); @@ -476,7 +488,9 @@ pub const draw = struct { // MARK: draw imagePipeline.bind(getScissor()); - c.glUniform2f(imageUniforms.screen, @floatFromInt(Window.width), @floatFromInt(Window.height)); + var viewport: [4]c_int = undefined; + c.glGetIntegerv(c.GL_VIEWPORT, &viewport); + c.glUniform2f(imageUniforms.screen, @floatFromInt(viewport[2]), @floatFromInt(viewport[3])); c.glUniform2f(imageUniforms.start, pos[0], pos[1]); c.glUniform2f(imageUniforms.size, dim[0], dim[1]); c.glUniform1i(imageUniforms.color, @bitCast(color)); @@ -496,7 +510,9 @@ pub const draw = struct { // MARK: draw pos = @floor(pos); dim = @ceil(dim); - c.glUniform2f(uniforms.screen, @floatFromInt(Window.width), @floatFromInt(Window.height)); + var viewport: [4]c_int = undefined; + c.glGetIntegerv(c.GL_VIEWPORT, &viewport); + c.glUniform2f(uniforms.screen, @floatFromInt(viewport[2]), @floatFromInt(viewport[3])); c.glUniform2f(uniforms.start, pos[0], pos[1]); c.glUniform2f(uniforms.size, dim[0], dim[1]); c.glUniform1i(uniforms.color, @bitCast(color)); @@ -519,7 +535,9 @@ pub const draw = struct { // MARK: draw pos = @floor(pos); dim = @ceil(dim); - c.glUniform2f(uniforms.screen, @floatFromInt(Window.width), @floatFromInt(Window.height)); + var viewport: [4]c_int = undefined; + c.glGetIntegerv(c.GL_VIEWPORT, &viewport); + c.glUniform2f(uniforms.screen, @floatFromInt(viewport[2]), @floatFromInt(viewport[3])); c.glUniform2f(uniforms.start, pos[0], pos[1]); c.glUniform2f(uniforms.size, dim[0], dim[1]); c.glUniform1i(uniforms.color, @bitCast(color)); @@ -973,6 +991,10 @@ pub const TextBuffer = struct { // MARK: TextBuffer pub fn render(self: TextBuffer, _x: f32, _y: f32, _fontSize: f32) void { self.renderShadow(_x, _y, _fontSize); + self.renderTextWithoutShadow(_x, _y, _fontSize); + } + + pub fn renderTextWithoutShadow(self: TextBuffer, _x: f32, _y: f32, _fontSize: f32) void { const oldTranslation = draw.setTranslation(.{_x, _y}); defer draw.restoreTranslation(oldTranslation); const oldScale = draw.setScale(_fontSize/16.0); @@ -980,7 +1002,9 @@ pub const TextBuffer = struct { // MARK: TextBuffer var x: f32 = 0; var y: f32 = 0; TextRendering.pipeline.bind(draw.getScissor()); - c.glUniform2f(TextRendering.uniforms.scene, @floatFromInt(main.Window.width), @floatFromInt(main.Window.height)); + var viewport: [4]c_int = undefined; + c.glGetIntegerv(c.GL_VIEWPORT, &viewport); + c.glUniform2f(TextRendering.uniforms.scene, @floatFromInt(viewport[2]), @floatFromInt(viewport[3])); c.glUniform1f(TextRendering.uniforms.ratio, draw.scale); c.glUniform1f(TextRendering.uniforms.alpha, @as(f32, @floatFromInt(draw.color >> 24))/255.0); c.glActiveTexture(c.GL_TEXTURE0); @@ -1046,7 +1070,9 @@ pub const TextBuffer = struct { // MARK: TextBuffer var x: f32 = 0; var y: f32 = 0; TextRendering.pipeline.bind(draw.getScissor()); - c.glUniform2f(TextRendering.uniforms.scene, @floatFromInt(main.Window.width), @floatFromInt(main.Window.height)); + var viewport: [4]c_int = undefined; + c.glGetIntegerv(c.GL_VIEWPORT, &viewport); + c.glUniform2f(TextRendering.uniforms.scene, @floatFromInt(viewport[2]), @floatFromInt(viewport[3])); c.glUniform1f(TextRendering.uniforms.ratio, draw.scale); c.glUniform1f(TextRendering.uniforms.alpha, @as(f32, @floatFromInt(draw.color >> 24))/255.0); c.glActiveTexture(c.GL_TEXTURE0); diff --git a/src/gui/windows/_windowlist.zig b/src/gui/windows/_windowlist.zig index f97835d3..ef551fcd 100644 --- a/src/gui/windows/_windowlist.zig +++ b/src/gui/windows/_windowlist.zig @@ -30,5 +30,6 @@ pub const save_creation = @import("save_creation.zig"); pub const save_selection = @import("save_selection.zig"); pub const settings = @import("settings.zig"); pub const shared_inventory_testing = @import("shared_inventory_testing.zig"); +pub const sign_editor = @import("sign_editor.zig"); pub const sound = @import("sound.zig"); pub const workbench = @import("workbench.zig"); diff --git a/src/gui/windows/gpu_performance_measuring.zig b/src/gui/windows/gpu_performance_measuring.zig index 4aca9f8c..e7902a90 100644 --- a/src/gui/windows/gpu_performance_measuring.zig +++ b/src/gui/windows/gpu_performance_measuring.zig @@ -21,6 +21,7 @@ pub const Samples = enum(u8) { chunk_rendering_occlusion_test, chunk_rendering_new_visible, entity_rendering, + block_entity_rendering, particle_rendering, transparent_rendering_preparation, transparent_rendering_occlusion_test, @@ -42,6 +43,7 @@ const names = [_][]const u8{ "Chunk Rendering Occlusion Test", "Chunk Rendering New Visible", "Entity Rendering", + "Block Entity Rendering", "Particle Rendering", "Transparent Rendering Preparation", "Transparent Rendering Occlusion Test", diff --git a/src/gui/windows/sign_editor.zig b/src/gui/windows/sign_editor.zig new file mode 100644 index 00000000..018cfd6c --- /dev/null +++ b/src/gui/windows/sign_editor.zig @@ -0,0 +1,68 @@ +const std = @import("std"); + +const main = @import("main"); +const settings = main.settings; +const Vec2f = main.vec.Vec2f; + +const gui = @import("../gui.zig"); +const GuiComponent = gui.GuiComponent; +const GuiWindow = gui.GuiWindow; +const Button = @import("../components/Button.zig"); +const Label = @import("../components/Label.zig"); +const TextInput = @import("../components/TextInput.zig"); +const VerticalList = @import("../components/VerticalList.zig"); + +pub var window = GuiWindow{ + .contentSize = Vec2f{128, 256}, + .closeIfMouseIsGrabbed = true, +}; +var textComponent: *TextInput = undefined; + +const padding: f32 = 8; + +var pos: main.vec.Vec3i = undefined; +var oldText: []const u8 = &.{}; + +pub fn deinit() void { + main.globalAllocator.free(oldText); + oldText = &.{}; +} + +pub fn openFromSignData(_pos: main.vec.Vec3i, _oldText: []const u8) void { + pos = _pos; + main.globalAllocator.free(oldText); + oldText = main.globalAllocator.dupe(u8, _oldText); + gui.closeWindowFromRef(&window); + gui.openWindowFromRef(&window); + main.Window.setMouseGrabbed(false); +} + +fn apply(_: usize) void { + const visibleCharacterCount = main.graphics.TextBuffer.Parser.countVisibleCharacters(textComponent.currentString.items); + if(textComponent.currentString.items.len > 500 or visibleCharacterCount > 100) { + std.log.err("Text is too long with {}/{} characters. Limits are 100/500", .{visibleCharacterCount, textComponent.currentString.items.len}); + return; + } + + main.block_entity.BlockEntityTypes.Sign.updateTextFromClient(pos, textComponent.currentString.items); + + gui.closeWindowFromRef(&window); +} + +pub fn onOpen() void { + const list = VerticalList.init(.{padding, 16 + padding}, 300, 16); + const width = 128 + padding; + textComponent = TextInput.init(.{0, 0}, width, 16*4 + 8, oldText, .{.callback = &apply}, .{}); + list.add(textComponent); + list.add(Button.initText(.{0, 0}, 100, "Apply", .{.callback = &apply})); + list.finish(.center); + window.rootComponent = list.toComponent(); + window.contentSize = window.rootComponent.?.pos() + window.rootComponent.?.size() + @as(Vec2f, @splat(padding)); + gui.updateWindowPositions(); +} + +pub fn onClose() void { + if(window.rootComponent) |*comp| { + comp.deinit(); + } +} diff --git a/src/network.zig b/src/network.zig index 8a52df0c..1e9be15d 100644 --- a/src/network.zig +++ b/src/network.zig @@ -791,12 +791,12 @@ pub const Protocols = struct { .voxelSize = try reader.readInt(u31), }; const ch = chunk.Chunk.init(pos); - try main.server.storage.ChunkCompression.decompressChunk(ch, reader.remaining); + try main.server.storage.ChunkCompression.loadChunk(ch, .client, reader.remaining); renderer.mesh_storage.updateChunkMesh(ch); } fn sendChunkOverTheNetwork(conn: *Connection, ch: *chunk.ServerChunk) void { ch.mutex.lock(); - const chunkData = main.server.storage.ChunkCompression.compressChunk(main.stackAllocator, &ch.super, ch.super.pos.voxelSize != 1); + const chunkData = main.server.storage.ChunkCompression.storeChunk(main.stackAllocator, &ch.super, .toClient, ch.super.pos.voxelSize != 1); ch.mutex.unlock(); defer main.stackAllocator.free(chunkData); var writer = utils.BinaryWriter.initCapacity(main.stackAllocator, chunkData.len + 16); @@ -808,18 +808,8 @@ pub const Protocols = struct { writer.writeSlice(chunkData); conn.send(.fast, id, writer.data.items); // TODO: Can this use the slow channel? } - fn sendChunkLocally(ch: *chunk.ServerChunk) void { - const chunkCopy = chunk.Chunk.init(ch.super.pos); - chunkCopy.data.deinit(); - chunkCopy.data.initCopy(&ch.super.data); - renderer.mesh_storage.updateChunkMesh(chunkCopy); - } pub fn sendChunk(conn: *Connection, ch: *chunk.ServerChunk) void { - if(conn.user.?.isLocal) { - sendChunkLocally(ch); - } else { - sendChunkOverTheNetwork(conn, ch); - } + sendChunkOverTheNetwork(conn, ch); } }; pub const playerPosition = struct { @@ -956,6 +946,7 @@ pub const Protocols = struct { .y = try reader.readInt(i32), .z = try reader.readInt(i32), .newBlock = Block.fromInt(try reader.readInt(u32)), + .blockEntityData = try reader.readSlice(try reader.readInt(usize)), }); } } @@ -968,6 +959,8 @@ pub const Protocols = struct { writer.writeInt(i32, update.y); writer.writeInt(i32, update.z); writer.writeInt(u32, update.newBlock.toInt()); + writer.writeInt(usize, update.blockEntityData.len); + writer.writeSlice(update.blockEntityData); } conn.send(.fast, id, writer.data.items); } @@ -1290,6 +1283,71 @@ pub const Protocols = struct { conn.send(.fast, id, writer.data.items); } }; + pub const blockEntityUpdate = struct { + pub const id: u8 = 14; + pub const asynchronous = false; + fn receive(conn: *Connection, reader: *utils.BinaryReader) !void { + if(!conn.isServerSide()) return error.Invalid; + + const pos = try reader.readVec(Vec3i); + const blockType = try reader.readInt(u16); + const simChunk = main.server.world.?.getSimulationChunkAndIncreaseRefCount(pos[0], pos[1], pos[2]) orelse return; + defer simChunk.decreaseRefCount(); + const ch = simChunk.chunk.load(.unordered) orelse return; + ch.mutex.lock(); + defer ch.mutex.unlock(); + const block = ch.getBlock(pos[0] - ch.super.pos.wx, pos[1] - ch.super.pos.wy, pos[2] - ch.super.pos.wz); + if(block.typ != blockType) return; + const blockEntity = block.blockEntity() orelse return; + try blockEntity.updateServerData(pos, &ch.super, .{.createOrUpdate = reader}); + ch.setChanged(); + + sendServerDataUpdateToClientsInternal(pos, &ch.super, block, blockEntity); + } + + pub fn sendClientDataUpdateToServer(conn: *Connection, pos: Vec3i) void { + const mesh = main.renderer.mesh_storage.getMeshAndIncreaseRefCount(.initFromWorldPos(pos, 1)) orelse return; + defer mesh.decreaseRefCount(); + mesh.mutex.lock(); + defer mesh.mutex.unlock(); + const index = mesh.chunk.getLocalBlockIndex(pos); + const block = mesh.chunk.data.getValue(index); + const blockEntity = block.blockEntity() orelse return; + + var writer = utils.BinaryWriter.init(main.stackAllocator); + defer writer.deinit(); + writer.writeVec(Vec3i, pos); + writer.writeInt(u16, block.typ); + blockEntity.getClientToServerData(pos, mesh.chunk, &writer); + + conn.send(.fast, id, writer.data.items); + } + + fn sendServerDataUpdateToClientsInternal(pos: Vec3i, ch: *chunk.Chunk, block: Block, blockEntity: *main.block_entity.BlockEntityType) void { + var writer = utils.BinaryWriter.init(main.stackAllocator); + defer writer.deinit(); + blockEntity.getServerToClientData(pos, ch, &writer); + + const users = main.server.getUserListAndIncreaseRefCount(main.stackAllocator); + defer main.server.freeUserListAndDecreaseRefCount(main.stackAllocator, users); + + for(users) |user| { + blockUpdate.send(user.conn, &.{.{.x = pos[0], .y = pos[1], .z = pos[2], .newBlock = block, .blockEntityData = writer.data.items}}); + } + } + + pub fn sendServerDataUpdateToClients(pos: Vec3i) void { + const simChunk = main.server.world.?.getSimulationChunkAndIncreaseRefCount(pos[0], pos[1], pos[2]) orelse return; + defer simChunk.decreaseRefCount(); + const ch = simChunk.chunk.load(.unordered) orelse return; + ch.mutex.lock(); + defer ch.mutex.unlock(); + const block = ch.getBlock(pos[0] - ch.super.pos.wx, pos[1] - ch.super.pos.wy, pos[2] - ch.super.pos.wz); + const blockEntity = block.blockEntity() orelse return; + + sendServerDataUpdateToClientsInternal(pos, &ch.super, block, blockEntity); + } + }; }; pub const Connection = struct { // MARK: Connection diff --git a/src/renderer.zig b/src/renderer.zig index 38e427db..e6d3619d 100644 --- a/src/renderer.zig +++ b/src/renderer.zig @@ -241,6 +241,10 @@ pub fn renderWorld(world: *World, ambientLight: Vec3f, skyColor: Vec3f, playerPo itemdrop.ItemDropRenderer.renderItemDrops(game.projectionMatrix, ambientLight, playerPos); gpu_performance_measuring.stopQuery(); + gpu_performance_measuring.startQuery(.block_entity_rendering); + main.block_entity.renderAll(game.projectionMatrix, ambientLight, playerPos); + gpu_performance_measuring.stopQuery(); + gpu_performance_measuring.startQuery(.particle_rendering); particles.ParticleSystem.render(game.projectionMatrix, game.camera.viewMatrix, ambientLight); gpu_performance_measuring.stopQuery(); @@ -1111,7 +1115,7 @@ pub const MeshSelection = struct { // MARK: MeshSelection .newBlock = newBlock, }, }); - mesh_storage.updateBlock(.{.x = x, .y = y, .z = z, .newBlock = newBlock}); + mesh_storage.updateBlock(.{.x = x, .y = y, .z = z, .newBlock = newBlock, .blockEntityData = &.{}}); } pub fn drawCube(projectionMatrix: Mat4f, viewMatrix: Mat4f, relativePositionToPlayer: Vec3d, min: Vec3f, max: Vec3f) void { diff --git a/src/renderer/chunk_meshing.zig b/src/renderer/chunk_meshing.zig index 935bca7e..cb0e4a50 100644 --- a/src/renderer/chunk_meshing.zig +++ b/src/renderer/chunk_meshing.zig @@ -314,7 +314,7 @@ pub const IndirectData = extern struct { baseInstance: u32, }; -const PrimitiveMesh = struct { // MARK: PrimitiveMesh +pub const PrimitiveMesh = struct { // MARK: PrimitiveMesh const FaceGroups = enum(u32) { core, neighbor0, @@ -485,7 +485,7 @@ const PrimitiveMesh = struct { // MARK: PrimitiveMesh return result; } - fn getLight(parent: *ChunkMesh, blockPos: Vec3i, textureIndex: u16, quadIndex: QuadIndex) [4]u32 { + pub fn getLight(parent: *ChunkMesh, blockPos: Vec3i, textureIndex: u16, quadIndex: QuadIndex) [4]u32 { const quadInfo = quadIndex.quadInfo(); const extraQuadInfo = quadIndex.extraQuadInfo(); const normal = quadInfo.normal; @@ -1201,7 +1201,7 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh } } - pub fn updateBlock(self: *ChunkMesh, _x: i32, _y: i32, _z: i32, _newBlock: Block, lightRefreshList: *main.List(*ChunkMesh), regenerateMeshList: *main.List(*ChunkMesh)) void { + pub fn updateBlock(self: *ChunkMesh, _x: i32, _y: i32, _z: i32, _newBlock: Block, blockEntityData: []const u8, lightRefreshList: *main.List(*ChunkMesh), regenerateMeshList: *main.List(*ChunkMesh)) void { const x: u5 = @intCast(_x & chunk.chunkMask); const y: u5 = @intCast(_y & chunk.chunkMask); const z: u5 = @intCast(_z & chunk.chunkMask); @@ -1210,13 +1210,21 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh const oldBlock = self.chunk.data.getValue(chunk.getIndex(x, y, z)); if(oldBlock == newBlock) { + if(newBlock.blockEntity()) |blockEntity| { + var reader = main.utils.BinaryReader.init(blockEntityData); + blockEntity.updateClientData(.{_x, _y, _z}, self.chunk, .{.createOrUpdate = &reader}) catch |err| { + std.log.err("Got error {s} while trying to apply block entity data {any} in position {} for block {s}", .{@errorName(err), blockEntityData, Vec3i{_x, _y, _z}, newBlock.id()}); + }; + } self.mutex.unlock(); return; } self.mutex.unlock(); if(oldBlock.blockEntity()) |blockEntity| { - blockEntity.onBreakClient(.{_x, _y, _z}, self.chunk); + blockEntity.updateClientData(.{_x, _y, _z}, self.chunk, .remove) catch |err| { + std.log.err("Got error {s} while trying to remove entity data in position {} for block {s}", .{@errorName(err), Vec3i{_x, _y, _z}, oldBlock.id()}); + }; } var neighborBlocks: [6]Block = undefined; @@ -1268,11 +1276,14 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh } self.mutex.lock(); self.chunk.data.setValue(chunk.getIndex(x, y, z), newBlock); - self.mutex.unlock(); if(newBlock.blockEntity()) |blockEntity| { - blockEntity.onPlaceClient(.{_x, _y, _z}, self.chunk); + var reader = main.utils.BinaryReader.init(blockEntityData); + blockEntity.updateClientData(.{_x, _y, _z}, self.chunk, .{.createOrUpdate = &reader}) catch |err| { + std.log.err("Got error {s} while trying to create block entity data {any} in position {} for block {s}", .{@errorName(err), blockEntityData, Vec3i{_x, _y, _z}, newBlock.id()}); + }; } + self.mutex.unlock(); self.updateBlockLight(x, y, z, newBlock, lightRefreshList); diff --git a/src/renderer/mesh_storage.zig b/src/renderer/mesh_storage.zig index 4a0c38b1..5d467f63 100644 --- a/src/renderer/mesh_storage.zig +++ b/src/renderer/mesh_storage.zig @@ -51,9 +51,24 @@ pub const BlockUpdate = struct { y: i32, z: i32, newBlock: blocks.Block, + blockEntityData: []const u8, - pub fn init(pos: Vec3i, block: blocks.Block) BlockUpdate { - return .{.x = pos[0], .y = pos[1], .z = pos[2], .newBlock = block}; + pub fn init(pos: Vec3i, block: blocks.Block, blockEntityData: []const u8) BlockUpdate { + return .{.x = pos[0], .y = pos[1], .z = pos[2], .newBlock = block, .blockEntityData = blockEntityData}; + } + + pub fn initManaged(allocator: main.heap.NeverFailingAllocator, template: BlockUpdate) BlockUpdate { + return .{ + .x = template.x, + .y = template.y, + .z = template.z, + .newBlock = template.newBlock, + .blockEntityData = allocator.dupe(u8, template.blockEntityData), + }; + } + + pub fn deinitManaged(self: BlockUpdate, allocator: main.heap.NeverFailingAllocator) void { + allocator.free(self.blockEntityData); } }; @@ -108,6 +123,9 @@ pub fn deinit() void { mesh.decreaseRefCount(); } priorityMeshUpdateList.deinit(); + while(blockUpdateList.dequeue()) |blockUpdate| { + blockUpdate.deinitManaged(main.globalAllocator); + } blockUpdateList.deinit(); meshList.clearAndFree(); for(clearList.items) |mesh| { @@ -868,9 +886,10 @@ fn batchUpdateBlocks() void { // First of all process all the block updates: while(blockUpdateList.dequeue()) |blockUpdate| { + defer blockUpdate.deinitManaged(main.globalAllocator); const pos = chunk.ChunkPosition{.wx = blockUpdate.x, .wy = blockUpdate.y, .wz = blockUpdate.z, .voxelSize = 1}; if(getMeshAndIncreaseRefCount(pos)) |mesh| { - mesh.updateBlock(blockUpdate.x, blockUpdate.y, blockUpdate.z, blockUpdate.newBlock, &lightRefreshList, ®enerateMeshList); + mesh.updateBlock(blockUpdate.x, blockUpdate.y, blockUpdate.z, blockUpdate.newBlock, blockUpdate.blockEntityData, &lightRefreshList, ®enerateMeshList); mesh.decreaseRefCount(); } // TODO: It seems like we simply ignore the block update if we don't have the mesh yet. } @@ -987,7 +1006,7 @@ pub const MeshGenerationTask = struct { // MARK: MeshGenerationTask // MARK: updaters pub fn updateBlock(update: BlockUpdate) void { - blockUpdateList.enqueue(update); + blockUpdateList.enqueue(BlockUpdate.initManaged(main.globalAllocator, update)); } pub fn updateChunkMesh(mesh: *chunk.Chunk) void { diff --git a/src/rotation/sign.zig b/src/rotation/sign.zig index 45e25dd6..65b5d700 100644 --- a/src/rotation/sign.zig +++ b/src/rotation/sign.zig @@ -16,7 +16,6 @@ const Vec3i = vec.Vec3i; const ZonElement = main.ZonElement; pub const naturalStandard: u16 = 0; -pub const dependsOnNeighbors = true; var rotatedModels: std.StringHashMap(ModelIndex) = undefined; pub fn init() void { @@ -127,11 +126,8 @@ fn getRotationFromDir(dir: Vec3f) u16 { return data; } -pub fn generateData(_: *main.game.World, _: Vec3i, _: Vec3f, playerDir: Vec3f, relativeDir: Vec3i, neighbor: ?Neighbor, currentData: *Block, neighborBlock: Block, blockPlacing: bool) bool { +pub fn generateData(_: *main.game.World, _: Vec3i, _: Vec3f, playerDir: Vec3f, relativeDir: Vec3i, neighbor: ?Neighbor, currentData: *Block, _: Block, blockPlacing: bool) bool { if(neighbor == null) return false; - const neighborModel = blocks.meshes.model(neighborBlock).model(); - const neighborSupport = !neighborBlock.replacable() and neighborModel.neighborFacingQuads[neighbor.?.reverse().toInt()].len != 0; - if(!neighborSupport) return false; if(!blockPlacing) return false; currentData.data = switch(Neighbor.fromRelPos(relativeDir) orelse unreachable) { .dirNegX => 2*centerRotations, @@ -144,10 +140,7 @@ pub fn generateData(_: *main.game.World, _: Vec3i, _: Vec3f, playerDir: Vec3f, r return true; } -pub fn updateData(block: *Block, neighbor: Neighbor, neighborBlock: Block) bool { - const neighborModel = blocks.meshes.model(neighborBlock).model(); - const neighborSupport = !neighborBlock.replacable() and neighborModel.neighborFacingQuads[neighbor.reverse().toInt()].len != 0; - if(neighborSupport) return false; +pub fn updateData(block: *Block, neighbor: Neighbor, _: Block) bool { const shouldBeBroken = switch(neighbor) { .dirNegX => block.data == 2*centerRotations, .dirNegY => block.data == 2*centerRotations + 1, diff --git a/src/server/storage.zig b/src/server/storage.zig index 4eca3de2..93ef6d7a 100644 --- a/src/server/storage.zig +++ b/src/server/storage.zig @@ -252,21 +252,40 @@ pub fn loadRegionFileAndIncreaseRefCount(wx: i32, wy: i32, wz: i32, voxelSize: u } pub const ChunkCompression = struct { // MARK: ChunkCompression - const CompressionAlgo = enum(u32) { - deflate_with_position = 0, - deflate = 1, + const ChunkCompressionAlgo = enum(u32) { + deflate_with_position_no_block_entities = 0, + deflate_no_block_entities = 1, uniform = 2, - deflate_with_8bit_palette = 3, - _, + deflate_with_8bit_palette_no_block_entities = 3, + deflate = 4, + deflate_with_8bit_palette = 5, }; - pub fn compressChunk(allocator: main.heap.NeverFailingAllocator, ch: *chunk.Chunk, allowLossy: bool) []const u8 { + const BlockEntityCompressionAlgo = enum(u8) { + raw = 0, // TODO: Maybe we need some basic compression at some point. For now this is good enough though. + }; + + const Target = enum {toClient, toDisk}; + + pub fn storeChunk(allocator: main.heap.NeverFailingAllocator, ch: *chunk.Chunk, comptime target: Target, allowLossy: bool) []const u8 { + var writer = BinaryWriter.init(allocator); + + compressBlockData(ch, allowLossy, &writer); + compressBlockEntityData(ch, target, &writer); + + return writer.data.toOwnedSlice(); + } + + pub fn loadChunk(ch: *chunk.Chunk, comptime side: main.utils.Side, data: []const u8) !void { + var reader = BinaryReader.init(data); + try decompressBlockData(ch, &reader); + try decompressBlockEntityData(ch, side, &reader); + } + + fn compressBlockData(ch: *chunk.Chunk, allowLossy: bool, writer: *BinaryWriter) void { if(ch.data.paletteLength == 1) { - var writer = BinaryWriter.initCapacity(allocator, @sizeOf(CompressionAlgo) + @sizeOf(u32)); - - writer.writeEnum(CompressionAlgo, .uniform); + writer.writeEnum(ChunkCompressionAlgo, .uniform); writer.writeInt(u32, ch.data.palette[0].toInt()); - - return writer.data.toOwnedSlice(); + return; } if(ch.data.paletteLength < 256) { var uncompressedData: [chunk.chunkVolume]u8 = undefined; @@ -301,16 +320,15 @@ pub const ChunkCompression = struct { // MARK: ChunkCompression const compressedData = main.utils.Compression.deflate(main.stackAllocator, &uncompressedData, .default); defer main.stackAllocator.free(compressedData); - var writer = BinaryWriter.initCapacity(allocator, @sizeOf(CompressionAlgo) + @sizeOf(u8) + @sizeOf(u32)*ch.data.paletteLength + compressedData.len); - - writer.writeEnum(CompressionAlgo, .deflate_with_8bit_palette); + writer.writeEnum(ChunkCompressionAlgo, .deflate_with_8bit_palette); writer.writeInt(u8, @intCast(ch.data.paletteLength)); for(0..ch.data.paletteLength) |i| { writer.writeInt(u32, ch.data.palette[i].toInt()); } + writer.writeVarInt(usize, compressedData.len); writer.writeSlice(compressedData); - return writer.data.toOwnedSlice(); + return; } var uncompressedWriter = BinaryWriter.initCapacity(main.stackAllocator, chunk.chunkVolume*@sizeOf(u32)); defer uncompressedWriter.deinit(); @@ -321,27 +339,25 @@ pub const ChunkCompression = struct { // MARK: ChunkCompression const compressedData = main.utils.Compression.deflate(main.stackAllocator, uncompressedWriter.data.items, .default); defer main.stackAllocator.free(compressedData); - var compressedWriter = BinaryWriter.initCapacity(allocator, @sizeOf(CompressionAlgo) + compressedData.len); - - compressedWriter.writeEnum(CompressionAlgo, .deflate); - compressedWriter.writeSlice(compressedData); - - return compressedWriter.data.toOwnedSlice(); + writer.writeEnum(ChunkCompressionAlgo, .deflate); + writer.writeVarInt(usize, compressedData.len); + writer.writeSlice(compressedData); } - pub fn decompressChunk(ch: *chunk.Chunk, _data: []const u8) !void { + fn decompressBlockData(ch: *chunk.Chunk, reader: *BinaryReader) !void { std.debug.assert(ch.data.paletteLength == 1); - var reader = BinaryReader.init(_data); - const compressionAlgorithm = try reader.readEnum(CompressionAlgo); + const compressionAlgorithm = try reader.readEnum(ChunkCompressionAlgo); switch(compressionAlgorithm) { - .deflate, .deflate_with_position => { - if(compressionAlgorithm == .deflate_with_position) _ = try reader.readSlice(16); + .deflate, .deflate_no_block_entities, .deflate_with_position_no_block_entities => { + if(compressionAlgorithm == .deflate_with_position_no_block_entities) _ = try reader.readSlice(16); const decompressedData = main.stackAllocator.alloc(u8, chunk.chunkVolume*@sizeOf(u32)); defer main.stackAllocator.free(decompressedData); - const decompressedLength = try main.utils.Compression.inflateTo(decompressedData, reader.remaining); + const compressedDataLen = if(compressionAlgorithm == .deflate) try reader.readVarInt(usize) else reader.remaining.len; + const compressedData = try reader.readSlice(compressedDataLen); + const decompressedLength = try main.utils.Compression.inflateTo(decompressedData, compressedData); if(decompressedLength != chunk.chunkVolume*@sizeOf(u32)) return error.corrupted; var decompressedReader = BinaryReader.init(decompressedData); @@ -350,7 +366,7 @@ pub const ChunkCompression = struct { // MARK: ChunkCompression ch.data.setValue(i, main.blocks.Block.fromInt(try decompressedReader.readInt(u32))); } }, - .deflate_with_8bit_palette => { + .deflate_with_8bit_palette, .deflate_with_8bit_palette_no_block_entities => { const paletteLength = try reader.readInt(u8); ch.data.deinit(); @@ -363,7 +379,10 @@ pub const ChunkCompression = struct { // MARK: ChunkCompression const decompressedData = main.stackAllocator.alloc(u8, chunk.chunkVolume); defer main.stackAllocator.free(decompressedData); - const decompressedLength = try main.utils.Compression.inflateTo(decompressedData, reader.remaining); + const compressedDataLen = if(compressionAlgorithm == .deflate_with_8bit_palette) try reader.readVarInt(usize) else reader.remaining.len; + const compressedData = try reader.readSlice(compressedDataLen); + + const decompressedLength = try main.utils.Compression.inflateTo(decompressedData, compressedData); if(decompressedLength != chunk.chunkVolume) return error.corrupted; for(0..chunk.chunkVolume) |i| { @@ -373,9 +392,68 @@ pub const ChunkCompression = struct { // MARK: ChunkCompression .uniform => { ch.data.palette[0] = main.blocks.Block.fromInt(try reader.readInt(u32)); }, - _ => { - return error.corrupted; - }, + } + } + + pub fn compressBlockEntityData(ch: *chunk.Chunk, comptime target: Target, writer: *BinaryWriter) void { + ch.blockPosToEntityDataMapMutex.lock(); + defer ch.blockPosToEntityDataMapMutex.unlock(); + + if(ch.blockPosToEntityDataMap.count() == 0) return; + + writer.writeEnum(BlockEntityCompressionAlgo, .raw); + + var iterator = ch.blockPosToEntityDataMap.iterator(); + while(iterator.next()) |entry| { + const index = entry.key_ptr.*; + const blockEntityIndex = entry.value_ptr.*; + const block = ch.data.getValue(index); + const blockEntity = block.blockEntity() orelse continue; + + var tempWriter = BinaryWriter.init(main.stackAllocator); + defer tempWriter.deinit(); + + if(target == .toDisk) { + blockEntity.onStoreServerToDisk(blockEntityIndex, &tempWriter); + } else { + blockEntity.onStoreServerToClient(blockEntityIndex, &tempWriter); + } + + if(tempWriter.data.items.len == 0) continue; + + writer.writeInt(u16, @intCast(index)); + writer.writeVarInt(usize, tempWriter.data.items.len); + writer.writeSlice(tempWriter.data.items); + } + } + + pub fn decompressBlockEntityData(ch: *chunk.Chunk, comptime side: main.utils.Side, reader: *BinaryReader) !void { + if(reader.remaining.len == 0) return; + + const compressionAlgo = try reader.readEnum(BlockEntityCompressionAlgo); + std.debug.assert(compressionAlgo == .raw); + + while(reader.remaining.len != 0) { + const index = try reader.readInt(u16); + const pos = ch.getGlobalBlockPosFromIndex(index); + const dataLength = try reader.readVarInt(usize); + + const blockEntityData = try reader.readSlice(dataLength); + const block = ch.data.getValue(index); + const blockEntity = block.blockEntity() orelse { + std.log.err("Could not load BlockEntity at position {} for block {s}: Block has no block entity", .{pos, block.id()}); + continue; + }; + + var tempReader = BinaryReader.init(blockEntityData); + if(side == .server) { + blockEntity.onLoadServer(pos, ch, &tempReader) catch |err| { + std.log.err("Could not load BlockEntity at position {} for block {s}: {s}", .{pos, block.id(), @errorName(err)}); + continue; + }; + } else { + try blockEntity.onLoadClient(pos, ch, &tempReader); + } } } }; diff --git a/src/server/world.zig b/src/server/world.zig index 3af9f5a9..856661f3 100644 --- a/src/server/world.zig +++ b/src/server/world.zig @@ -314,7 +314,7 @@ const ChunkManager = struct { // MARK: ChunkManager @as(usize, @intCast(pos.wz -% region.pos.wz))/pos.voxelSize/chunk.chunkSize, )) |data| blk: { // Load chunk from file: defer main.stackAllocator.free(data); - storage.ChunkCompression.decompressChunk(&ch.super, data) catch { + storage.ChunkCompression.loadChunk(&ch.super, .server, data) catch { std.log.err("Storage for chunk {} in region file at {} is corrupted", .{pos, region.pos}); break :blk; }; @@ -1065,6 +1065,20 @@ pub const ServerWorld = struct { // MARK: ServerWorld return ch.getBlock(x - ch.super.pos.wx, y - ch.super.pos.wy, z - ch.super.pos.wz); } + pub fn getBlockAndBlockEntityData(self: *ServerWorld, x: i32, y: i32, z: i32, blockEntityDataWriter: *utils.BinaryWriter) ?Block { + const chunkPos = Vec3i{x, y, z} & ~@as(Vec3i, @splat(main.chunk.chunkMask)); + const otherChunk = self.getSimulationChunkAndIncreaseRefCount(chunkPos[0], chunkPos[1], chunkPos[2]) orelse return null; + defer otherChunk.decreaseRefCount(); + const ch = otherChunk.getChunk() orelse return null; + ch.mutex.lock(); + defer ch.mutex.unlock(); + const block = ch.getBlock(x - ch.super.pos.wx, y - ch.super.pos.wy, z - ch.super.pos.wz); + if(block.blockEntity()) |blockEntity| { + blockEntity.getServerToClientData(.{x, y, z}, &ch.super, blockEntityDataWriter); + } + return block; + } + /// Returns the actual block on failure pub fn cmpxchgBlock(_: *ServerWorld, wx: i32, wy: i32, wz: i32, oldBlock: ?Block, _newBlock: Block) ?Block { const baseChunk = ChunkManager.getOrGenerateChunkAndIncreaseRefCount(.{.wx = wx & ~@as(i32, chunk.chunkMask), .wy = wy & ~@as(i32, chunk.chunkMask), .wz = wz & ~@as(i32, chunk.chunkMask), .voxelSize = 1}); @@ -1080,11 +1094,16 @@ pub const ServerWorld = struct { // MARK: ServerWorld return currentBlock; } if(currentBlock != _newBlock) { - if(currentBlock.blockEntity()) |blockEntity| blockEntity.onBreakServer(.{wx, wy, wz}, &baseChunk.super); + if(currentBlock.blockEntity()) |blockEntity| blockEntity.updateServerData(.{wx, wy, wz}, &baseChunk.super, .remove) catch |err| { + std.log.err("Got error {s} while trying to remove entity data in position {} for block {s}", .{@errorName(err), Vec3i{wx, wy, wz}, currentBlock.id()}); + }; } baseChunk.updateBlockAndSetChanged(x, y, z, _newBlock); if(currentBlock != _newBlock) { - if(_newBlock.blockEntity()) |blockEntity| blockEntity.onPlaceServer(.{wx, wy, wz}, &baseChunk.super); + var reader = utils.BinaryReader.init(&.{}); + if(_newBlock.blockEntity()) |blockEntity| blockEntity.updateServerData(.{wx, wy, wz}, &baseChunk.super, .{.createOrUpdate = &reader}) catch |err| { + std.log.err("Got error {s} while trying to create empty entity data in position {} for block {s}", .{@errorName(err), Vec3i{wx, wy, wz}, _newBlock.id()}); + }; } } baseChunk.mutex.unlock(); @@ -1127,7 +1146,7 @@ pub const ServerWorld = struct { // MARK: ServerWorld defer server.freeUserListAndDecreaseRefCount(main.stackAllocator, userList); for(userList) |user| { - main.network.Protocols.blockUpdate.send(user.conn, &.{.{.x = wx, .y = wy, .z = wz, .newBlock = newBlock}}); + main.network.Protocols.blockUpdate.send(user.conn, &.{.{.x = wx, .y = wy, .z = wz, .newBlock = newBlock, .blockEntityData = &.{}}}); } return null; } diff --git a/src/utils.zig b/src/utils.zig index 4bb083d0..92c9e990 100644 --- a/src/utils.zig +++ b/src/utils.zig @@ -1601,6 +1601,8 @@ const endian: std.builtin.Endian = .big; pub const BinaryReader = struct { remaining: []const u8, + pub const AllErrors = error{OutOfBounds, IntOutOfBounds, InvalidEnumTag}; + pub fn init(data: []const u8) BinaryReader { return .{.remaining = data}; } @@ -1635,6 +1637,21 @@ pub const BinaryReader = struct { return std.mem.readInt(T, self.remaining[0..bufSize], endian); } + pub fn readVarInt(self: *BinaryReader, T: type) !T { + comptime std.debug.assert(@typeInfo(T).int.signedness == .unsigned); + comptime std.debug.assert(@bitSizeOf(T) > 8); // Why would you use a VarInt for this? + var result: T = 0; + var shift: std.meta.Int(.unsigned, std.math.log2_int_ceil(usize, @bitSizeOf(T))) = 0; + while(true) { + const nextByte = try self.readInt(u8); + const value: T = nextByte & 0x7f; + result |= try std.math.shlExact(T, value, shift); + if(nextByte & 0x80 == 0) break; + shift = try std.math.add(@TypeOf(shift), shift, 7); + } + return result; + } + pub fn readFloat(self: *BinaryReader, T: type) error{OutOfBounds, IntOutOfBounds}!T { const IntT = std.meta.Int(.unsigned, @typeInfo(T).float.bits); return @as(T, @bitCast(try self.readInt(IntT))); @@ -1698,6 +1715,19 @@ pub const BinaryWriter = struct { std.mem.writeInt(T, self.data.addMany(bufSize)[0..bufSize], value, endian); } + pub fn writeVarInt(self: *BinaryWriter, T: type, value: T) void { + comptime std.debug.assert(@typeInfo(T).int.signedness == .unsigned); + comptime std.debug.assert(@bitSizeOf(T) > 8); // Why would you use a VarInt for this? + var remaining: T = value; + while(true) { + var writeByte: u8 = @intCast(remaining & 0x7f); + remaining >>= 7; + if(remaining != 0) writeByte |= 0x80; + self.writeInt(u8, writeByte); + if(remaining == 0) break; + } + } + pub fn writeFloat(self: *BinaryWriter, T: type, value: T) void { const IntT = std.meta.Int(.unsigned, @typeInfo(T).float.bits); self.writeInt(IntT, @bitCast(value)); @@ -1738,6 +1768,19 @@ const ReadWriteTest = struct { try std.testing.expectEqual(expected, actual); } + fn testVarInt(comptime IntT: type, expected: IntT) !void { + var writer = getWriter(); + defer writer.deinit(); + writer.writeVarInt(IntT, expected); + + const expectedWidth = 1 + std.math.log2_int(IntT, @max(1, expected))/7; + try std.testing.expectEqual(expectedWidth, writer.data.items.len); + + var reader = getReader(writer.data.items); + const actual = try reader.readVarInt(IntT); + + try std.testing.expectEqual(expected, actual); + } fn testFloat(comptime FloatT: type, expected: FloatT) !void { var writer = getWriter(); defer writer.deinit(); @@ -1804,6 +1847,17 @@ test "read/write signed int" { } } +test "read/write unsigned varint" { + inline for([_]type{u9, u16, u31, u32, u64, u128}) |IntT| { + for(0..@bitSizeOf(IntT)) |i| { + try ReadWriteTest.testVarInt(IntT, @as(IntT, 1) << @intCast(i)); + try ReadWriteTest.testVarInt(IntT, (@as(IntT, 1) << @intCast(i)) - 1); + } + const max = std.math.maxInt(IntT); + try ReadWriteTest.testVarInt(IntT, max); + } +} + test "read/write float" { inline for([_]type{f16, f32, f64, f80, f128}) |floatT| { try ReadWriteTest.testFloat(floatT, std.math.floatMax(floatT)); @@ -1940,7 +1994,7 @@ pub fn SparseSet(comptime T: type, comptime IdType: type) type { // MARK: Sparse return @intFromEnum(id) < self.sparseToDenseIndex.items.len and self.sparseToDenseIndex.items[@intFromEnum(id)] != .noValue; } - pub fn set(self: *Self, allocator: NeverFailingAllocator, id: IdType, value: T) void { + pub fn add(self: *Self, allocator: NeverFailingAllocator, id: IdType) *T { std.debug.assert(id != .noValue); const denseId: IdType = @enumFromInt(self.dense.items.len); @@ -1952,22 +2006,31 @@ pub fn SparseSet(comptime T: type, comptime IdType: type) type { // MARK: Sparse std.debug.assert(self.sparseToDenseIndex.items[@intFromEnum(id)] == .noValue); self.sparseToDenseIndex.items[@intFromEnum(id)] = denseId; - self.dense.append(allocator, value); self.denseToSparseIndex.append(allocator, id); + return self.dense.addOne(allocator); } - pub fn remove(self: *Self, id: IdType) !void { + pub fn set(self: *Self, allocator: NeverFailingAllocator, id: IdType, value: T) void { + self.add(allocator, id).* = value; + } + + pub fn fetchRemove(self: *Self, id: IdType) !T { if(!self.contains(id)) return error.ElementNotFound; const denseId = @intFromEnum(self.sparseToDenseIndex.items[@intFromEnum(id)]); self.sparseToDenseIndex.items[@intFromEnum(id)] = .noValue; - _ = self.dense.swapRemove(denseId); + const result = self.dense.swapRemove(denseId); _ = self.denseToSparseIndex.swapRemove(denseId); if(denseId != self.dense.items.len) { self.sparseToDenseIndex.items[@intFromEnum(self.denseToSparseIndex.items[denseId])] = @enumFromInt(denseId); } + return result; + } + + pub fn remove(self: *Self, id: IdType) !void { + _ = try self.fetchRemove(id); } pub fn get(self: *Self, id: IdType) ?*T {