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>
18
assets/cubyz/blocks/sign/_defaults.zig.zon
Normal file
@ -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",
|
||||
}
|
6
assets/cubyz/blocks/sign/baobab.zig.zon
Normal file
@ -0,0 +1,6 @@
|
||||
.{
|
||||
.texture = "cubyz:sign/baobab",
|
||||
.item = .{
|
||||
.texture = "sign/baobab.png",
|
||||
},
|
||||
}
|
6
assets/cubyz/blocks/sign/birch.zig.zon
Normal file
@ -0,0 +1,6 @@
|
||||
.{
|
||||
.texture = "cubyz:sign/birch",
|
||||
.item = .{
|
||||
.texture = "sign/birch.png",
|
||||
},
|
||||
}
|
6
assets/cubyz/blocks/sign/mahogany.zig.zon
Normal file
@ -0,0 +1,6 @@
|
||||
.{
|
||||
.texture = "cubyz:sign/mahogany",
|
||||
.item = .{
|
||||
.texture = "sign/mahogany.png",
|
||||
},
|
||||
}
|
6
assets/cubyz/blocks/sign/oak.zig.zon
Normal file
@ -0,0 +1,6 @@
|
||||
.{
|
||||
.texture = "cubyz:sign/oak",
|
||||
.item = .{
|
||||
.texture = "sign/oak.png",
|
||||
},
|
||||
}
|
6
assets/cubyz/blocks/sign/pine.zig.zon
Normal file
@ -0,0 +1,6 @@
|
||||
.{
|
||||
.texture = "cubyz:sign/pine",
|
||||
.item = .{
|
||||
.texture = "sign/pine.png",
|
||||
},
|
||||
}
|
6
assets/cubyz/blocks/sign/willow.zig.zon
Normal file
@ -0,0 +1,6 @@
|
||||
.{
|
||||
.texture = "cubyz:sign/willow",
|
||||
.item = .{
|
||||
.texture = "sign/willow.png",
|
||||
},
|
||||
}
|
BIN
assets/cubyz/blocks/textures/sign/baobab.png
Normal file
After Width: | Height: | Size: 317 B |
BIN
assets/cubyz/blocks/textures/sign/birch.png
Normal file
After Width: | Height: | Size: 309 B |
BIN
assets/cubyz/blocks/textures/sign/mahogany.png
Normal file
After Width: | Height: | Size: 306 B |
BIN
assets/cubyz/blocks/textures/sign/oak.png
Normal file
After Width: | Height: | Size: 303 B |
BIN
assets/cubyz/blocks/textures/sign/pine.png
Normal file
After Width: | Height: | Size: 296 B |
BIN
assets/cubyz/blocks/textures/sign/willow.png
Normal file
After Width: | Height: | Size: 306 B |
BIN
assets/cubyz/items/textures/sign/baobab.png
Normal file
After Width: | Height: | Size: 332 B |
BIN
assets/cubyz/items/textures/sign/birch.png
Normal file
After Width: | Height: | Size: 332 B |
BIN
assets/cubyz/items/textures/sign/mahogany.png
Normal file
After Width: | Height: | Size: 327 B |
BIN
assets/cubyz/items/textures/sign/oak.png
Normal file
After Width: | Height: | Size: 333 B |
BIN
assets/cubyz/items/textures/sign/pine.png
Normal file
After Width: | Height: | Size: 324 B |
BIN
assets/cubyz/items/textures/sign/willow.png
Normal file
After Width: | Height: | Size: 322 B |
123
assets/cubyz/models/sign/ceiling.obj
Normal file
@ -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
|
84
assets/cubyz/models/sign/floor.obj
Normal file
@ -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
|
45
assets/cubyz/models/sign/side.obj
Normal file
@ -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
|
26
assets/cubyz/shaders/block_entity/sign.frag
Normal file
@ -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);
|
||||
}
|
69
assets/cubyz/shaders/block_entity/sign.vert
Normal file
@ -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;
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
@ -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");
|
||||
|
@ -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",
|
||||
|
68
src/gui/windows/sign_editor.zig
Normal file
@ -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();
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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 {
|
||||
|