Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
056859f
Batch block updates
Argmaster Apr 17, 2025
6ac6ae1
Merge remote-tracking branch 'origin/master' into block-update-batching
Argmaster Apr 23, 2025
d551aba
Apply review change requests
Argmaster Apr 23, 2025
e931773
Allow blockUpdate to carry multiple block updates in single message
Argmaster Apr 23, 2025
24a1955
Read until there is nothing left
Argmaster Apr 24, 2025
54737bc
Use mesh_storage.BlockUpdate
Argmaster Apr 24, 2025
7a8bfd1
Break instead of boolean
Argmaster Apr 24, 2025
3e84533
Restore client side neighbor updates
Argmaster Apr 24, 2025
8ebec58
Move side check in blockUpdate out of the loop
Argmaster Apr 24, 2025
cc1c7a5
Update src/utils.zig
Argmaster Apr 24, 2025
41b6552
Fix minor issues
Argmaster Apr 24, 2025
731fe13
Reverse ownership logic + change contains into liesInChunk
Argmaster Apr 24, 2025
ebddb5e
Merge remote-tracking branch 'origin/master' into block-update-batching
Argmaster Apr 26, 2025
1bbbb52
Merge remote-tracking branch 'origin/master' into block-update-batching
Argmaster Apr 27, 2025
61fc761
Update liesInChunk
Argmaster Apr 28, 2025
fe859e8
No name for upadeBlock param
Argmaster Apr 28, 2025
e95eaab
Apply review change requests
Argmaster Apr 28, 2025
391936d
Merge remote-tracking branch 'upstream/master' into block-update-batc…
Argmaster Apr 29, 2025
3723263
Fix formatting
Argmaster Apr 29, 2025
9d33686
Restore onBreakClient where it should be
Argmaster Apr 29, 2025
02df194
Update src/renderer/chunk_meshing.zig
Argmaster Apr 29, 2025
114cd33
Update src/renderer/chunk_meshing.zig
Argmaster Apr 30, 2025
35ad112
Merge remote-tracking branch 'upstream/master' into block-update-batc…
Argmaster Apr 30, 2025
d3b5310
Converge formatting with master
Argmaster Apr 30, 2025
e63a967
fix formatting (https://github.com/ziglang/zig-spec/issues/38 is so s…
IntegratedQuantum Apr 30, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions src/Inventory.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1654,15 +1654,16 @@ pub const Command = struct { // MARK: Command
}) {
if(side == .server) {
// Inform the client of the actual block:
main.network.Protocols.blockUpdate.send(user.?.conn, self.pos[0], self.pos[1], self.pos[2], main.server.world.?.getBlock(self.pos[0], self.pos[1], self.pos[2]) orelse return);
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)});
}
return;
}

if(side == .server) {
if(main.server.world.?.cmpxchgBlock(self.pos[0], self.pos[1], self.pos[2], self.oldBlock, self.newBlock)) |actualBlock| {
// Inform the client of the actual block:
main.network.Protocols.blockUpdate.send(user.?.conn, self.pos[0], self.pos[1], self.pos[2], actualBlock);
main.network.Protocols.blockUpdate.send(user.?.conn, &.{.init(self.pos, actualBlock)});
return error.serverFailure;
}
}
Expand Down
8 changes: 7 additions & 1 deletion src/chunk.zig
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ pub fn getIndex(x: i32, y: i32, z: i32) u32 {
std.debug.assert((x & chunkMask) == x and (y & chunkMask) == y and (z & chunkMask) == z);
return (@as(u32, @intCast(x)) << chunkShift2) | (@as(u32, @intCast(y)) << chunkShift) | @as(u32, @intCast(z));
}

/// Gets the x coordinate from a given index inside this chunk.
fn extractXFromIndex(index: usize) i32 {
return @intCast(index >> chunkShift2 & chunkMask);
Expand Down Expand Up @@ -303,6 +304,11 @@ pub const Chunk = struct { // MARK: Chunk
return self.data.getValue(index);
}

/// Checks if the given relative coordinates lie within the bounds of this chunk.
pub fn liesInChunk(self: *const Chunk, x: i32, y: i32, z: i32) bool {
return x >= 0 and x < self.width and y >= 0 and y < self.width and z >= 0 and z < self.width;
}

pub fn getLocalBlockIndex(self: *const Chunk, worldPos: Vec3i) u32 {
return getIndex(
(worldPos[0] - self.pos.wx) >> self.voxelSizeShift,
Expand Down Expand Up @@ -376,7 +382,7 @@ pub const ServerChunk = struct { // MARK: ServerChunk

/// Checks if the given relative coordinates lie within the bounds of this chunk.
pub fn liesInChunk(self: *const ServerChunk, x: i32, y: i32, z: i32) bool {
return x >= 0 and x < self.super.width and y >= 0 and y < self.super.width and z >= 0 and z < self.super.width;
return self.super.liesInChunk(x, y, z);
}

/// This is useful to convert for loops to work for reduced resolution:
Expand Down
28 changes: 17 additions & 11 deletions src/network.zig
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const Vec3d = vec.Vec3d;
const Vec3f = vec.Vec3f;
const Vec3i = vec.Vec3i;
const NeverFailingAllocator = main.heap.NeverFailingAllocator;
const BlockUpdate = renderer.mesh_storage.BlockUpdate;

//TODO: Might want to use SSL or something similar to encode the message

Expand Down Expand Up @@ -943,23 +944,28 @@ pub const Protocols = struct {
pub const id: u8 = 7;
pub const asynchronous = false;
fn receive(conn: *Connection, reader: *utils.BinaryReader) !void {
const x = try reader.readInt(i32);
const y = try reader.readInt(i32);
const z = try reader.readInt(i32);
const newBlock = Block.fromInt(try reader.readInt(u32));
if(conn.isServerSide()) {
return error.InvalidPacket;
} else {
renderer.mesh_storage.updateBlock(x, y, z, newBlock);
}
while(reader.remaining.len != 0) {
renderer.mesh_storage.updateBlock(.{
.x = try reader.readInt(i32),
.y = try reader.readInt(i32),
.z = try reader.readInt(i32),
.newBlock = Block.fromInt(try reader.readInt(u32)),
});
}
}
pub fn send(conn: *Connection, x: i32, y: i32, z: i32, newBlock: Block) void {
pub fn send(conn: *Connection, updates: []const BlockUpdate) void {
var writer = utils.BinaryWriter.initCapacity(main.stackAllocator, 16);
defer writer.deinit();
writer.writeInt(i32, x);
writer.writeInt(i32, y);
writer.writeInt(i32, z);
writer.writeInt(u32, newBlock.toInt());

for(updates) |update| {
writer.writeInt(i32, update.x);
writer.writeInt(i32, update.y);
writer.writeInt(i32, update.z);
writer.writeInt(u32, update.newBlock.toInt());
}
conn.send(.fast, id, writer.data.items);
}
};
Expand Down
2 changes: 1 addition & 1 deletion src/renderer.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1111,7 +1111,7 @@ pub const MeshSelection = struct { // MARK: MeshSelection
.newBlock = newBlock,
},
});
mesh_storage.updateBlock(x, y, z, newBlock);
mesh_storage.updateBlock(.{.x = x, .y = y, .z = z, .newBlock = newBlock});
}

pub fn drawCube(projectionMatrix: Mat4f, viewMatrix: Mat4f, relativePositionToPlayer: Vec3d, min: Vec3f, max: Vec3f) void {
Expand Down
59 changes: 32 additions & 27 deletions src/renderer/chunk_meshing.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1172,9 +1172,7 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh
}
}

pub fn updateBlock(self: *ChunkMesh, _x: i32, _y: i32, _z: i32, _newBlock: Block) void {
var lightRefreshList = main.List(*ChunkMesh).init(main.stackAllocator);
defer lightRefreshList.deinit();
pub fn updateBlock(self: *ChunkMesh, _x: i32, _y: i32, _z: i32, _newBlock: Block, 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);
Expand All @@ -1194,36 +1192,41 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh

var neighborBlocks: [6]Block = undefined;
@memset(&neighborBlocks, .{.typ = 0, .data = 0});

for(chunk.Neighbor.iterable) |neighbor| {
const nx = x + neighbor.relX();
const ny = y + neighbor.relY();
const nz = z + neighbor.relZ();

if(nx & chunk.chunkMask != nx or ny & chunk.chunkMask != ny or nz & chunk.chunkMask != nz) {
const nnx: u5 = @intCast(nx & chunk.chunkMask);
const nny: u5 = @intCast(ny & chunk.chunkMask);
const nnz: u5 = @intCast(nz & chunk.chunkMask);

const neighborChunkMesh = mesh_storage.getNeighborAndIncreaseRefCount(self.pos, self.pos.voxelSize, neighbor) orelse continue;
defer neighborChunkMesh.decreaseRefCount();
const index = chunk.getIndex(nx & chunk.chunkMask, ny & chunk.chunkMask, nz & chunk.chunkMask);

const index = chunk.getIndex(nnx, nny, nnz);

neighborChunkMesh.mutex.lock();
var neighborBlock = neighborChunkMesh.chunk.data.getValue(index);
if(neighborBlock.mode().dependsOnNeighbors) {
if(neighborBlock.mode().updateData(&neighborBlock, neighbor.reverse(), newBlock)) {
neighborChunkMesh.chunk.data.setValue(index, neighborBlock);
neighborChunkMesh.mutex.unlock();
neighborChunkMesh.updateBlockLight(@intCast(nx & chunk.chunkMask), @intCast(ny & chunk.chunkMask), @intCast(nz & chunk.chunkMask), neighborBlock, &lightRefreshList);
neighborChunkMesh.generateMesh(&lightRefreshList);
neighborChunkMesh.mutex.lock();
}

if(neighborBlock.mode().dependsOnNeighbors and neighborBlock.mode().updateData(&neighborBlock, neighbor.reverse(), newBlock)) {
neighborChunkMesh.chunk.data.setValue(index, neighborBlock);
neighborChunkMesh.mutex.unlock();
neighborChunkMesh.updateBlockLight(nnx, nny, nnz, neighborBlock, lightRefreshList);
appendIfNotContained(regenerateMeshList, neighborChunkMesh);
neighborChunkMesh.mutex.lock();
}
neighborChunkMesh.mutex.unlock();
neighborBlocks[neighbor.toInt()] = neighborBlock;
} else {
const index = chunk.getIndex(nx, ny, nz);
self.mutex.lock();
var neighborBlock = self.chunk.data.getValue(index);
if(neighborBlock.mode().dependsOnNeighbors) {
if(neighborBlock.mode().updateData(&neighborBlock, neighbor.reverse(), newBlock)) {
self.chunk.data.setValue(index, neighborBlock);
self.updateBlockLight(@intCast(nx & chunk.chunkMask), @intCast(ny & chunk.chunkMask), @intCast(nz & chunk.chunkMask), neighborBlock, &lightRefreshList);
}
if(neighborBlock.mode().dependsOnNeighbors and neighborBlock.mode().updateData(&neighborBlock, neighbor.reverse(), newBlock)) {
self.chunk.data.setValue(index, neighborBlock);
self.updateBlockLight(@intCast(nx), @intCast(ny), @intCast(nz), neighborBlock, lightRefreshList);
}
self.mutex.unlock();
neighborBlocks[neighbor.toInt()] = neighborBlock;
Expand All @@ -1242,9 +1245,9 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh
class.onPlaceClient(.{_x, _y, _z}, self.chunk);
}

self.updateBlockLight(x, y, z, newBlock, &lightRefreshList);
self.updateBlockLight(x, y, z, newBlock, lightRefreshList);

self.mutex.lock();
defer self.mutex.unlock();
// Update neighbor chunks:
if(x == 0) {
self.lastNeighborsHigherLod[chunk.Neighbor.dirNegX.toInt()] = null;
Expand All @@ -1268,16 +1271,18 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh
self.lastNeighborsSameLod[chunk.Neighbor.dirUp.toInt()] = null;
}
self.mutex.unlock();
self.generateMesh(&lightRefreshList); // TODO: Batch mesh updates instead of applying them for each block changes.
self.mutex.lock();
for(lightRefreshList.items) |other| {
if(other.needsLightRefresh.load(.unordered)) {
other.scheduleLightRefreshAndDecreaseRefCount1();
} else {
other.decreaseRefCount();

appendIfNotContained(regenerateMeshList, self);
}

fn appendIfNotContained(list: *main.List(*ChunkMesh), mesh: *ChunkMesh) void {
for(list.items) |other| {
if(other == mesh) {
return;
}
}
self.uploadData();
mesh.increaseRefCount();
list.append(mesh);
}

fn clearNeighborA(self: *ChunkMesh, neighbor: chunk.Neighbor, comptime isLod: bool) void {
Expand Down
33 changes: 14 additions & 19 deletions src/renderer/lighting.zig
Original file line number Diff line number Diff line change
Expand Up @@ -141,23 +141,27 @@ pub const ChannelChunk = struct {
}
self.data.optimizeLayout();
self.lock.unlockWrite();
if(mesh_storage.getMeshAndIncreaseRefCount(self.ch.pos)) |mesh| outer: {
self.addSelfToLightRefreshList(lightRefreshList);

for(chunk.Neighbor.iterable) |neighbor| {
if(neighborLists[neighbor.toInt()].items.len == 0) continue;
const neighborMesh = mesh_storage.getNeighborAndIncreaseRefCount(self.ch.pos, self.ch.pos.voxelSize, neighbor) orelse continue;
defer neighborMesh.decreaseRefCount();
neighborMesh.lightingData[@intFromBool(self.isSun)].propagateFromNeighbor(lightQueue, neighborLists[neighbor.toInt()].items, lightRefreshList);
}
}

fn addSelfToLightRefreshList(self: *ChannelChunk, lightRefreshList: *main.List(*chunk_meshing.ChunkMesh)) void {
if(mesh_storage.getMeshAndIncreaseRefCount(self.ch.pos)) |mesh| {
for(lightRefreshList.items) |other| {
if(mesh == other) {
mesh.decreaseRefCount();
break :outer;
return;
}
}
mesh.needsLightRefresh.store(true, .release);
lightRefreshList.append(mesh);
}

for(chunk.Neighbor.iterable) |neighbor| {
if(neighborLists[neighbor.toInt()].items.len == 0) continue;
const neighborMesh = mesh_storage.getNeighborAndIncreaseRefCount(self.ch.pos, self.ch.pos.voxelSize, neighbor) orelse continue;
defer neighborMesh.decreaseRefCount();
neighborMesh.lightingData[@intFromBool(self.isSun)].propagateFromNeighbor(lightQueue, neighborLists[neighbor.toInt()].items, lightRefreshList);
}
}

fn propagateDestructive(self: *ChannelChunk, lightQueue: *main.utils.CircularBufferQueue(Entry), constructiveEntries: *main.ListUnmanaged(ChunkEntries), isFirstBlock: bool, lightRefreshList: *main.List(*chunk_meshing.ChunkMesh)) main.ListUnmanaged(PositionEntry) {
Expand Down Expand Up @@ -230,16 +234,7 @@ pub const ChannelChunk = struct {
}
}
self.lock.unlockWrite();
if(mesh_storage.getMeshAndIncreaseRefCount(self.ch.pos)) |mesh| outer: {
for(lightRefreshList.items) |other| {
if(mesh == other) {
mesh.decreaseRefCount();
break :outer;
}
}
mesh.needsLightRefresh.store(true, .release);
lightRefreshList.append(mesh);
}
self.addSelfToLightRefreshList(lightRefreshList);

for(chunk.Neighbor.iterable) |neighbor| {
if(neighborLists[neighbor.toInt()].items.len == 0) continue;
Expand Down
57 changes: 45 additions & 12 deletions src/renderer/mesh_storage.zig
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const Mat4f = vec.Mat4f;
const EventStatus = main.entity_data.EventStatus;

const chunk_meshing = @import("chunk_meshing.zig");
const ChunkMesh = chunk_meshing.ChunkMesh;

const ChunkMeshNode = struct {
mesh: ?*chunk_meshing.ChunkMesh = null,
Expand All @@ -44,12 +45,18 @@ var lastPy: i32 = 0;
var lastPz: i32 = 0;
var lastRD: u16 = 0;
var mutex: std.Thread.Mutex = .{};
const BlockUpdate = struct {

pub const BlockUpdate = struct {
x: i32,
y: i32,
z: i32,
newBlock: blocks.Block,

pub fn init(pos: Vec3i, block: blocks.Block) BlockUpdate {
return .{.x = pos[0], .y = pos[1], .z = pos[2], .newBlock = block};
}
};

var blockUpdateList: main.utils.ConcurrentQueue(BlockUpdate) = undefined;

var meshMemoryPool: main.heap.MemoryPool(chunk_meshing.ChunkMesh) = undefined;
Expand Down Expand Up @@ -758,15 +765,8 @@ pub noinline fn updateAndGetRenderChunks(conn: *network.Connection, frustum: *co
return meshList.items;
}

pub fn updateMeshes(targetTime: i64) void { // MARK: updateMeshes()
// First of all process all the block updates:
while(blockUpdateList.dequeue()) |blockUpdate| {
const pos = chunk.ChunkPosition{.wx = blockUpdate.x, .wy = blockUpdate.y, .wz = blockUpdate.z, .voxelSize = 1};
if(getMeshAndIncreaseRefCount(pos)) |mesh| {
defer mesh.decreaseRefCount();
mesh.updateBlock(blockUpdate.x, blockUpdate.y, blockUpdate.z, blockUpdate.newBlock);
} // TODO: It seems like we simply ignore the block update if we don't have the mesh yet.
}
pub fn updateMeshes(targetTime: i64) void { // MARK: updateMeshes()=
if(!blockUpdateList.empty()) batchUpdateBlocks();

mutex.lock();
defer mutex.unlock();
Expand Down Expand Up @@ -859,6 +859,39 @@ pub fn updateMeshes(targetTime: i64) void { // MARK: updateMeshes()
}
}

fn batchUpdateBlocks() void {
var lightRefreshList = main.List(*ChunkMesh).init(main.stackAllocator);
defer lightRefreshList.deinit();

var regenerateMeshList = main.List(*ChunkMesh).init(main.stackAllocator);
defer regenerateMeshList.deinit();

// First of all process all the block updates:
while(blockUpdateList.dequeue()) |blockUpdate| {
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.decreaseRefCount();
} // TODO: It seems like we simply ignore the block update if we don't have the mesh yet.
}
for(regenerateMeshList.items) |mesh| {
mesh.generateMesh(&lightRefreshList);
}
{
for(lightRefreshList.items) |mesh| {
if(mesh.needsLightRefresh.load(.unordered)) {
mesh.scheduleLightRefreshAndDecreaseRefCount1();
} else {
mesh.decreaseRefCount();
}
}
}
for(regenerateMeshList.items) |mesh| {
mesh.uploadData();
mesh.decreaseRefCount();
}
}

// MARK: adders

pub fn addMeshToClearListAndDecreaseRefCount(mesh: *chunk_meshing.ChunkMesh) void {
Expand Down Expand Up @@ -953,8 +986,8 @@ pub const MeshGenerationTask = struct { // MARK: MeshGenerationTask

// MARK: updaters

pub fn updateBlock(x: i32, y: i32, z: i32, newBlock: blocks.Block) void {
blockUpdateList.enqueue(.{.x = x, .y = y, .z = z, .newBlock = newBlock});
pub fn updateBlock(update: BlockUpdate) void {
blockUpdateList.enqueue(update);
}

pub fn updateChunkMesh(mesh: *chunk.Chunk) void {
Expand Down
Loading
Loading