- [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>
This commit is contained in:
IntegratedQuantum 2025-05-31 10:49:50 +02:00 committed by GitHub
parent b333d22bbc
commit eaaf524f99
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
39 changed files with 1202 additions and 145 deletions

View 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",
}

View File

@ -0,0 +1,6 @@
.{
.texture = "cubyz:sign/baobab",
.item = .{
.texture = "sign/baobab.png",
},
}

View File

@ -0,0 +1,6 @@
.{
.texture = "cubyz:sign/birch",
.item = .{
.texture = "sign/birch.png",
},
}

View File

@ -0,0 +1,6 @@
.{
.texture = "cubyz:sign/mahogany",
.item = .{
.texture = "sign/mahogany.png",
},
}

View File

@ -0,0 +1,6 @@
.{
.texture = "cubyz:sign/oak",
.item = .{
.texture = "sign/oak.png",
},
}

View File

@ -0,0 +1,6 @@
.{
.texture = "cubyz:sign/pine",
.item = .{
.texture = "sign/pine.png",
},
}

View File

@ -0,0 +1,6 @@
.{
.texture = "cubyz:sign/willow",
.item = .{
.texture = "sign/willow.png",
},
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 317 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 309 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 306 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 303 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 296 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 306 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 332 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 332 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 327 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 333 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 324 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 322 B

View 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

View 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

View 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

View 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);
}

View 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;
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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,

View File

@ -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);

View File

@ -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");

View File

@ -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",

View 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();
}
}

View File

@ -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

View File

@ -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 {

View File

@ -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);

View File

@ -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, &regenerateMeshList);
mesh.updateBlock(blockUpdate.x, blockUpdate.y, blockUpdate.z, blockUpdate.newBlock, blockUpdate.blockEntityData, &lightRefreshList, &regenerateMeshList);
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 {

View File

@ -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,

View File

@ -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);
}
}
}
};

View File

@ -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;
}

View File

@ -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 {