From a85a3ec2d24f89b15ab4680395fa1902be300e97 Mon Sep 17 00:00:00 2001 From: IntegratedQuantum Date: Mon, 28 Jul 2025 20:44:26 +0200 Subject: [PATCH] Atomicize the palette --- src/block_entity.zig | 6 +- src/chunk.zig | 12 ++-- src/network.zig | 2 +- src/renderer/chunk_meshing.zig | 46 +++++++-------- src/renderer/lighting.zig | 58 ++++++++++++------- src/server/storage.zig | 16 ++--- .../terrain/chunkgen/TerrainGenerator.zig | 4 +- src/server/world.zig | 4 +- src/utils.zig | 49 ++++++++++------ src/utils/heap.zig | 12 ++-- 10 files changed, 120 insertions(+), 89 deletions(-) diff --git a/src/block_entity.zig b/src/block_entity.zig index ef6ac4ee..4c207788 100644 --- a/src/block_entity.zig +++ b/src/block_entity.zig @@ -351,7 +351,7 @@ pub const BlockEntityTypes = struct { } data.valuePtr.* = .{ .blockPos = pos, - .block = chunk.data.getValue(chunk.getLocalBlockIndex(pos)), + .block = chunk.data.getValueFromOwnerThread(chunk.getLocalBlockIndex(pos)), .renderedTexture = null, .text = main.globalAllocator.dupe(u8, event.createOrUpdate.remaining), }; @@ -405,7 +405,7 @@ pub const BlockEntityTypes = struct { mesh.mutex.lock(); defer mesh.mutex.unlock(); const index = mesh.chunk.getLocalBlockIndex(pos); - const block = mesh.chunk.data.getValue(index); + const block = mesh.chunk.data.getValueFromOwnerThread(index); const blockEntity = block.blockEntity() orelse return; if(!std.mem.eql(u8, blockEntity.id, id)) return; @@ -418,7 +418,7 @@ pub const BlockEntityTypes = struct { } data.valuePtr.* = .{ .blockPos = pos, - .block = mesh.chunk.data.getValue(mesh.chunk.getLocalBlockIndex(pos)), + .block = mesh.chunk.data.getValueFromOwnerThread(mesh.chunk.getLocalBlockIndex(pos)), .renderedTexture = null, .text = main.globalAllocator.dupe(u8, newText), }; diff --git a/src/chunk.zig b/src/chunk.zig index dc3b4a85..6ba37e4e 100644 --- a/src/chunk.zig +++ b/src/chunk.zig @@ -296,7 +296,7 @@ pub const Chunk = struct { // MARK: Chunk while(iterator.next()) |elem| { const index = elem.key_ptr.*; const entityDataIndex = elem.value_ptr.*; - const block = self.data.getValue(index); + const block = self.data.getValueFromOwnerThread(index); const blockEntity = block.blockEntity() orelse unreachable; switch(side) { .client => { @@ -327,7 +327,7 @@ pub const Chunk = struct { // MARK: Chunk const y = _y >> self.voxelSizeShift; const z = _z >> self.voxelSizeShift; const index = getIndex(x, y, z); - return self.data.getValue(index); + return self.data.getValueFromOwnerThread(index); } /// Checks if the given relative coordinates lie within the bounds of this chunk. @@ -437,7 +437,7 @@ pub const ServerChunk = struct { // MARK: ServerChunk const y = _y >> self.super.voxelSizeShift; const z = _z >> self.super.voxelSizeShift; const index = getIndex(x, y, z); - return self.super.data.getValue(index); + return self.super.data.getValueFromOwnerThread(index); } /// Updates a block if it is inside this chunk. @@ -487,7 +487,7 @@ pub const ServerChunk = struct { // MARK: ServerChunk const y = _y >> self.super.voxelSizeShift; const z = _z >> self.super.voxelSizeShift; const index = getIndex(x, y, z); - const oldBlock = self.super.data.getValue(index); + const oldBlock = self.super.data.getValueFromOwnerThread(index); if(oldBlock.typ == 0 or oldBlock.degradable()) { self.super.data.setValue(index, newBlock); } @@ -544,7 +544,7 @@ pub const ServerChunk = struct { // MARK: ServerChunk while(dz <= 1) : (dz += 1) { const index = getIndex(x*2 + dx, y*2 + dy, z*2 + dz); const i = dx*4 + dz*2 + dy; - octantBlocks[i] = other.super.data.getValue(index); + octantBlocks[i] = other.super.data.getValueFromOwnerThread(index); octantBlocks[i].typ = octantBlocks[i].lodReplacement(); if(octantBlocks[i].typ == 0) { neighborCount[i] = 0; @@ -558,7 +558,7 @@ pub const ServerChunk = struct { // MARK: ServerChunk const nz = z*2 + dz + n.relZ(); if((nx & chunkMask) == nx and (ny & chunkMask) == ny and (nz & chunkMask) == nz) { // If it's inside the chunk. const neighborIndex = getIndex(nx, ny, nz); - if(other.super.data.getValue(neighborIndex).transparent()) { + if(other.super.data.getValueFromOwnerThread(neighborIndex).transparent()) { count += 5; } } else { diff --git a/src/network.zig b/src/network.zig index 0f6e4515..2607801f 100644 --- a/src/network.zig +++ b/src/network.zig @@ -1312,7 +1312,7 @@ pub const Protocols = struct { mesh.mutex.lock(); defer mesh.mutex.unlock(); const index = mesh.chunk.getLocalBlockIndex(pos); - const block = mesh.chunk.data.getValue(index); + const block = mesh.chunk.data.getValueFromOwnerThread(index); const blockEntity = block.blockEntity() orelse return; var writer = utils.BinaryWriter.init(main.stackAllocator); diff --git a/src/renderer/chunk_meshing.zig b/src/renderer/chunk_meshing.zig index a2ad6d18..ae24a778 100644 --- a/src/renderer/chunk_meshing.zig +++ b/src/renderer/chunk_meshing.zig @@ -799,7 +799,7 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh while(y < chunk.chunkSize) : (y += 1) { var z: u8 = 0; while(z < chunk.chunkSize) : (z += 1) { - const block = self.chunk.data.getValue(chunk.getIndex(x, y, z)); + const block = self.chunk.data.getValueFromOwnerThread(chunk.getIndex(x, y, z)); if(block.light() != 0) lightEmittingBlocks.append(.{x, y, z}); } } @@ -807,7 +807,7 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh self.mutex.unlock(); self.lightingData[0].propagateLights(lightEmittingBlocks.items, true, lightRefreshList); sunLight: { - var allSun: bool = self.chunk.data.paletteLength == 1 and self.chunk.data.palette[0].typ == 0; + var allSun: bool = self.chunk.data.paletteLength == 1 and self.chunk.data.palette.raw.toSlice()[0].raw.typ == 0; var sunStarters: [chunk.chunkSize*chunk.chunkSize][3]u8 = undefined; var index: usize = 0; const lightStartMap = mesh_storage.getLightMapPiece(self.pos.wx, self.pos.wy, self.pos.voxelSize) orelse break :sunLight; @@ -918,7 +918,7 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh var paletteCache = main.stackAllocator.alloc(OcclusionInfo, self.chunk.data.paletteLength); defer main.stackAllocator.free(paletteCache); for(0..self.chunk.data.paletteLength) |i| { - const block = self.chunk.data.palette[i]; + const block = self.chunk.data.palette.raw.toSlice()[i].raw; const model = blocks.meshes.model(block).model(); var result: OcclusionInfo = .{}; if(model.noNeighborsOccluded or block.viewThrough()) { @@ -1002,7 +1002,7 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh hasFaces[x][y] |= setBit; } if(occlusionInfo.hasInternalQuads) { - const block = self.chunk.data.palette[paletteId]; + const block = self.chunk.data.palette.raw.toSlice()[paletteId].raw; if(block.transparent()) { appendInternalQuads(block, x, y, z, false, &transparentCore, main.stackAllocator); } else { @@ -1022,10 +1022,10 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh const z = @ctz(bitMask); const setBit = @as(u32, 1) << @intCast(z); bitMask &= ~setBit; - var block = self.chunk.data.getValue(chunk.getIndex(@intCast(x), @intCast(y), z)); + var block = self.chunk.data.getValueFromOwnerThread(chunk.getIndex(@intCast(x), @intCast(y), z)); if(depthFilteredViewThroughMask[x][y] & setBit != 0) block.typ = block.opaqueVariant(); if(block.viewThrough() and !block.alwaysViewThrough()) { // Needs to check the neighbor block - const neighborBlock = self.chunk.data.getValue(chunk.getIndex(@intCast(x - 1), @intCast(y), z)); + const neighborBlock = self.chunk.data.getValueFromOwnerThread(chunk.getIndex(@intCast(x - 1), @intCast(y), z)); if(block == neighborBlock) continue; } if(block.transparent()) { @@ -1049,10 +1049,10 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh const z = @ctz(bitMask); const setBit = @as(u32, 1) << @intCast(z); bitMask &= ~setBit; - var block = self.chunk.data.getValue(chunk.getIndex(@intCast(x), @intCast(y), z)); + var block = self.chunk.data.getValueFromOwnerThread(chunk.getIndex(@intCast(x), @intCast(y), z)); if(depthFilteredViewThroughMask[x][y] & setBit != 0) block.typ = block.opaqueVariant(); if(block.viewThrough() and !block.alwaysViewThrough()) { // Needs to check the neighbor block - const neighborBlock = self.chunk.data.getValue(chunk.getIndex(@intCast(x + 1), @intCast(y), z)); + const neighborBlock = self.chunk.data.getValueFromOwnerThread(chunk.getIndex(@intCast(x + 1), @intCast(y), z)); if(block == neighborBlock) continue; } if(block.transparent()) { @@ -1076,10 +1076,10 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh const z = @ctz(bitMask); const setBit = @as(u32, 1) << @intCast(z); bitMask &= ~setBit; - var block = self.chunk.data.getValue(chunk.getIndex(@intCast(x), @intCast(y), z)); + var block = self.chunk.data.getValueFromOwnerThread(chunk.getIndex(@intCast(x), @intCast(y), z)); if(depthFilteredViewThroughMask[x][y] & setBit != 0) block.typ = block.opaqueVariant(); if(block.viewThrough() and !block.alwaysViewThrough()) { // Needs to check the neighbor block - const neighborBlock = self.chunk.data.getValue(chunk.getIndex(@intCast(x), @intCast(y - 1), z)); + const neighborBlock = self.chunk.data.getValueFromOwnerThread(chunk.getIndex(@intCast(x), @intCast(y - 1), z)); if(block == neighborBlock) continue; } if(block.transparent()) { @@ -1103,10 +1103,10 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh const z = @ctz(bitMask); const setBit = @as(u32, 1) << @intCast(z); bitMask &= ~setBit; - var block = self.chunk.data.getValue(chunk.getIndex(@intCast(x), @intCast(y), z)); + var block = self.chunk.data.getValueFromOwnerThread(chunk.getIndex(@intCast(x), @intCast(y), z)); if(depthFilteredViewThroughMask[x][y] & setBit != 0) block.typ = block.opaqueVariant(); if(block.viewThrough() and !block.alwaysViewThrough()) { // Needs to check the neighbor block - const neighborBlock = self.chunk.data.getValue(chunk.getIndex(@intCast(x), @intCast(y + 1), z)); + const neighborBlock = self.chunk.data.getValueFromOwnerThread(chunk.getIndex(@intCast(x), @intCast(y + 1), z)); if(block == neighborBlock) continue; } if(block.transparent()) { @@ -1130,10 +1130,10 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh const z = @ctz(bitMask); const setBit = @as(u32, 1) << @intCast(z); bitMask &= ~setBit; - var block = self.chunk.data.getValue(chunk.getIndex(@intCast(x), @intCast(y), z)); + var block = self.chunk.data.getValueFromOwnerThread(chunk.getIndex(@intCast(x), @intCast(y), z)); if(depthFilteredViewThroughMask[x][y] & setBit != 0) block.typ = block.opaqueVariant(); if(block.viewThrough() and !block.alwaysViewThrough()) { // Needs to check the neighbor block - const neighborBlock = self.chunk.data.getValue(chunk.getIndex(@intCast(x), @intCast(y), z - 1)); + const neighborBlock = self.chunk.data.getValueFromOwnerThread(chunk.getIndex(@intCast(x), @intCast(y), z - 1)); if(block == neighborBlock) continue; } if(block.transparent()) { @@ -1157,10 +1157,10 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh const z = @ctz(bitMask); const setBit = @as(u32, 1) << @intCast(z); bitMask &= ~setBit; - var block = self.chunk.data.getValue(chunk.getIndex(@intCast(x), @intCast(y), z)); + var block = self.chunk.data.getValueFromOwnerThread(chunk.getIndex(@intCast(x), @intCast(y), z)); if(depthFilteredViewThroughMask[x][y] & setBit != 0) block.typ = block.opaqueVariant(); if(block.viewThrough() and !block.alwaysViewThrough()) { // Needs to check the neighbor block - const neighborBlock = self.chunk.data.getValue(chunk.getIndex(@intCast(x), @intCast(y), z + 1)); + const neighborBlock = self.chunk.data.getValueFromOwnerThread(chunk.getIndex(@intCast(x), @intCast(y), z + 1)); if(block == neighborBlock) continue; } if(block.transparent()) { @@ -1202,7 +1202,7 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh const z: u5 = @intCast(_z & chunk.chunkMask); var newBlock = _newBlock; self.mutex.lock(); - const oldBlock = self.chunk.data.getValue(chunk.getIndex(x, y, z)); + const oldBlock = self.chunk.data.getValueFromOwnerThread(chunk.getIndex(x, y, z)); if(oldBlock == newBlock) { if(newBlock.blockEntity()) |blockEntity| { @@ -1240,7 +1240,7 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh const index = chunk.getIndex(nnx, nny, nnz); neighborChunkMesh.mutex.lock(); - var neighborBlock = neighborChunkMesh.chunk.data.getValue(index); + var neighborBlock = neighborChunkMesh.chunk.data.getValueFromOwnerThread(index); if(neighborBlock.mode().dependsOnNeighbors and neighborBlock.mode().updateData(&neighborBlock, neighbor.reverse(), newBlock)) { neighborChunkMesh.chunk.data.setValue(index, neighborBlock); @@ -1254,7 +1254,7 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh } else { const index = chunk.getIndex(nx, ny, nz); self.mutex.lock(); - var neighborBlock = self.chunk.data.getValue(index); + var neighborBlock = self.chunk.data.getValueFromOwnerThread(index); 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); @@ -1409,9 +1409,9 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh const otherX = x +% neighbor.relX() & chunk.chunkMask; const otherY = y +% neighbor.relY() & chunk.chunkMask; const otherZ = z +% neighbor.relZ() & chunk.chunkMask; - var block = self.chunk.data.getValue(chunk.getIndex(x, y, z)); + var block = self.chunk.data.getValueFromOwnerThread(chunk.getIndex(x, y, z)); if(settings.leavesQuality == 0) block.typ = block.opaqueVariant(); - var otherBlock = neighborMesh.chunk.data.getValue(chunk.getIndex(otherX, otherY, otherZ)); + var otherBlock = neighborMesh.chunk.data.getValueFromOwnerThread(chunk.getIndex(otherX, otherY, otherZ)); if(settings.leavesQuality == 0) otherBlock.typ = otherBlock.opaqueVariant(); if(canBeSeenThroughOtherBlock(block, otherBlock, neighbor)) { if(block.transparent()) { @@ -1501,9 +1501,9 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh const otherX = (x +% neighbor.relX() +% offsetX >> 1) & chunk.chunkMask; const otherY = (y +% neighbor.relY() +% offsetY >> 1) & chunk.chunkMask; const otherZ = (z +% neighbor.relZ() +% offsetZ >> 1) & chunk.chunkMask; - var block = self.chunk.data.getValue(chunk.getIndex(x, y, z)); + var block = self.chunk.data.getValueFromOwnerThread(chunk.getIndex(x, y, z)); if(settings.leavesQuality == 0) block.typ = block.opaqueVariant(); - var otherBlock = neighborMesh.chunk.data.getValue(chunk.getIndex(otherX, otherY, otherZ)); + var otherBlock = neighborMesh.chunk.data.getValueFromOwnerThread(chunk.getIndex(otherX, otherY, otherZ)); if(settings.leavesQuality == 0) otherBlock.typ = otherBlock.opaqueVariant(); if(canBeSeenThroughOtherBlock(otherBlock, block, neighbor.reverse())) { if(otherBlock.transparent()) { diff --git a/src/renderer/lighting.zig b/src/renderer/lighting.zig index df9ef30d..27ae9b42 100644 --- a/src/renderer/lighting.zig +++ b/src/renderer/lighting.zig @@ -17,6 +17,21 @@ pub fn deinit() void { memoryPool.deinit(); } +const LightValue = packed struct(u32) { + r: u8, + g: u8, + b: u8, + pad: u8 = undefined, + + fn fromArray(arr: [3]u8) LightValue { + return .{.r = arr[0], .g = arr[1], .b = arr[2]}; + } + + fn toArray(self: LightValue) [3]u8 { + return .{self.r, self.g, self.b}; + } +}; + fn extractColor(in: u32) [3]u8 { return .{ @truncate(in >> 16), @@ -26,7 +41,7 @@ fn extractColor(in: u32) [3]u8 { } pub const ChannelChunk = struct { - data: main.utils.PaletteCompressedRegion([3]u8, chunk.chunkVolume), + data: main.utils.PaletteCompressedRegion(LightValue, chunk.chunkVolume), lock: main.utils.ReadWriteLock, ch: *chunk.Chunk, isSun: bool, @@ -66,9 +81,8 @@ pub const ChannelChunk = struct { }; pub fn getValue(self: *ChannelChunk, x: i32, y: i32, z: i32) [3]u8 { - self.lock.assertLockedRead(); const index = chunk.getIndex(x, y, z); - return self.data.getValue(index); + return self.data.getValueUnordered(index).toArray(); } fn calculateIncomingOcclusion(result: *[3]u8, block: blocks.Block, voxelSize: u31, neighbor: chunk.Neighbor) void { @@ -109,14 +123,14 @@ pub const ChannelChunk = struct { self.lock.lockWrite(); while(lightQueue.popFront()) |entry| { const index = chunk.getIndex(entry.x, entry.y, entry.z); - const oldValue: [3]u8 = self.data.getValue(index); + const oldValue: [3]u8 = self.data.getValueFromOwnerThread(index).toArray(); const newValue: [3]u8 = .{ @max(entry.value[0], oldValue[0]), @max(entry.value[1], oldValue[1]), @max(entry.value[2], oldValue[2]), }; if(newValue[0] == oldValue[0] and newValue[1] == oldValue[1] and newValue[2] == oldValue[2]) continue; - self.data.setValue(index, newValue); + self.data.setValue(index, .fromArray(newValue)); for(chunk.Neighbor.iterable) |neighbor| { if(neighbor.toInt() == entry.sourceDir) continue; const nx = entry.x + neighbor.relX(); @@ -128,14 +142,14 @@ pub const ChannelChunk = struct { result.value[1] -|= 8*|@as(u8, @intCast(self.ch.pos.voxelSize)); result.value[2] -|= 8*|@as(u8, @intCast(self.ch.pos.voxelSize)); } - calculateOutgoingOcclusion(&result.value, self.ch.data.getValue(index), self.ch.pos.voxelSize, neighbor); + calculateOutgoingOcclusion(&result.value, self.ch.data.getValueFromOwnerThread(index), self.ch.pos.voxelSize, neighbor); if(result.value[0] == 0 and result.value[1] == 0 and result.value[2] == 0) continue; if(nx < 0 or nx >= chunk.chunkSize or ny < 0 or ny >= chunk.chunkSize or nz < 0 or nz >= chunk.chunkSize) { neighborLists[neighbor.toInt()].append(main.stackAllocator, result); continue; } const neighborIndex = chunk.getIndex(nx, ny, nz); - calculateIncomingOcclusion(&result.value, self.ch.data.getValue(neighborIndex), self.ch.pos.voxelSize, neighbor.reverse()); + calculateIncomingOcclusion(&result.value, self.ch.data.getValueFromOwnerThread(neighborIndex), self.ch.pos.voxelSize, neighbor.reverse()); if(result.value[0] != 0 or result.value[1] != 0 or result.value[2] != 0) lightQueue.pushBack(result); } } @@ -175,7 +189,7 @@ pub const ChannelChunk = struct { self.lock.lockWrite(); while(lightQueue.popFront()) |entry| { const index = chunk.getIndex(entry.x, entry.y, entry.z); - const oldValue: [3]u8 = self.data.getValue(index); + const oldValue: [3]u8 = self.data.getValueFromOwnerThread(index).toArray(); var activeValue: @Vector(3, bool) = @bitCast(entry.activeValue); var append: bool = false; if(activeValue[0] and entry.value[0] != oldValue[0]) { @@ -209,7 +223,7 @@ pub const ChannelChunk = struct { if(activeValue[0]) insertValue[0] = 0; if(activeValue[1]) insertValue[1] = 0; if(activeValue[2]) insertValue[2] = 0; - self.data.setValue(index, insertValue); + self.data.setValue(index, .fromArray(insertValue)); for(chunk.Neighbor.iterable) |neighbor| { if(neighbor.toInt() == entry.sourceDir) continue; const nx = entry.x + neighbor.relX(); @@ -221,13 +235,13 @@ pub const ChannelChunk = struct { result.value[1] -|= 8*|@as(u8, @intCast(self.ch.pos.voxelSize)); result.value[2] -|= 8*|@as(u8, @intCast(self.ch.pos.voxelSize)); } - calculateOutgoingOcclusion(&result.value, self.ch.data.getValue(index), self.ch.pos.voxelSize, neighbor); + calculateOutgoingOcclusion(&result.value, self.ch.data.getValueFromOwnerThread(index), self.ch.pos.voxelSize, neighbor); if(nx < 0 or nx >= chunk.chunkSize or ny < 0 or ny >= chunk.chunkSize or nz < 0 or nz >= chunk.chunkSize) { neighborLists[neighbor.toInt()].append(main.stackAllocator, result); continue; } const neighborIndex = chunk.getIndex(nx, ny, nz); - calculateIncomingOcclusion(&result.value, self.ch.data.getValue(neighborIndex), self.ch.pos.voxelSize, neighbor.reverse()); + calculateIncomingOcclusion(&result.value, self.ch.data.getValueFromOwnerThread(neighborIndex), self.ch.pos.voxelSize, neighbor.reverse()); lightQueue.pushBack(result); } } @@ -251,7 +265,7 @@ pub const ChannelChunk = struct { for(lights) |entry| { const index = chunk.getIndex(entry.x, entry.y, entry.z); var result = entry; - calculateIncomingOcclusion(&result.value, self.ch.data.getValue(index), self.ch.pos.voxelSize, @enumFromInt(entry.sourceDir)); + calculateIncomingOcclusion(&result.value, self.ch.data.getValueFromOwnerThread(index), self.ch.pos.voxelSize, @enumFromInt(entry.sourceDir)); if(result.value[0] != 0 or result.value[1] != 0 or result.value[2] != 0) lightQueue.pushBack(result); } self.propagateDirect(lightQueue, lightRefreshList); @@ -262,7 +276,7 @@ pub const ChannelChunk = struct { for(lights) |entry| { const index = chunk.getIndex(entry.x, entry.y, entry.z); var result = entry; - calculateIncomingOcclusion(&result.value, self.ch.data.getValue(index), self.ch.pos.voxelSize, @enumFromInt(entry.sourceDir)); + calculateIncomingOcclusion(&result.value, self.ch.data.getValueFromOwnerThread(index), self.ch.pos.voxelSize, @enumFromInt(entry.sourceDir)); lightQueue.pushBack(result); } return self.propagateDestructive(lightQueue, constructiveEntries, false, lightRefreshList); @@ -276,7 +290,7 @@ pub const ChannelChunk = struct { if(self.isSun) { lightQueue.pushBack(.{.x = @intCast(pos[0]), .y = @intCast(pos[1]), .z = @intCast(pos[2]), .value = .{255, 255, 255}, .sourceDir = 6, .activeValue = 0b111}); } else { - lightQueue.pushBack(.{.x = @intCast(pos[0]), .y = @intCast(pos[1]), .z = @intCast(pos[2]), .value = extractColor(self.ch.data.getValue(index).light()), .sourceDir = 6, .activeValue = 0b111}); + lightQueue.pushBack(.{.x = @intCast(pos[0]), .y = @intCast(pos[1]), .z = @intCast(pos[2]), .value = extractColor(self.ch.data.getValueFromOwnerThread(index).light()), .sourceDir = 6, .activeValue = 0b111}); } } if(checkNeighbors) { @@ -311,15 +325,15 @@ pub const ChannelChunk = struct { defer neighborLightChunk.lock.unlockRead(); const index = chunk.getIndex(x, y, z); const neighborIndex = chunk.getIndex(otherX, otherY, otherZ); - var value: [3]u8 = neighborLightChunk.data.getValue(neighborIndex); + var value: [3]u8 = neighborLightChunk.data.getValueFromOwnerThread(neighborIndex).toArray(); if(!self.isSun or neighbor != .dirUp or value[0] != 255 or value[1] != 255 or value[2] != 255) { value[0] -|= 8*|@as(u8, @intCast(self.ch.pos.voxelSize)); value[1] -|= 8*|@as(u8, @intCast(self.ch.pos.voxelSize)); value[2] -|= 8*|@as(u8, @intCast(self.ch.pos.voxelSize)); } - calculateOutgoingOcclusion(&value, self.ch.data.getValue(neighborIndex), self.ch.pos.voxelSize, neighbor); + calculateOutgoingOcclusion(&value, self.ch.data.getValueFromOwnerThread(neighborIndex), self.ch.pos.voxelSize, neighbor); if(value[0] == 0 and value[1] == 0 and value[2] == 0) continue; - calculateIncomingOcclusion(&value, self.ch.data.getValue(index), self.ch.pos.voxelSize, neighbor.reverse()); + calculateIncomingOcclusion(&value, self.ch.data.getValueFromOwnerThread(index), self.ch.pos.voxelSize, neighbor.reverse()); if(value[0] != 0 or value[1] != 0 or value[2] != 0) lightQueue.pushBack(.{.x = @intCast(x), .y = @intCast(y), .z = @intCast(z), .value = value, .sourceDir = neighbor.toInt(), .activeValue = 0b111}); } } @@ -335,7 +349,7 @@ pub const ChannelChunk = struct { self.data.deinit(); self.data.init(); } - self.data.palette[0] = .{255, 255, 255}; + self.data.palette.raw.toSlice()[0].store(.fromArray(@splat(255)), .unordered); self.lock.unlockWrite(); const val = 255 -| 8*|@as(u8, @intCast(self.ch.pos.voxelSize)); var lightQueue = main.utils.CircularBufferQueue(Entry).init(main.stackAllocator, 1 << 12); @@ -381,7 +395,7 @@ pub const ChannelChunk = struct { self.lock.lockRead(); for(lights) |pos| { const index = chunk.getIndex(pos[0], pos[1], pos[2]); - lightQueue.pushBack(.{.x = @intCast(pos[0]), .y = @intCast(pos[1]), .z = @intCast(pos[2]), .value = self.data.getValue(index), .sourceDir = 6, .activeValue = 0b111}); + lightQueue.pushBack(.{.x = @intCast(pos[0]), .y = @intCast(pos[1]), .z = @intCast(pos[2]), .value = self.data.getValueFromOwnerThread(index).toArray(), .sourceDir = 6, .activeValue = 0b111}); } self.lock.unlockRead(); var constructiveEntries: main.ListUnmanaged(ChunkEntries) = .{}; @@ -398,15 +412,15 @@ pub const ChannelChunk = struct { channelChunk.lock.lockWrite(); for(entryList.items) |entry| { const index = chunk.getIndex(entry.x, entry.y, entry.z); - var value = channelChunk.data.getValue(index); - const light = if(self.isSun) .{0, 0, 0} else extractColor(channelChunk.ch.data.getValue(index).light()); + var value = channelChunk.data.getValueFromOwnerThread(index).toArray(); + const light = if(self.isSun) .{0, 0, 0} else extractColor(channelChunk.ch.data.getValueFromOwnerThread(index).light()); value = .{ @max(value[0], light[0]), @max(value[1], light[1]), @max(value[2], light[2]), }; if(value[0] == 0 and value[1] == 0 and value[2] == 0) continue; - channelChunk.data.setValue(index, .{0, 0, 0}); + channelChunk.data.setValue(index, .fromArray(.{0, 0, 0})); lightQueue.pushBack(.{.x = entry.x, .y = entry.y, .z = entry.z, .value = value, .sourceDir = 6, .activeValue = 0b111}); } channelChunk.lock.unlockWrite(); diff --git a/src/server/storage.zig b/src/server/storage.zig index bcba8115..6fde3961 100644 --- a/src/server/storage.zig +++ b/src/server/storage.zig @@ -284,7 +284,7 @@ pub const ChunkCompression = struct { // MARK: ChunkCompression fn compressBlockData(ch: *chunk.Chunk, allowLossy: bool, writer: *BinaryWriter) void { if(ch.data.paletteLength == 1) { writer.writeEnum(ChunkCompressionAlgo, .uniform); - writer.writeInt(u32, ch.data.palette[0].toInt()); + writer.writeInt(u32, ch.data.getValueFromOwnerThread(0).toInt()); return; } if(ch.data.paletteLength < 256) { @@ -293,7 +293,7 @@ pub const ChunkCompression = struct { // MARK: ChunkCompression for(0..chunk.chunkVolume) |i| { uncompressedData[i] = @intCast(ch.data.data.getValue(i)); if(allowLossy) { - const block = ch.data.palette[uncompressedData[i]]; + const block = ch.data.palette.raw.toSlice()[uncompressedData[i]].raw; const model = main.blocks.meshes.model(block).model(); const occluder = model.allNeighborsOccluded and !block.viewThrough(); if(occluder) { @@ -326,7 +326,7 @@ pub const ChunkCompression = struct { // MARK: ChunkCompression writer.writeInt(u8, @intCast(ch.data.paletteLength)); for(0..ch.data.paletteLength) |i| { - writer.writeInt(u32, ch.data.palette[i].toInt()); + writer.writeInt(u32, ch.data.palette.raw.toSlice()[i].raw.toInt()); } writer.writeVarInt(usize, compressedData.len); writer.writeSlice(compressedData); @@ -336,7 +336,7 @@ pub const ChunkCompression = struct { // MARK: ChunkCompression defer uncompressedWriter.deinit(); for(0..chunk.chunkVolume) |i| { - uncompressedWriter.writeInt(u32, ch.data.getValue(i).toInt()); + uncompressedWriter.writeInt(u32, ch.data.getValueFromOwnerThread(i).toInt()); } const compressedData = main.utils.Compression.deflate(main.stackAllocator, uncompressedWriter.data.items, .default); defer main.stackAllocator.free(compressedData); @@ -375,7 +375,7 @@ pub const ChunkCompression = struct { // MARK: ChunkCompression ch.data.initCapacity(paletteLength); for(0..paletteLength) |i| { - ch.data.palette[i] = main.blocks.Block.fromInt(try reader.readInt(u32)); + ch.data.palette.raw.toSlice()[i] = .init(main.blocks.Block.fromInt(try reader.readInt(u32))); } const decompressedData = main.stackAllocator.alloc(u8, chunk.chunkVolume); @@ -392,7 +392,7 @@ pub const ChunkCompression = struct { // MARK: ChunkCompression } }, .uniform => { - ch.data.palette[0] = main.blocks.Block.fromInt(try reader.readInt(u32)); + ch.data.palette.raw.toSlice()[0] = .init(main.blocks.Block.fromInt(try reader.readInt(u32))); }, } } @@ -409,7 +409,7 @@ pub const ChunkCompression = struct { // MARK: ChunkCompression while(iterator.next()) |entry| { const index = entry.key_ptr.*; const blockEntityIndex = entry.value_ptr.*; - const block = ch.data.getValue(index); + const block = ch.data.getValueFromOwnerThread(index); const blockEntity = block.blockEntity() orelse continue; var tempWriter = BinaryWriter.init(main.stackAllocator); @@ -441,7 +441,7 @@ pub const ChunkCompression = struct { // MARK: ChunkCompression const dataLength = try reader.readVarInt(usize); const blockEntityData = try reader.readSlice(dataLength); - const block = ch.data.getValue(index); + const block = ch.data.getValueFromOwnerThread(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; diff --git a/src/server/terrain/chunkgen/TerrainGenerator.zig b/src/server/terrain/chunkgen/TerrainGenerator.zig index a32366cc..3eec0e7e 100644 --- a/src/server/terrain/chunkgen/TerrainGenerator.zig +++ b/src/server/terrain/chunkgen/TerrainGenerator.zig @@ -47,13 +47,13 @@ pub fn generate(worldSeed: u64, chunk: *main.chunk.ServerChunk, caveMap: CaveMap if(minHeight > chunk.super.pos.wz +| chunk.super.width) { chunk.super.data.deinit(); chunk.super.data.init(); - chunk.super.data.palette[0] = stone; + chunk.super.data.palette.raw.toSlice()[0] = .init(stone); return; } if(maxHeight < chunk.super.pos.wz) { chunk.super.data.deinit(); chunk.super.data.init(); - chunk.super.data.palette[0] = air; + chunk.super.data.palette.raw.toSlice()[0] = .init(air); return; } } diff --git a/src/server/world.zig b/src/server/world.zig index 3dba427f..a296b1c5 100644 --- a/src/server/world.zig +++ b/src/server/world.zig @@ -328,8 +328,8 @@ const ChunkManager = struct { // MARK: ChunkManager generator.generate(server.world.?.seed ^ generator.generatorSeed, ch, caveMap, biomeMap); } if(pos.voxelSize != 1) { // Generate LOD replacements - for(ch.super.data.palette[0..ch.super.data.paletteLength]) |*block| { - block.typ = block.lodReplacement(); + for(ch.super.data.palette.raw.toSlice()[0..ch.super.data.paletteLength]) |*block| { + block.store(.{.typ = block.raw.lodReplacement(), .data = block.raw.data}, .unordered); } } return ch; diff --git a/src/utils.zig b/src/utils.zig index 8c12a40b..05e9e5a2 100644 --- a/src/utils.zig +++ b/src/utils.zig @@ -1027,7 +1027,7 @@ pub fn DynamicPackedIntArray(size: comptime_int) type { // MARK: DynamicPackedIn } pub fn deinit(self: *Self) void { - main.heap.GarbageCollection.deferredFreeSlice(dynamicIntArrayAllocator.allocator(), Atomic(u32), self.content.swap(@bitCast(@as(u64, 0)), .monotonic).toSlice()); + main.heap.GarbageCollection.deferredFreeSlice(dynamicIntArrayAllocator.allocator(), self.content.swap(@bitCast(@as(u64, 0)), .monotonic).toSlice()); } inline fn bitInterleave(bits: comptime_int, source: u32) u32 { @@ -1060,7 +1060,7 @@ pub fn DynamicPackedIntArray(size: comptime_int) type { // MARK: DynamicPackedIn }, else => unreachable, } - main.heap.GarbageCollection.deferredFreeSlice(dynamicIntArrayAllocator.allocator(), Atomic(u32), oldContent.toSlice()); + main.heap.GarbageCollection.deferredFreeSlice(dynamicIntArrayAllocator.allocator(), oldContent.toSlice()); self.content.store(newContent, .release); } @@ -1110,7 +1110,7 @@ pub fn DynamicPackedIntArray(size: comptime_int) type { // MARK: DynamicPackedIn pub fn PaletteCompressedRegion(T: type, size: comptime_int) type { // MARK: PaletteCompressedRegion return struct { data: DynamicPackedIntArray(size) = .{}, - palette: []T, + palette: Atomic(PowerOfTwoSlice(Atomic(T))), paletteOccupancy: []u32, paletteLength: u32, activePaletteEntries: u32, @@ -1119,12 +1119,12 @@ pub fn PaletteCompressedRegion(T: type, size: comptime_int) type { // MARK: Pale pub fn init(self: *Self) void { self.* = .{ - .palette = main.globalAllocator.alloc(T, 1), + .palette = .init(.fromSlice(main.globalAllocator.alignedAlloc(Atomic(T), .@"64", 1))), .paletteOccupancy = main.globalAllocator.alloc(u32, 1), .paletteLength = 1, .activePaletteEntries = 1, }; - self.palette[0] = std.mem.zeroes(T); + self.palette.raw.toSlice()[0] = .init(std.mem.zeroes(T)); self.paletteOccupancy[0] = size; } @@ -1134,18 +1134,18 @@ pub fn PaletteCompressedRegion(T: type, size: comptime_int) type { // MARK: Pale const bufferLength = @as(u32, 1) << bitSize; self.* = .{ .data = DynamicPackedIntArray(size).initCapacity(bitSize), - .palette = main.globalAllocator.alloc(T, bufferLength), + .palette = .init(.fromSlice(main.globalAllocator.alignedAlloc(Atomic(T), .@"64", bufferLength))), .paletteOccupancy = main.globalAllocator.alloc(u32, bufferLength), .paletteLength = paletteLength, .activePaletteEntries = 1, }; - self.palette[0] = std.mem.zeroes(T); + self.palette.raw.toSlice()[0] = .init(std.mem.zeroes(T)); self.paletteOccupancy[0] = size; } pub fn deinit(self: *Self) void { self.data.deinit(); - main.globalAllocator.free(self.palette); + main.heap.GarbageCollection.deferredFreeSlice(main.globalAllocator, self.palette.raw.toSlice()); main.globalAllocator.free(self.paletteOccupancy); } @@ -1156,29 +1156,40 @@ pub fn PaletteCompressedRegion(T: type, size: comptime_int) type { // MARK: Pale return @as(u5, 1) << logLog; } - pub fn getValue(self: *const Self, i: usize) T { - return self.palette[self.data.getValue(i)]; + /// Uses the Java memory model to get a value. + /// In case of congestion a random value may get returned. + pub fn getValueUnordered(self: *const Self, i: usize) T { + const paletteIndex = self.data.getValue(i); + const palette = self.palette.load(.unordered).toSlice(); + if(paletteIndex >= palette.len) return undefined; + return palette[paletteIndex].load(.unordered); + } + + pub fn getValueFromOwnerThread(self: *const Self, i: usize) T { + return self.palette.raw.toSlice()[self.data.getValue(i)].raw; } fn getOrInsertPaletteIndex(noalias self: *Self, val: T) u32 { - std.debug.assert(self.paletteLength <= self.palette.len); + std.debug.assert(self.paletteLength <= self.palette.raw.toSlice().len); var paletteIndex: u32 = 0; while(paletteIndex < self.paletteLength) : (paletteIndex += 1) { // TODO: There got to be a faster way to do this. Either using SIMD or using a cache or hashmap. - if(std.meta.eql(self.palette[paletteIndex], val)) { + if(std.meta.eql(self.palette.raw.toSlice()[paletteIndex].raw, val)) { break; } } if(paletteIndex == self.paletteLength) { - if(self.paletteLength == self.palette.len) { + if(self.paletteLength == self.palette.raw.toSlice().len) { self.data.resizeOnce(); - self.palette = main.globalAllocator.realloc(self.palette, @as(usize, 1) << self.data.bitSizeUnsafe()); + const newPalette = main.globalAllocator.alignedAlloc(Atomic(T), .@"64", @as(usize, 1) << self.data.bitSizeUnsafe()); + @memcpy(newPalette[0..self.palette.raw.toSlice().len], self.palette.raw.toSlice()); + main.heap.GarbageCollection.deferredFreeSlice(main.globalAllocator, self.palette.swap(.fromSlice(newPalette), .monotonic).toSlice()); const oldLen = self.paletteOccupancy.len; self.paletteOccupancy = main.globalAllocator.realloc(self.paletteOccupancy, @as(usize, 1) << self.data.bitSizeUnsafe()); @memset(self.paletteOccupancy[oldLen..], 0); } - self.palette[paletteIndex] = val; + self.palette.raw.toSlice()[paletteIndex].store(val, .unordered); self.paletteLength += 1; - std.debug.assert(self.paletteLength <= self.palette.len); + std.debug.assert(self.paletteLength <= self.palette.raw.toSlice().len); } return paletteIndex; } @@ -1247,7 +1258,7 @@ pub fn PaletteCompressedRegion(T: type, size: comptime_int) type { // MARK: Pale if(len == i) break :outer; } paletteMap[len] = i; - self.palette[i] = self.palette[len]; + self.palette.raw.toSlice()[i].store(self.palette.raw.toSlice()[len].raw, .unordered); self.paletteOccupancy[i] = self.paletteOccupancy[len]; self.paletteOccupancy[len] = 0; } @@ -1259,7 +1270,9 @@ pub fn PaletteCompressedRegion(T: type, size: comptime_int) type { // MARK: Pale newData.content.raw = self.data.content.swap(newData.content.raw, .release); newData.deinit(); self.paletteLength = self.activePaletteEntries; - self.palette = main.globalAllocator.realloc(self.palette, @as(usize, 1) << self.data.bitSizeUnsafe()); + const newPalette = main.globalAllocator.alignedAlloc(Atomic(T), .@"64", @as(usize, 1) << self.data.bitSizeUnsafe()); + @memcpy(newPalette, self.palette.raw.toSlice()[0..newPalette.len]); + main.heap.GarbageCollection.deferredFreeSlice(main.globalAllocator, self.palette.swap(.fromSlice(newPalette), .monotonic).toSlice()); self.paletteOccupancy = main.globalAllocator.realloc(self.paletteOccupancy, @as(usize, 1) << self.data.bitSizeUnsafe()); } }; diff --git a/src/utils/heap.zig b/src/utils/heap.zig index 2bd2c928..bf28bf5c 100644 --- a/src/utils/heap.zig +++ b/src/utils/heap.zig @@ -585,8 +585,9 @@ pub const GarbageCollection = struct { // MARK: GarbageCollection freeFunction: *const fn(*anyopaque) void, }; const FreeSliceItem = struct { - slice: []const u8, + slice: []u8, allocator: NeverFailingAllocator, + alignment: std.mem.Alignment, }; threadlocal var lists: [4]main.ListUnmanaged(FreeItem) = undefined; threadlocal var sliceLists: [4]main.ListUnmanaged(FreeSliceItem) = undefined; @@ -621,7 +622,7 @@ pub const GarbageCollection = struct { // MARK: GarbageCollection fn freeItemsFromSliceList(list: *main.ListUnmanaged(FreeSliceItem)) void { while(list.popOrNull()) |item| { - item.allocator.free(item.slice); + item.allocator.rawFree(item.slice, item.alignment, @returnAddress()); } } @@ -686,10 +687,13 @@ pub const GarbageCollection = struct { // MARK: GarbageCollection lists[threadCycle].append(main.globalAllocator, item); } - pub fn deferredFreeSlice(allocator: NeverFailingAllocator, comptime T: type, items: []T) void { + pub fn deferredFreeSlice(allocator: NeverFailingAllocator, items: anytype) void { + if(items.len == 0) return; + const Slice = @typeInfo(@TypeOf(items)).pointer; sliceLists[threadCycle].append(main.globalAllocator, .{ - .slice = std.mem.sliceAsBytes(items), + .slice = @constCast(std.mem.sliceAsBytes(items)), .allocator = allocator, + .alignment = .fromByteUnits(Slice.alignment), }); }