diff --git a/mods/cubyz/rotation/log.zig b/mods/cubyz/rotation/log.zig index fc298e35..b82d4e5b 100644 --- a/mods/cubyz/rotation/log.zig +++ b/mods/cubyz/rotation/log.zig @@ -212,7 +212,7 @@ pub fn generateData( } const sidePos = pos + side.relPos(); - const sideBlock = main.renderer.mesh_storage.getBlock(sidePos[0], sidePos[1], sidePos[2]) orelse continue; + const sideBlock = main.renderer.mesh_storage.getBlockFromRenderThread(sidePos[0], sidePos[1], sidePos[2]) orelse continue; const canConnectToSide = currentBlock.mode() == sideBlock.mode() and currentBlock.modeData() == sideBlock.modeData(); if(canConnectToSide) { diff --git a/src/block_entity.zig b/src/block_entity.zig index c96fbe80..ef6ac4ee 100644 --- a/src/block_entity.zig +++ b/src/block_entity.zig @@ -401,8 +401,7 @@ pub const BlockEntityTypes = struct { pub fn updateTextFromClient(pos: Vec3i, newText: []const u8) void { { - const mesh = main.renderer.mesh_storage.getMeshAndIncreaseRefCount(.initFromWorldPos(pos, 1)) orelse return; - defer mesh.decreaseRefCount(); + const mesh = main.renderer.mesh_storage.getMesh(.initFromWorldPos(pos, 1)) orelse return; mesh.mutex.lock(); defer mesh.mutex.unlock(); const index = mesh.chunk.getLocalBlockIndex(pos); @@ -478,8 +477,7 @@ pub const BlockEntityTypes = struct { signData.renderedTexture.?.bindTo(0); c.glUniform1i(uniforms.quadIndex, @intFromEnum(quad)); - const mesh = main.renderer.mesh_storage.getMeshAndIncreaseRefCount(main.chunk.ChunkPosition.initFromWorldPos(signData.blockPos, 1)) orelse continue :outer; - defer mesh.decreaseRefCount(); + const mesh = main.renderer.mesh_storage.getMesh(main.chunk.ChunkPosition.initFromWorldPos(signData.blockPos, 1)) orelse continue :outer; mesh.lightingData[0].lock.lockRead(); defer mesh.lightingData[0].lock.unlockRead(); mesh.lightingData[1].lock.lockRead(); diff --git a/src/chunk.zig b/src/chunk.zig index 9f54a754..503bb860 100644 --- a/src/chunk.zig +++ b/src/chunk.zig @@ -190,6 +190,8 @@ pub const ChunkPosition = struct { // MARK: ChunkPosition return self.equals(notNull); } return false; + } else if(@TypeOf(other) == ChunkPosition) { + return self.wx == other.wx and self.wy == other.wy and self.wz == other.wz and self.voxelSize == other.voxelSize; } else if(@TypeOf(other.*) == ServerChunk) { return self.wx == other.super.pos.wx and self.wy == other.super.pos.wy and self.wz == other.super.pos.wz and self.voxelSize == other.super.pos.voxelSize; } else if(@typeInfo(@TypeOf(other)) == .pointer) { diff --git a/src/game.zig b/src/game.zig index 2969e5da..b5b33220 100644 --- a/src/game.zig +++ b/src/game.zig @@ -260,7 +260,7 @@ pub const collision = struct { while(y <= maxY) : (y += 1) { var z: i32 = maxZ; while(z >= minZ) : (z -= 1) { - const _block = if(side == .client) main.renderer.mesh_storage.getBlock(x, y, z) else main.server.world.?.getBlock(x, y, z); + const _block = if(side == .client) main.renderer.mesh_storage.getBlockFromRenderThread(x, y, z) else main.server.world.?.getBlock(x, y, z); if(_block) |block| { if(collideWithBlock(block, x, y, z, boundingBoxCenter, fullBoundingBoxExtent, directionVector)) |res| { if(res.dist < minDistance) { @@ -298,7 +298,7 @@ pub const collision = struct { while(x <= maxX) : (x += 1) { var y = minY; while(y <= maxY) : (y += 1) { - const _block = if(side == .client) main.renderer.mesh_storage.getBlock(x, y, z) else main.server.world.?.getBlock(x, y, z); + const _block = if(side == .client) main.renderer.mesh_storage.getBlockFromRenderThread(x, y, z) else main.server.world.?.getBlock(x, y, z); if(_block) |block| { const blockPos: Vec3d = .{@floatFromInt(x), @floatFromInt(y), @floatFromInt(z)}; @@ -368,7 +368,7 @@ pub const collision = struct { while(y <= maxY) : (y += 1) { var z: i32 = maxZ; while(z >= minZ) : (z -= 1) { - const _block = if(side == .client) main.renderer.mesh_storage.getBlock(x, y, z) else main.server.world.?.getBlock(x, y, z); + const _block = if(side == .client) main.renderer.mesh_storage.getBlockFromRenderThread(x, y, z) else main.server.world.?.getBlock(x, y, z); const totalBox: Box = .{ .min = @floatFromInt(Vec3i{x, y, z}), .max = @floatFromInt(Vec3i{x + 1, y + 1, z + 1}), @@ -480,7 +480,7 @@ pub const collision = struct { var posZ: i32 = minZ; while(posZ <= maxZ) : (posZ += 1) { const block: ?Block = - if(side == .client) main.renderer.mesh_storage.getBlock(posX, posY, posZ) else main.server.world.?.getBlock(posX, posY, posZ); + if(side == .client) main.renderer.mesh_storage.getBlockFromRenderThread(posX, posY, posZ) else main.server.world.?.getBlock(posX, posY, posZ); if(block == null or block.?.touchFunction() == null) continue; const touchX: bool = isBlockIntersecting(block.?, posX, posY, posZ, center, extentX); @@ -643,9 +643,9 @@ pub const Player = struct { // MARK: Player pub fn placeBlock() void { if(main.renderer.MeshSelection.selectedBlockPos) |blockPos| { if(!main.KeyBoard.key("shift").pressed) { - if(main.renderer.mesh_storage.triggerOnInteractBlock(blockPos[0], blockPos[1], blockPos[2]) == .handled) return; + if(main.renderer.mesh_storage.triggerOnInteractBlockFromRenderThread(blockPos[0], blockPos[1], blockPos[2]) == .handled) return; } - const block = main.renderer.mesh_storage.getBlock(blockPos[0], blockPos[1], blockPos[2]) orelse main.blocks.Block{.typ = 0, .data = 0}; + const block = main.renderer.mesh_storage.getBlockFromRenderThread(blockPos[0], blockPos[1], blockPos[2]) orelse main.blocks.Block{.typ = 0, .data = 0}; const gui = block.gui(); if(gui.len != 0 and !main.KeyBoard.key("shift").pressed) { main.gui.openWindow(gui); @@ -677,7 +677,7 @@ pub const Player = struct { // MARK: Player pub fn acquireSelectedBlock() void { if(main.renderer.MeshSelection.selectedBlockPos) |selectedPos| { - const block = main.renderer.mesh_storage.getBlock(selectedPos[0], selectedPos[1], selectedPos[2]) orelse return; + const block = main.renderer.mesh_storage.getBlockFromRenderThread(selectedPos[0], selectedPos[1], selectedPos[2]) orelse return; const item: items.Item = for(0..items.itemListSize) |idx| { const baseItem: main.items.BaseItemIndex = @enumFromInt(idx); @@ -919,7 +919,7 @@ pub fn update(deltaTime: f64) void { // MARK: update() const airFrictionCoefficient = gravity/airTerminalVelocity; // λ = a/v in equillibrium const playerDensity = 1.2; var move: Vec3d = .{0, 0, 0}; - if(main.renderer.mesh_storage.getBlock(@intFromFloat(@floor(Player.super.pos[0])), @intFromFloat(@floor(Player.super.pos[1])), @intFromFloat(@floor(Player.super.pos[2]))) != null) { + if(main.renderer.mesh_storage.getBlockFromRenderThread(@intFromFloat(@floor(Player.super.pos[0])), @intFromFloat(@floor(Player.super.pos[1])), @intFromFloat(@floor(Player.super.pos[2]))) != null) { const volumeProperties = collision.calculateVolumeProperties(.client, Player.super.pos, Player.outerBoundingBox, .{.density = 0.001, .terminalVelocity = airTerminalVelocity, .mobility = 1.0}); const effectiveGravity = gravity*(playerDensity - volumeProperties.density)/playerDensity; const volumeFrictionCoeffecient: f32 = @floatCast(gravity/volumeProperties.terminalVelocity); diff --git a/src/main.zig b/src/main.zig index 094b5159..0fe846c8 100644 --- a/src/main.zig +++ b/src/main.zig @@ -55,10 +55,12 @@ pub fn initThreadLocals() void { seed = @bitCast(@as(i64, @truncate(std.time.nanoTimestamp()))); stackAllocatorBase = heap.StackAllocator.init(globalAllocator, 1 << 23); stackAllocator = stackAllocatorBase.allocator(); + heap.GarbageCollection.addThread(); } pub fn deinitThreadLocals() void { stackAllocatorBase.deinit(); + heap.GarbageCollection.removeThread(); } fn cacheStringImpl(comptime len: usize, comptime str: [len]u8) []const u8 { @@ -549,6 +551,7 @@ pub fn main() void { // MARK: main() defer if(global_gpa.deinit() == .leak) { std.log.err("Memory leak", .{}); }; + defer heap.GarbageCollection.assertAllThreadsStopped(); initThreadLocals(); defer deinitThreadLocals(); @@ -672,6 +675,7 @@ pub fn main() void { // MARK: main() audio.setMusic("cubyz:cubyz"); while(c.glfwWindowShouldClose(Window.window) == 0) { + heap.GarbageCollection.syncPoint(); const isHidden = c.glfwGetWindowAttrib(Window.window, c.GLFW_ICONIFIED) == c.GLFW_TRUE; if(!isHidden) { c.glfwSwapBuffers(Window.window); diff --git a/src/network.zig b/src/network.zig index 98f3f170..142dfb07 100644 --- a/src/network.zig +++ b/src/network.zig @@ -587,6 +587,7 @@ pub const ConnectionManager = struct { // MARK: ConnectionManager var lastTime: i64 = networkTimestamp(); while(self.running.load(.monotonic)) { + main.heap.GarbageCollection.syncPoint(); self.waitingToFinishReceive.broadcast(); var source: Address = undefined; if(self.socket.receive(&self.receiveBuffer, 1, &source)) |data| { @@ -1308,8 +1309,7 @@ pub const Protocols = struct { } pub fn sendClientDataUpdateToServer(conn: *Connection, pos: Vec3i) void { - const mesh = main.renderer.mesh_storage.getMeshAndIncreaseRefCount(.initFromWorldPos(pos, 1)) orelse return; - defer mesh.decreaseRefCount(); + const mesh = main.renderer.mesh_storage.getMesh(.initFromWorldPos(pos, 1)) orelse return; mesh.mutex.lock(); defer mesh.mutex.unlock(); const index = mesh.chunk.getLocalBlockIndex(pos); diff --git a/src/renderer.zig b/src/renderer.zig index 923acad3..f7dfa7a2 100644 --- a/src/renderer.zig +++ b/src/renderer.zig @@ -287,7 +287,7 @@ pub fn renderWorld(world: *World, ambientLight: Vec3f, skyColor: Vec3f, playerPo worldFrameBuffer.bindTexture(c.GL_TEXTURE3); - const playerBlock = mesh_storage.getBlockFromAnyLod(@intFromFloat(@floor(playerPos[0])), @intFromFloat(@floor(playerPos[1])), @intFromFloat(@floor(playerPos[2]))); + const playerBlock = mesh_storage.getBlockFromAnyLodFromRenderThread(@intFromFloat(@floor(playerPos[0])), @intFromFloat(@floor(playerPos[1])), @intFromFloat(@floor(playerPos[2]))); if(settings.bloom) { Bloom.render(lastWidth, lastHeight, playerBlock, playerPos, game.camera.viewMatrix); @@ -915,7 +915,7 @@ pub const MeshSelection = struct { // MARK: MeshSelection selectedBlockPos = null; while(total_tMax < closestDistance) { - const block = mesh_storage.getBlock(voxelPos[0], voxelPos[1], voxelPos[2]) orelse break; + const block = mesh_storage.getBlockFromRenderThread(voxelPos[0], voxelPos[1], voxelPos[2]) orelse break; if(block.typ != 0) blk: { const fluidPlaceable = item != null and item.? == .baseItem and item.?.baseItem.hasTag(.fluidPlaceable); for(block.blockTags()) |tag| { @@ -971,7 +971,7 @@ pub const MeshSelection = struct { // MARK: MeshSelection pub fn placeBlock(inventory: main.items.Inventory, slot: u32) void { if(selectedBlockPos) |selectedPos| { - var oldBlock = mesh_storage.getBlock(selectedPos[0], selectedPos[1], selectedPos[2]) orelse return; + var oldBlock = mesh_storage.getBlockFromRenderThread(selectedPos[0], selectedPos[1], selectedPos[2]) orelse return; var block = oldBlock; if(inventory.getItem(slot)) |item| { switch(item) { @@ -999,7 +999,7 @@ pub const MeshSelection = struct { // MARK: MeshSelection neighborDir = selectedPos - posBeforeBlock; const relPos: Vec3f = @floatCast(lastPos - @as(Vec3d, @floatFromInt(neighborPos))); const neighborBlock = block; - oldBlock = mesh_storage.getBlock(neighborPos[0], neighborPos[1], neighborPos[2]) orelse return; + oldBlock = mesh_storage.getBlockFromRenderThread(neighborPos[0], neighborPos[1], neighborPos[2]) orelse return; block = oldBlock; if(block.typ == itemBlock) { if(rotationMode.generateData(main.game.world.?, neighborPos, relPos, lastDir, neighborDir, neighborOfSelection, &block, neighborBlock, false)) { @@ -1047,7 +1047,7 @@ pub const MeshSelection = struct { // MARK: MeshSelection lastSelectedBlockPos = selectedPos; currentBlockProgress = 0; } - const block = mesh_storage.getBlock(selectedPos[0], selectedPos[1], selectedPos[2]) orelse return; + const block = mesh_storage.getBlockFromRenderThread(selectedPos[0], selectedPos[1], selectedPos[2]) orelse return; if(block.hasTag(.fluid) or block.hasTag(.air)) { return; } diff --git a/src/renderer/chunk_meshing.zig b/src/renderer/chunk_meshing.zig index 8f525d92..1dda2821 100644 --- a/src/renderer/chunk_meshing.zig +++ b/src/renderer/chunk_meshing.zig @@ -420,8 +420,7 @@ pub const PrimitiveMesh = struct { // MARK: PrimitiveMesh if(x == x & chunk.chunkMask and y == y & chunk.chunkMask and z == z & chunk.chunkMask) { return getValues(parent, wx, wy, wz); } - const neighborMesh = mesh_storage.getMeshAndIncreaseRefCount(.{.wx = wx, .wy = wy, .wz = wz, .voxelSize = parent.pos.voxelSize}) orelse return .{0, 0, 0, 0, 0, 0}; - defer neighborMesh.decreaseRefCount(); + const neighborMesh = mesh_storage.getMesh(.{.wx = wx, .wy = wy, .wz = wz, .voxelSize = parent.pos.voxelSize}) orelse return .{0, 0, 0, 0, 0, 0}; neighborMesh.lightingData[0].lock.lockRead(); neighborMesh.lightingData[1].lock.lockRead(); defer neighborMesh.lightingData[0].lock.unlockRead(); @@ -692,7 +691,8 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh blockBreakingFacesSortingData: []SortingData = &.{}, blockBreakingFacesChanged: bool = false, - pub fn init(self: *ChunkMesh, pos: chunk.ChunkPosition, ch: *chunk.Chunk) void { + pub fn init(pos: chunk.ChunkPosition, ch: *chunk.Chunk) *ChunkMesh { + const self = mesh_storage.meshMemoryPool.create(); self.* = ChunkMesh{ .pos = pos, .size = chunk.chunkSize*pos.voxelSize, @@ -709,10 +709,10 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh }, .blockBreakingFaces = .init(main.globalAllocator), }; + return self; } - pub fn deinit(self: *ChunkMesh) void { - std.debug.assert(self.refCount.load(.monotonic) == 0); + pub fn deinit(self: *ChunkMesh, _: usize) void { chunkBuffer.free(self.chunkAllocation); self.opaqueMesh.deinit(); self.transparentMesh.deinit(); @@ -727,35 +727,14 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh main.globalAllocator.free(self.blockBreakingFacesSortingData); main.globalAllocator.free(self.lightList); lightBuffers[std.math.log2_int(u32, self.pos.voxelSize)].free(self.lightAllocation); + mesh_storage.meshMemoryPool.destroy(self); } - pub fn increaseRefCount(self: *ChunkMesh) void { - const prevVal = self.refCount.fetchAdd(1, .monotonic); - std.debug.assert(prevVal != 0); - } - - /// In cases where it's not certain whether the thing was cleared already. - pub fn tryIncreaseRefCount(self: *ChunkMesh) bool { - var prevVal = self.refCount.load(.monotonic); - while(prevVal != 0) { - prevVal = self.refCount.cmpxchgWeak(prevVal, prevVal + 1, .monotonic, .monotonic) orelse return true; - } - return false; - } - - pub fn decreaseRefCount(self: *ChunkMesh) void { - const prevVal = self.refCount.fetchSub(1, .monotonic); - std.debug.assert(prevVal != 0); - if(prevVal == 1) { - mesh_storage.addMeshToClearListAndDecreaseRefCount(self); - } - } - - pub fn scheduleLightRefreshAndDecreaseRefCount(self: *ChunkMesh) void { - LightRefreshTask.scheduleAndDecreaseRefCount(self); + pub fn scheduleLightRefresh(pos: chunk.ChunkPosition) void { + LightRefreshTask.schedule(pos); } const LightRefreshTask = struct { - mesh: *ChunkMesh, + pos: chunk.ChunkPosition, pub const vtable = main.utils.ThreadPool.VTable{ .getPriority = main.utils.castFunctionSelfToAnyopaque(getPriority), @@ -765,10 +744,10 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh .taskType = .misc, }; - pub fn scheduleAndDecreaseRefCount(mesh: *ChunkMesh) void { + pub fn schedule(pos: chunk.ChunkPosition) void { const task = main.globalAllocator.create(LightRefreshTask); task.* = .{ - .mesh = mesh, + .pos = pos, }; main.threadPool.addTask(task, &vtable); } @@ -778,23 +757,21 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh } pub fn isStillNeeded(_: *LightRefreshTask) bool { - return true; // TODO: Is it worth checking for this? + return true; } pub fn run(self: *LightRefreshTask) void { - if(self.mesh.needsLightRefresh.swap(false, .acq_rel)) { - self.mesh.mutex.lock(); - self.mesh.finishData(); - self.mesh.mutex.unlock(); - mesh_storage.addToUpdateListAndDecreaseRefCount(self.mesh); - } else { - self.mesh.decreaseRefCount(); + defer main.globalAllocator.destroy(self); + const mesh = mesh_storage.getMesh(self.pos) orelse return; + if(mesh.needsLightRefresh.swap(false, .acq_rel)) { + mesh.mutex.lock(); + mesh.finishData(); + mesh.mutex.unlock(); + mesh_storage.addToUpdateList(mesh); } - main.globalAllocator.destroy(self); } pub fn clean(self: *LightRefreshTask) void { - self.mesh.decreaseRefCount(); main.globalAllocator.destroy(self); } }; @@ -809,7 +786,7 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh return block.typ != 0 and (other.typ == 0 or (block != other and other.viewThrough()) or other.alwaysViewThrough() or !blocks.meshes.model(other).model().isNeighborOccluded[neighbor.reverse().toInt()]); } - fn initLight(self: *ChunkMesh, lightRefreshList: *main.List(*ChunkMesh)) void { + fn initLight(self: *ChunkMesh, lightRefreshList: *main.List(chunk.ChunkPosition)) void { self.mutex.lock(); var lightEmittingBlocks = main.List([3]u8).init(main.stackAllocator); defer lightEmittingBlocks.deinit(); @@ -854,10 +831,10 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh } } - pub fn generateLightingData(self: *ChunkMesh) error{AlreadyStored}!void { + pub fn generateLightingData(self: *ChunkMesh) error{AlreadyStored, NoLongerNeeded}!void { try mesh_storage.addMeshToStorage(self); - var lightRefreshList = main.List(*ChunkMesh).init(main.stackAllocator); + var lightRefreshList = main.List(chunk.ChunkPosition).init(main.stackAllocator); defer lightRefreshList.deinit(); self.initLight(&lightRefreshList); @@ -876,8 +853,7 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh pos.wx +%= pos.voxelSize*chunk.chunkSize*dx; pos.wy +%= pos.voxelSize*chunk.chunkSize*dy; pos.wz +%= pos.voxelSize*chunk.chunkSize*dz; - const neighborMesh = mesh_storage.getMeshAndIncreaseRefCount(pos) orelse continue; - defer neighborMesh.decreaseRefCount(); + const neighborMesh = mesh_storage.getMesh(pos) orelse continue; const shiftSelf: u5 = @intCast(((dx + 1)*3 + dy + 1)*3 + dz + 1); const shiftOther: u5 = @intCast(((-dx + 1)*3 + -dy + 1)*3 + -dz + 1); @@ -894,12 +870,8 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh } } - for(lightRefreshList.items) |other| { - if(other.needsLightRefresh.load(.unordered)) { - other.scheduleLightRefreshAndDecreaseRefCount(); - } else { - other.decreaseRefCount(); - } + for(lightRefreshList.items) |pos| { + scheduleLightRefresh(pos); } } @@ -913,7 +885,7 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh model.appendNeighborFacingQuadsToList(list, allocator, block, neighbor, x, y, z, backFace); } - pub fn generateMesh(self: *ChunkMesh, lightRefreshList: *main.List(*ChunkMesh)) void { + pub fn generateMesh(self: *ChunkMesh, lightRefreshList: *main.List(chunk.ChunkPosition)) void { var alwaysViewThroughMask: [chunk.chunkSize][chunk.chunkSize]u32 = undefined; @memset(std.mem.asBytes(&alwaysViewThroughMask), 0); var alwaysViewThroughMask2: [chunk.chunkSize][chunk.chunkSize]u32 = undefined; @@ -1213,7 +1185,7 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh self.finishNeighbors(lightRefreshList); } - fn updateBlockLight(self: *ChunkMesh, x: u5, y: u5, z: u5, newBlock: Block, lightRefreshList: *main.List(*ChunkMesh)) void { + fn updateBlockLight(self: *ChunkMesh, x: u5, y: u5, z: u5, newBlock: Block, lightRefreshList: *main.List(chunk.ChunkPosition)) void { for(self.lightingData[0..]) |lightingData| { lightingData.propagateLightsDestructive(&.{.{x, y, z}}, lightRefreshList); } @@ -1222,7 +1194,7 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh } } - 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 { + pub fn updateBlock(self: *ChunkMesh, _x: i32, _y: i32, _z: i32, _newBlock: Block, blockEntityData: []const u8, lightRefreshList: *main.List(chunk.ChunkPosition), 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); @@ -1261,8 +1233,7 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh 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 neighborChunkMesh = mesh_storage.getNeighbor(self.pos, self.pos.voxelSize, neighbor) orelse continue; const index = chunk.getIndex(nnx, nny, nnz); @@ -1342,7 +1313,6 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh return; } } - mesh.increaseRefCount(); list.append(mesh); } @@ -1392,11 +1362,10 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh } } - fn finishNeighbors(self: *ChunkMesh, lightRefreshList: *main.List(*ChunkMesh)) void { + fn finishNeighbors(self: *ChunkMesh, lightRefreshList: *main.List(chunk.ChunkPosition)) void { for(chunk.Neighbor.iterable) |neighbor| { - const nullNeighborMesh = mesh_storage.getNeighborAndIncreaseRefCount(self.pos, self.pos.voxelSize, neighbor); + const nullNeighborMesh = mesh_storage.getNeighbor(self.pos, self.pos.voxelSize, neighbor); if(nullNeighborMesh) |neighborMesh| sameLodBlock: { - defer neighborMesh.decreaseRefCount(); std.debug.assert(neighborMesh != self); deadlockFreeDoubleLock(&self.mutex, &neighborMesh.mutex); defer self.mutex.unlock(); @@ -1470,8 +1439,7 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh neighborMesh.transparentMesh.replaceRange(.neighbor(neighbor.reverse()), transparentNeighbor.items); _ = neighborMesh.needsLightRefresh.store(true, .release); - neighborMesh.increaseRefCount(); - lightRefreshList.append(neighborMesh); + lightRefreshList.append(neighborMesh.pos); } else { self.mutex.lock(); defer self.mutex.unlock(); @@ -1483,7 +1451,7 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh } // lod border: if(self.pos.voxelSize == @as(u31, 1) << settings.highestLod) continue; - const neighborMesh = mesh_storage.getNeighborAndIncreaseRefCount(self.pos, 2*self.pos.voxelSize, neighbor) orelse { + const neighborMesh = mesh_storage.getNeighbor(self.pos, 2*self.pos.voxelSize, neighbor) orelse { self.mutex.lock(); defer self.mutex.unlock(); if(self.lastNeighborsHigherLod[neighbor.toInt()] != null) { @@ -1493,7 +1461,6 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh } continue; }; - defer neighborMesh.decreaseRefCount(); deadlockFreeDoubleLock(&self.mutex, &neighborMesh.mutex); defer self.mutex.unlock(); defer neighborMesh.mutex.unlock(); @@ -1557,7 +1524,7 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh defer self.mutex.unlock(); _ = self.needsLightRefresh.swap(false, .acq_rel); self.finishData(); - mesh_storage.finishMesh(self); + mesh_storage.finishMesh(self.pos); } fn uploadChunkPosition(self: *ChunkMesh) void { diff --git a/src/renderer/lighting.zig b/src/renderer/lighting.zig index f1571b62..441d5095 100644 --- a/src/renderer/lighting.zig +++ b/src/renderer/lighting.zig @@ -98,7 +98,7 @@ pub const ChannelChunk = struct { } } - fn propagateDirect(self: *ChannelChunk, lightQueue: *main.utils.CircularBufferQueue(Entry), lightRefreshList: *main.List(*chunk_meshing.ChunkMesh)) void { + fn propagateDirect(self: *ChannelChunk, lightQueue: *main.utils.CircularBufferQueue(Entry), lightRefreshList: *main.List(chunk.ChunkPosition)) void { var neighborLists: [6]main.ListUnmanaged(Entry) = @splat(.{}); defer { for(&neighborLists) |*list| { @@ -145,26 +145,24 @@ pub const ChannelChunk = struct { 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(); + const neighborMesh = mesh_storage.getNeighbor(self.ch.pos, self.ch.pos.voxelSize, neighbor) orelse continue; 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(); - return; - } + fn addSelfToLightRefreshList(self: *ChannelChunk, lightRefreshList: *main.List(chunk.ChunkPosition)) void { + for(lightRefreshList.items) |other| { + if(self.ch.pos.equals(other)) { + return; } + } + if(mesh_storage.getMesh(self.ch.pos)) |mesh| { mesh.needsLightRefresh.store(true, .release); - lightRefreshList.append(mesh); + lightRefreshList.append(self.ch.pos); } } - fn propagateDestructive(self: *ChannelChunk, lightQueue: *main.utils.CircularBufferQueue(Entry), constructiveEntries: *main.ListUnmanaged(ChunkEntries), isFirstBlock: bool, lightRefreshList: *main.List(*chunk_meshing.ChunkMesh)) main.ListUnmanaged(PositionEntry) { + fn propagateDestructive(self: *ChannelChunk, lightQueue: *main.utils.CircularBufferQueue(Entry), constructiveEntries: *main.ListUnmanaged(ChunkEntries), isFirstBlock: bool, lightRefreshList: *main.List(chunk.ChunkPosition)) main.ListUnmanaged(PositionEntry) { var neighborLists: [6]main.ListUnmanaged(Entry) = @splat(.{}); var constructiveList: main.ListUnmanaged(PositionEntry) = .{}; defer { @@ -238,7 +236,7 @@ pub const ChannelChunk = struct { 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; + const neighborMesh = mesh_storage.getNeighbor(self.ch.pos, self.ch.pos.voxelSize, neighbor) orelse continue; constructiveEntries.append(main.stackAllocator, .{ .mesh = neighborMesh, .entries = neighborMesh.lightingData[@intFromBool(self.isSun)].propagateDestructiveFromNeighbor(lightQueue, neighborLists[neighbor.toInt()].items, constructiveEntries, lightRefreshList), @@ -248,7 +246,7 @@ pub const ChannelChunk = struct { return constructiveList; } - fn propagateFromNeighbor(self: *ChannelChunk, lightQueue: *main.utils.CircularBufferQueue(Entry), lights: []const Entry, lightRefreshList: *main.List(*chunk_meshing.ChunkMesh)) void { + fn propagateFromNeighbor(self: *ChannelChunk, lightQueue: *main.utils.CircularBufferQueue(Entry), lights: []const Entry, lightRefreshList: *main.List(chunk.ChunkPosition)) void { std.debug.assert(lightQueue.empty()); for(lights) |entry| { const index = chunk.getIndex(entry.x, entry.y, entry.z); @@ -259,7 +257,7 @@ pub const ChannelChunk = struct { self.propagateDirect(lightQueue, lightRefreshList); } - fn propagateDestructiveFromNeighbor(self: *ChannelChunk, lightQueue: *main.utils.CircularBufferQueue(Entry), lights: []const Entry, constructiveEntries: *main.ListUnmanaged(ChunkEntries), lightRefreshList: *main.List(*chunk_meshing.ChunkMesh)) main.ListUnmanaged(PositionEntry) { + fn propagateDestructiveFromNeighbor(self: *ChannelChunk, lightQueue: *main.utils.CircularBufferQueue(Entry), lights: []const Entry, constructiveEntries: *main.ListUnmanaged(ChunkEntries), lightRefreshList: *main.List(chunk.ChunkPosition)) main.ListUnmanaged(PositionEntry) { std.debug.assert(lightQueue.empty()); for(lights) |entry| { const index = chunk.getIndex(entry.x, entry.y, entry.z); @@ -270,7 +268,7 @@ pub const ChannelChunk = struct { return self.propagateDestructive(lightQueue, constructiveEntries, false, lightRefreshList); } - pub fn propagateLights(self: *ChannelChunk, lights: []const [3]u8, comptime checkNeighbors: bool, lightRefreshList: *main.List(*chunk_meshing.ChunkMesh)) void { + pub fn propagateLights(self: *ChannelChunk, lights: []const [3]u8, comptime checkNeighbors: bool, lightRefreshList: *main.List(chunk.ChunkPosition)) void { var lightQueue = main.utils.CircularBufferQueue(Entry).init(main.stackAllocator, 1 << 12); defer lightQueue.deinit(); for(lights) |pos| { @@ -307,8 +305,7 @@ pub const ChannelChunk = struct { const otherX = x +% neighbor.relX() & chunk.chunkMask; const otherY = y +% neighbor.relY() & chunk.chunkMask; const otherZ = z +% neighbor.relZ() & chunk.chunkMask; - const neighborMesh = mesh_storage.getNeighborAndIncreaseRefCount(self.ch.pos, self.ch.pos.voxelSize, neighbor) orelse continue; - defer neighborMesh.decreaseRefCount(); + const neighborMesh = mesh_storage.getNeighbor(self.ch.pos, self.ch.pos.voxelSize, neighbor) orelse continue; const neighborLightChunk = neighborMesh.lightingData[@intFromBool(self.isSun)]; neighborLightChunk.lock.lockRead(); defer neighborLightChunk.lock.unlockRead(); @@ -331,7 +328,7 @@ pub const ChannelChunk = struct { self.propagateDirect(&lightQueue, lightRefreshList); } - pub fn propagateUniformSun(self: *ChannelChunk, lightRefreshList: *main.List(*chunk_meshing.ChunkMesh)) void { + pub fn propagateUniformSun(self: *ChannelChunk, lightRefreshList: *main.List(chunk.ChunkPosition)) void { std.debug.assert(self.isSun); self.lock.lockWrite(); if(self.data.paletteLength != 1) { @@ -345,8 +342,7 @@ pub const ChannelChunk = struct { defer lightQueue.deinit(); for(chunk.Neighbor.iterable) |neighbor| { if(neighbor == .dirUp) continue; - const neighborMesh = mesh_storage.getNeighborAndIncreaseRefCount(self.ch.pos, self.ch.pos.voxelSize, neighbor) orelse continue; - defer neighborMesh.decreaseRefCount(); + const neighborMesh = mesh_storage.getNeighbor(self.ch.pos, self.ch.pos.voxelSize, neighbor) orelse continue; var list: [chunk.chunkSize*chunk.chunkSize]Entry = undefined; for(0..chunk.chunkSize) |x| { for(0..chunk.chunkSize) |y| { @@ -379,7 +375,7 @@ pub const ChannelChunk = struct { } } - pub fn propagateLightsDestructive(self: *ChannelChunk, lights: []const [3]u8, lightRefreshList: *main.List(*chunk_meshing.ChunkMesh)) void { + pub fn propagateLightsDestructive(self: *ChannelChunk, lights: []const [3]u8, lightRefreshList: *main.List(chunk.ChunkPosition)) void { var lightQueue = main.utils.CircularBufferQueue(Entry).init(main.stackAllocator, 1 << 12); defer lightQueue.deinit(); self.lock.lockRead(); @@ -396,7 +392,6 @@ pub const ChannelChunk = struct { }); for(constructiveEntries.items) |entries| { const mesh = entries.mesh; - defer if(mesh) |_mesh| _mesh.decreaseRefCount(); var entryList = entries.entries; defer entryList.deinit(main.stackAllocator); const channelChunk = if(mesh) |_mesh| _mesh.lightingData[@intFromBool(self.isSun)] else self; diff --git a/src/renderer/mesh_storage.zig b/src/renderer/mesh_storage.zig index 5d467f63..49ead679 100644 --- a/src/renderer/mesh_storage.zig +++ b/src/renderer/mesh_storage.zig @@ -22,24 +22,22 @@ const chunk_meshing = @import("chunk_meshing.zig"); const ChunkMesh = chunk_meshing.ChunkMesh; const ChunkMeshNode = struct { - mesh: ?*chunk_meshing.ChunkMesh = null, + mesh: Atomic(?*chunk_meshing.ChunkMesh) = .init(null), active: bool = false, rendered: bool = false, finishedMeshing: bool = false, // Must be synced with mesh.finishedMeshing finishedMeshingHigherResolution: u8 = 0, // Must be synced with finishedMeshing of the 8 higher resolution chunks. pos: chunk.ChunkPosition = undefined, isNeighborLod: [6]bool = @splat(false), // Must be synced with mesh.isNeighborLod - mutex: std.Thread.Mutex = .{}, }; const storageSize = 64; const storageMask = storageSize - 1; var storageLists: [settings.highestSupportedLod + 1]*[storageSize*storageSize*storageSize]ChunkMeshNode = undefined; var mapStorageLists: [settings.highestSupportedLod + 1]*[storageSize*storageSize]?*LightMap.LightMapFragment = undefined; var meshList = main.List(*chunk_meshing.ChunkMesh).init(main.globalAllocator); -var priorityMeshUpdateList: main.utils.ConcurrentQueue(*chunk_meshing.ChunkMesh) = undefined; -pub var updatableList = main.List(*chunk_meshing.ChunkMesh).init(main.globalAllocator); +var priorityMeshUpdateList: main.utils.ConcurrentQueue(chunk.ChunkPosition) = undefined; +pub var updatableList = main.List(chunk.ChunkPosition).init(main.globalAllocator); var mapUpdatableList: main.utils.ConcurrentQueue(*LightMap.LightMapFragment) = undefined; -var clearList = main.List(*chunk_meshing.ChunkMesh).init(main.globalAllocator); var lastPx: i32 = 0; var lastPy: i32 = 0; var lastPz: i32 = 0; @@ -74,7 +72,7 @@ pub const BlockUpdate = struct { var blockUpdateList: main.utils.ConcurrentQueue(BlockUpdate) = undefined; -var meshMemoryPool: main.heap.MemoryPool(chunk_meshing.ChunkMesh) = undefined; +pub var meshMemoryPool: main.heap.MemoryPool(chunk_meshing.ChunkMesh) = undefined; pub fn init() void { // MARK: init() lastRD = 0; @@ -104,6 +102,7 @@ pub fn deinit() void { lastPz = 0; lastRD = 0; freeOldMeshes(olderPx, olderPy, olderPz, olderRD); + main.heap.GarbageCollection.waitForFreeCompletion(); for(storageLists) |storageList| { main.globalAllocator.destroy(storageList); } @@ -111,28 +110,17 @@ pub fn deinit() void { main.globalAllocator.destroy(mapStorageList); } - for(updatableList.items) |mesh| { - mesh.decreaseRefCount(); - } updatableList.clearAndFree(); while(mapUpdatableList.dequeue()) |map| { map.decreaseRefCount(); } mapUpdatableList.deinit(); - while(priorityMeshUpdateList.dequeue()) |mesh| { - mesh.decreaseRefCount(); - } priorityMeshUpdateList.deinit(); while(blockUpdateList.dequeue()) |blockUpdate| { blockUpdate.deinitManaged(main.globalAllocator); } blockUpdateList.deinit(); meshList.clearAndFree(); - for(clearList.items) |mesh| { - mesh.deinit(); - meshMemoryPool.destroy(mesh); - } - clearList.clearAndFree(); meshMemoryPool.deinit(); } @@ -191,20 +179,16 @@ pub fn getLightMapPieceAndIncreaseRefCount(x: i32, y: i32, voxelSize: u31) ?*Lig return result; } -pub fn getBlock(x: i32, y: i32, z: i32) ?blocks.Block { +pub fn getBlockFromRenderThread(x: i32, y: i32, z: i32) ?blocks.Block { const node = getNodePointer(.{.wx = x, .wy = y, .wz = z, .voxelSize = 1}); - node.mutex.lock(); - defer node.mutex.unlock(); - const mesh = node.mesh orelse return null; + const mesh = node.mesh.load(.acquire) orelse return null; const block = mesh.chunk.getBlock(x & chunk.chunkMask, y & chunk.chunkMask, z & chunk.chunkMask); return block; } -pub fn triggerOnInteractBlock(x: i32, y: i32, z: i32) EventStatus { +pub fn triggerOnInteractBlockFromRenderThread(x: i32, y: i32, z: i32) EventStatus { const node = getNodePointer(.{.wx = x, .wy = y, .wz = z, .voxelSize = 1}); - node.mutex.lock(); - defer node.mutex.unlock(); - const mesh = node.mesh orelse return .ignored; + const mesh = node.mesh.load(.acquire) orelse return .ignored; const block = mesh.chunk.getBlock(x & chunk.chunkMask, y & chunk.chunkMask, z & chunk.chunkMask); if(block.blockEntity()) |blockEntity| { return blockEntity.onInteract(.{x, y, z}, mesh.chunk); @@ -215,9 +199,7 @@ pub fn triggerOnInteractBlock(x: i32, y: i32, z: i32) EventStatus { pub fn getLight(wx: i32, wy: i32, wz: i32) ?[6]u8 { const node = getNodePointer(.{.wx = wx, .wy = wy, .wz = wz, .voxelSize = 1}); - node.mutex.lock(); - defer node.mutex.unlock(); - const mesh = node.mesh orelse return null; + const mesh = node.mesh.load(.acquire) orelse return null; const x = (wx >> mesh.chunk.voxelSizeShift) & chunk.chunkMask; const y = (wy >> mesh.chunk.voxelSizeShift) & chunk.chunkMask; const z = (wz >> mesh.chunk.voxelSizeShift) & chunk.chunkMask; @@ -228,53 +210,44 @@ pub fn getLight(wx: i32, wy: i32, wz: i32) ?[6]u8 { return mesh.lightingData[1].getValue(x, y, z) ++ mesh.lightingData[0].getValue(x, y, z); } -pub fn getBlockFromAnyLod(x: i32, y: i32, z: i32) blocks.Block { +pub fn getBlockFromAnyLodFromRenderThread(x: i32, y: i32, z: i32) blocks.Block { var lod: u5 = 0; while(lod < settings.highestLod) : (lod += 1) { const node = getNodePointer(.{.wx = x, .wy = y, .wz = z, .voxelSize = @as(u31, 1) << lod}); - node.mutex.lock(); - defer node.mutex.unlock(); - const mesh = node.mesh orelse continue; + const mesh = node.mesh.load(.acquire) orelse continue; const block = mesh.chunk.getBlock(x & chunk.chunkMask << lod, y & chunk.chunkMask << lod, z & chunk.chunkMask << lod); return block; } return blocks.Block{.typ = 0, .data = 0}; } -pub fn getMeshAndIncreaseRefCount(pos: chunk.ChunkPosition) ?*chunk_meshing.ChunkMesh { +pub fn getMesh(pos: chunk.ChunkPosition) ?*chunk_meshing.ChunkMesh { const lod = std.math.log2_int(u31, pos.voxelSize); const mask = ~((@as(i32, 1) << lod + chunk.chunkShift) - 1); const node = getNodePointer(pos); - node.mutex.lock(); - const mesh = node.mesh orelse { - node.mutex.unlock(); - return null; - }; - mesh.increaseRefCount(); - node.mutex.unlock(); + const mesh = node.mesh.load(.acquire) orelse return null; if(pos.wx & mask != mesh.pos.wx or pos.wy & mask != mesh.pos.wy or pos.wz & mask != mesh.pos.wz) { - mesh.decreaseRefCount(); return null; } return mesh; } -pub fn getMeshFromAnyLodAndIncreaseRefCount(wx: i32, wy: i32, wz: i32, voxelSize: u31) ?*chunk_meshing.ChunkMesh { +pub fn getMeshFromAnyLod(wx: i32, wy: i32, wz: i32, voxelSize: u31) ?*chunk_meshing.ChunkMesh { var lod: u5 = @ctz(voxelSize); while(lod < settings.highestLod) : (lod += 1) { - const mesh = getMeshAndIncreaseRefCount(.{.wx = wx & ~chunk.chunkMask << lod, .wy = wy & ~chunk.chunkMask << lod, .wz = wz & ~chunk.chunkMask << lod, .voxelSize = @as(u31, 1) << lod}); + const mesh = getMesh(.{.wx = wx & ~chunk.chunkMask << lod, .wy = wy & ~chunk.chunkMask << lod, .wz = wz & ~chunk.chunkMask << lod, .voxelSize = @as(u31, 1) << lod}); return mesh orelse continue; } return null; } -pub fn getNeighborAndIncreaseRefCount(_pos: chunk.ChunkPosition, resolution: u31, neighbor: chunk.Neighbor) ?*chunk_meshing.ChunkMesh { +pub fn getNeighbor(_pos: chunk.ChunkPosition, resolution: u31, neighbor: chunk.Neighbor) ?*chunk_meshing.ChunkMesh { var pos = _pos; pos.wx +%= pos.voxelSize*chunk.chunkSize*neighbor.relX(); pos.wy +%= pos.voxelSize*chunk.chunkSize*neighbor.relY(); pos.wz +%= pos.voxelSize*chunk.chunkSize*neighbor.relZ(); pos.voxelSize = resolution; - return getMeshAndIncreaseRefCount(pos); + return getMesh(pos); } fn reduceRenderDistance(fullRenderDistance: i64, reduction: i64) i32 { @@ -399,15 +372,15 @@ fn freeOldMeshes(olderPx: i32, olderPy: i32, olderPz: i32, olderRD: u16) void { const index = (xIndex*storageSize + yIndex)*storageSize + zIndex; const node = &storageLists[_lod][@intCast(index)]; - node.mutex.lock(); - const oldMesh = node.mesh; - node.mesh = null; - node.mutex.unlock(); + const oldMesh = node.mesh.swap(null, .monotonic); node.pos = undefined; if(oldMesh) |mesh| { node.finishedMeshing = false; updateHigherLodNodeFinishedMeshing(mesh.pos, false); - mesh.decreaseRefCount(); + main.heap.GarbageCollection.deferredFree(.{ + .ptr = mesh, + .freeFunction = main.utils.castFunctionSelfToAnyopaque(ChunkMesh.deinit), + }); } node.isNeighborLod = @splat(false); } @@ -542,14 +515,12 @@ fn createNewMeshes(olderPx: i32, olderPy: i32, olderPz: i32, olderRD: u16, meshR const pos = chunk.ChunkPosition{.wx = x, .wy = y, .wz = z, .voxelSize = @as(u31, 1) << lod}; const node = &storageLists[_lod][@intCast(index)]; - node.mutex.lock(); node.pos = pos; - if(node.mesh) |mesh| { + if(node.mesh.load(.acquire)) |mesh| { std.debug.assert(std.meta.eql(pos, mesh.pos)); } else { meshRequests.append(pos); } - node.mutex.unlock(); } } } @@ -756,7 +727,7 @@ pub noinline fn updateAndGetRenderChunks(conn: *network.Connection, frustum: *co } } if(!std.meta.eql(node.isNeighborLod, isNeighborLod)) { - const mesh = node.mesh.?; // No need to lock the mutex, since no other thread is allowed to overwrite the mesh (unless it's null). + const mesh = node.mesh.load(.acquire).?; // no other thread is allowed to overwrite the mesh (unless it's null). mesh.isNeighborLod = isNeighborLod; node.isNeighborLod = isNeighborLod; mesh.uploadData(); @@ -766,14 +737,12 @@ pub noinline fn updateAndGetRenderChunks(conn: *network.Connection, frustum: *co node.rendered = false; if(!node.finishedMeshing) continue; - const mesh = node.mesh.?; // No need to lock the mutex, since no other thread is allowed to overwrite the mesh (unless it's null). + const mesh = node.mesh.load(.acquire).?; // no other thread is allowed to overwrite the mesh (unless it's null). - node.mutex.lock(); if(mesh.needsMeshUpdate) { mesh.uploadData(); mesh.needsMeshUpdate = false; } - node.mutex.unlock(); // Remove empty meshes. if(!mesh.isEmpty()) { meshList.append(mesh); @@ -788,32 +757,14 @@ pub fn updateMeshes(targetTime: i64) void { // MARK: updateMeshes()= mutex.lock(); defer mutex.unlock(); - for(clearList.items) |mesh| { - mesh.deinit(); - meshMemoryPool.destroy(mesh); - } - clearList.clearRetainingCapacity(); - while(priorityMeshUpdateList.dequeue()) |mesh| { + while(priorityMeshUpdateList.dequeue()) |pos| { + const mesh = getMesh(pos) orelse continue; if(!mesh.needsMeshUpdate) { - mutex.unlock(); - defer mutex.lock(); - mesh.decreaseRefCount(); continue; } mesh.needsMeshUpdate = false; - const node = getNodePointer(mesh.pos); - node.mutex.lock(); - if(node.mesh != mesh) { - node.mutex.unlock(); - mutex.unlock(); - defer mutex.lock(); - mesh.decreaseRefCount(); - continue; - } - node.mutex.unlock(); mutex.unlock(); defer mutex.lock(); - mesh.decreaseRefCount(); mesh.uploadData(); if(std.time.milliTimestamp() >= targetTime) break; // Update at least one mesh. } @@ -836,15 +787,14 @@ pub fn updateMeshes(targetTime: i64) void { // MARK: updateMeshes()= { var i: usize = 0; while(i < updatableList.items.len) { - const mesh = updatableList.items[i]; - if(!isInRenderDistance(mesh.pos)) { + const pos = updatableList.items[i]; + if(!isInRenderDistance(pos)) { _ = updatableList.swapRemove(i); mutex.unlock(); defer mutex.lock(); - mesh.decreaseRefCount(); continue; } - const priority = mesh.pos.getPriority(playerPos); + const priority = pos.getPriority(playerPos); if(priority > closestPriority) { closestPriority = priority; closestIndex = i; @@ -853,32 +803,24 @@ pub fn updateMeshes(targetTime: i64) void { // MARK: updateMeshes()= } if(updatableList.items.len == 0) break; } - const mesh = updatableList.swapRemove(closestIndex); + const pos = updatableList.swapRemove(closestIndex); mutex.unlock(); defer mutex.lock(); - if(isInRenderDistance(mesh.pos)) { - const node = getNodePointer(mesh.pos); - std.debug.assert(std.meta.eql(node.pos, mesh.pos)); + if(isInRenderDistance(pos)) { + const node = getNodePointer(pos); + if(node.finishedMeshing) continue; + const mesh = getMesh(pos) orelse continue; node.finishedMeshing = true; mesh.finishedMeshing = true; - updateHigherLodNodeFinishedMeshing(mesh.pos, true); + updateHigherLodNodeFinishedMeshing(pos, true); mesh.uploadData(); - node.mutex.lock(); - const oldMesh = node.mesh; - node.mesh = mesh; - node.mutex.unlock(); - if(oldMesh) |_oldMesh| { - _oldMesh.decreaseRefCount(); - } - } else { - mesh.decreaseRefCount(); } if(std.time.milliTimestamp() >= targetTime) break; // Update at least one mesh. } } fn batchUpdateBlocks() void { - var lightRefreshList = main.List(*ChunkMesh).init(main.stackAllocator); + var lightRefreshList = main.List(chunk.ChunkPosition).init(main.stackAllocator); defer lightRefreshList.deinit(); var regenerateMeshList = main.List(*ChunkMesh).init(main.stackAllocator); @@ -888,74 +830,51 @@ fn batchUpdateBlocks() void { 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| { + if(getMesh(pos)) |mesh| { mesh.updateBlock(blockUpdate.x, blockUpdate.y, blockUpdate.z, blockUpdate.newBlock, blockUpdate.blockEntityData, &lightRefreshList, ®enerateMeshList); - mesh.decreaseRefCount(); } // TODO: It seems like we simply ignore the block update if we don't have the mesh yet. } for(regenerateMeshList.items) |mesh| { mesh.generateMesh(&lightRefreshList); } - { - for(lightRefreshList.items) |mesh| { - if(mesh.needsLightRefresh.load(.unordered)) { - mesh.scheduleLightRefreshAndDecreaseRefCount(); - } else { - mesh.decreaseRefCount(); - } - } + for(lightRefreshList.items) |pos| { + ChunkMesh.scheduleLightRefresh(pos); } for(regenerateMeshList.items) |mesh| { mesh.uploadData(); - mesh.decreaseRefCount(); } } // MARK: adders -pub fn addMeshToClearListAndDecreaseRefCount(mesh: *chunk_meshing.ChunkMesh) void { - std.debug.assert(mesh.refCount.load(.monotonic) == 0); - mutex.lock(); - defer mutex.unlock(); - clearList.append(mesh); -} - -pub fn addToUpdateListAndDecreaseRefCount(mesh: *chunk_meshing.ChunkMesh) void { +pub fn addToUpdateList(mesh: *chunk_meshing.ChunkMesh) void { std.debug.assert(mesh.refCount.load(.monotonic) != 0); mutex.lock(); defer mutex.unlock(); if(mesh.finishedMeshing) { - priorityMeshUpdateList.enqueue(mesh); + priorityMeshUpdateList.enqueue(mesh.pos); mesh.needsMeshUpdate = true; - } else { - mutex.unlock(); - defer mutex.lock(); - mesh.decreaseRefCount(); } } -pub fn addMeshToStorage(mesh: *chunk_meshing.ChunkMesh) error{AlreadyStored}!void { +pub fn addMeshToStorage(mesh: *chunk_meshing.ChunkMesh) error{AlreadyStored, NoLongerNeeded}!void { mutex.lock(); defer mutex.unlock(); - if(isInRenderDistance(mesh.pos)) { - const node = getNodePointer(mesh.pos); - node.mutex.lock(); - defer node.mutex.unlock(); - if(node.mesh != null) { - return error.AlreadyStored; - } - node.mesh = mesh; - node.finishedMeshing = mesh.finishedMeshing; - updateHigherLodNodeFinishedMeshing(mesh.pos, mesh.finishedMeshing); - mesh.increaseRefCount(); + if(!isInRenderDistance(mesh.pos)) { + return error.NoLongerNeeded; } + const node = getNodePointer(mesh.pos); + if(node.mesh.cmpxchgStrong(null, mesh, .release, .monotonic) != null) { + return error.AlreadyStored; + } + node.finishedMeshing = mesh.finishedMeshing; + updateHigherLodNodeFinishedMeshing(mesh.pos, mesh.finishedMeshing); } -pub fn finishMesh(mesh: *chunk_meshing.ChunkMesh) void { +pub fn finishMesh(pos: chunk.ChunkPosition) void { mutex.lock(); defer mutex.unlock(); - mesh.increaseRefCount(); - updatableList.append(mesh); + updatableList.append(pos); } pub const MeshGenerationTask = struct { // MARK: MeshGenerationTask @@ -991,10 +910,8 @@ pub const MeshGenerationTask = struct { // MARK: MeshGenerationTask pub fn run(self: *MeshGenerationTask) void { defer main.globalAllocator.destroy(self); const pos = self.mesh.pos; - const mesh = meshMemoryPool.create(); - mesh.init(pos, self.mesh); - defer mesh.decreaseRefCount(); - mesh.generateLightingData() catch return; + const mesh = ChunkMesh.init(pos, self.mesh); + mesh.generateLightingData() catch mesh.deinit(undefined); } pub fn clean(self: *MeshGenerationTask) void { @@ -1023,7 +940,7 @@ pub fn addBreakingAnimation(pos: Vec3i, breakingProgress: f32) void { const animationFrame: usize = @intFromFloat(breakingProgress*@as(f32, @floatFromInt(main.blocks.meshes.blockBreakingTextures.items.len))); const texture = main.blocks.meshes.blockBreakingTextures.items[animationFrame]; - const block = getBlock(pos[0], pos[1], pos[2]) orelse return; + const block = getBlockFromRenderThread(pos[0], pos[1], pos[2]) orelse return; const model = main.blocks.meshes.model(block).model(); for(model.internalQuads) |quadIndex| { @@ -1039,8 +956,7 @@ pub fn addBreakingAnimation(pos: Vec3i, breakingProgress: f32) void { fn addBreakingAnimationFace(pos: Vec3i, quadIndex: main.models.QuadIndex, texture: u16, neighbor: ?chunk.Neighbor, isTransparent: bool) void { const worldPos = pos +% if(neighbor) |n| n.relPos() else Vec3i{0, 0, 0}; const relPos = worldPos & @as(Vec3i, @splat(main.chunk.chunkMask)); - const mesh = getMeshAndIncreaseRefCount(.{.wx = worldPos[0], .wy = worldPos[1], .wz = worldPos[2], .voxelSize = 1}) orelse return; - defer mesh.decreaseRefCount(); + const mesh = getMesh(.{.wx = worldPos[0], .wy = worldPos[1], .wz = worldPos[2], .voxelSize = 1}) orelse return; mesh.mutex.lock(); defer mesh.mutex.unlock(); const lightIndex = blk: { @@ -1074,8 +990,7 @@ fn addBreakingAnimationFace(pos: Vec3i, quadIndex: main.models.QuadIndex, textur fn removeBreakingAnimationFace(pos: Vec3i, quadIndex: main.models.QuadIndex, neighbor: ?chunk.Neighbor) void { const worldPos = pos +% if(neighbor) |n| n.relPos() else Vec3i{0, 0, 0}; const relPos = worldPos & @as(Vec3i, @splat(main.chunk.chunkMask)); - const mesh = getMeshAndIncreaseRefCount(.{.wx = worldPos[0], .wy = worldPos[1], .wz = worldPos[2], .voxelSize = 1}) orelse return; - defer mesh.decreaseRefCount(); + const mesh = getMesh(.{.wx = worldPos[0], .wy = worldPos[1], .wz = worldPos[2], .voxelSize = 1}) orelse return; for(mesh.blockBreakingFaces.items, 0..) |face, i| { if(face.position.x == relPos[0] and face.position.y == relPos[1] and face.position.z == relPos[2] and face.blockAndQuad.quadIndex == quadIndex) { _ = mesh.blockBreakingFaces.swapRemove(i); @@ -1086,7 +1001,7 @@ fn removeBreakingAnimationFace(pos: Vec3i, quadIndex: main.models.QuadIndex, nei } pub fn removeBreakingAnimation(pos: Vec3i) void { - const block = getBlock(pos[0], pos[1], pos[2]) orelse return; + const block = getBlockFromRenderThread(pos[0], pos[1], pos[2]) orelse return; const model = main.blocks.meshes.model(block).model(); for(model.internalQuads) |quadIndex| { diff --git a/src/server/server.zig b/src/server/server.zig index ff87fe23..7e388853 100644 --- a/src/server/server.zig +++ b/src/server/server.zig @@ -442,6 +442,7 @@ pub fn start(name: []const u8, port: ?u16) void { defer deinit(); running.store(true, .release); while(running.load(.monotonic)) { + main.heap.GarbageCollection.syncPoint(); const newTime = std.time.nanoTimestamp(); if(newTime -% lastTime < updateNanoTime) { std.Thread.sleep(@intCast(lastTime +% updateNanoTime -% newTime)); diff --git a/src/utils.zig b/src/utils.zig index 28794083..c76edb96 100644 --- a/src/utils.zig +++ b/src/utils.zig @@ -749,15 +749,17 @@ pub fn BlockingMaxHeap(comptime T: type) type { // MARK: BlockingMaxHeap /// Returns the biggest element and removes it from the heap. /// If empty blocks until a new object is added or the datastructure is closed. - pub fn extractMax(self: *@This()) !T { + pub fn extractMax(self: *@This()) error{Timeout, Closed}!T { self.mutex.lock(); defer self.mutex.unlock(); + const startTime = std.time.nanoTimestamp(); + while(true) { if(self.size == 0) { self.waitingThreadCount += 1; - self.waitingThreads.wait(&self.mutex); - self.waitingThreadCount -= 1; + defer self.waitingThreadCount -= 1; + try self.waitingThreads.timedWait(&self.mutex, 10_000_000); } else { const ret = self.array[0]; self.removeIndex(0); @@ -766,6 +768,7 @@ pub fn BlockingMaxHeap(comptime T: type) type { // MARK: BlockingMaxHeap if(self.closed) { return error.Closed; } + if(std.time.nanoTimestamp() -% startTime > 10_000_000) return error.Timeout; } } @@ -909,9 +912,13 @@ pub const ThreadPool = struct { // MARK: ThreadPool defer main.deinitThreadLocals(); var lastUpdate = std.time.milliTimestamp(); - while(true) { + outer: while(true) { + main.heap.GarbageCollection.syncPoint(); { - const task = self.loadList.extractMax() catch break; + const task = self.loadList.extractMax() catch |err| switch(err) { + error.Timeout => continue :outer, + error.Closed => break :outer, + }; self.currentTasks[id].store(task.vtable, .monotonic); const start = std.time.microTimestamp(); task.vtable.run(task.self); diff --git a/src/utils/heap.zig b/src/utils/heap.zig index 90540742..2aa17767 100644 --- a/src/utils/heap.zig +++ b/src/utils/heap.zig @@ -577,6 +577,113 @@ pub fn MemoryPool(Item: type) type { // MARK: MemoryPool }; } +pub const GarbageCollection = struct { // MARK: GarbageCollection + var sharedState: std.atomic.Value(u32) = .init(0); + threadlocal var threadCycle: u2 = undefined; + threadlocal var lastSyncPointTime: i64 = undefined; + const FreeItem = struct { + ptr: *anyopaque, + extraData: usize = 0, + freeFunction: *const fn(*anyopaque, usize) void, + }; + threadlocal var lists: [4]main.ListUnmanaged(FreeItem) = undefined; + + const State = packed struct { + waitingThreads: u15 = 0, + totalThreads: u15 = 0, + cycle: u2 = 0, + }; + + pub fn addThread() void { + const old: State = @bitCast(sharedState.fetchAdd(@bitCast(State{.totalThreads = 1}), .monotonic)); + _ = old.totalThreads + 1; // Assert no overflow + threadCycle = old.cycle; + lastSyncPointTime = std.time.milliTimestamp(); + for(&lists) |*list| { + list.* = .initCapacity(main.globalAllocator, 1024); + } + if(old.waitingThreads == 0) { + startNewCycle(); + } + } + + fn freeItemsFromList(list: *main.ListUnmanaged(FreeItem)) void { + while(list.popOrNull()) |item| { + item.freeFunction(item.ptr, item.extraData); + } + } + + pub fn removeThread() void { + const old: State = @bitCast(sharedState.fetchSub(@bitCast(State{.totalThreads = 1}), .monotonic)); + _ = old.totalThreads - 1; // Assert no overflow + if(old.cycle != threadCycle) removeThreadFromWaiting(); + const newTime = std.time.milliTimestamp(); + if(newTime -% lastSyncPointTime > 10_000) { + std.log.err("No sync point executed in {} ms for thread. Did you forget to add a sync point in the thread's main loop?", .{newTime -% lastSyncPointTime}); + std.debug.dumpCurrentStackTrace(null); + } + for(&lists) |*list| { + freeItemsFromList(list); + list.deinit(main.globalAllocator); + } + } + + pub fn assertAllThreadsStopped() void { + std.debug.assert(sharedState.load(.unordered) & 0x3fffffff == 0); + } + + fn startNewCycle() void { + var cur = sharedState.load(.unordered); + while(true) { + var new: State = @bitCast(cur); + new.waitingThreads = new.totalThreads; + new.cycle +%= 1; + cur = sharedState.cmpxchgWeak(cur, @bitCast(new), .monotonic, .monotonic) orelse break; + } + } + + fn removeThreadFromWaiting() void { + const old: State = @bitCast(sharedState.fetchSub(@bitCast(State{.waitingThreads = 1}), .acq_rel)); + _ = old.waitingThreads - 1; // Assert no overflow + threadCycle = old.cycle; + + if(old.waitingThreads == 1) startNewCycle(); + } + + /// Must be called when no objects originating from other threads are held on the current function stack + pub fn syncPoint() void { + const newTime = std.time.milliTimestamp(); + if(newTime -% lastSyncPointTime > 10_000) { + std.log.err("No sync point executed in {} ms. Did you forget to add a sync point in the thread's main loop", .{newTime -% lastSyncPointTime}); + std.debug.dumpCurrentStackTrace(null); + } + lastSyncPointTime = newTime; + + const old: State = @bitCast(sharedState.load(.unordered)); + if(old.cycle == threadCycle) return; + removeThreadFromWaiting(); + freeItemsFromList(&lists[threadCycle]); + // TODO: Free all the data here and swap lists + } + + pub fn deferredFree(item: FreeItem) void { + lists[threadCycle].append(main.globalAllocator, item); + } + + /// Waits until all deferred frees have been completed. + pub fn waitForFreeCompletion() void { + const startCycle = threadCycle; + while(threadCycle == startCycle) { + syncPoint(); + std.Thread.sleep(1_000_000); + } + while(threadCycle != startCycle) { + syncPoint(); + std.Thread.sleep(1_000_000); + } + } +}; + pub fn PowerOfTwoPoolAllocator(minSize: comptime_int, maxSize: comptime_int, maxAlignment: comptime_int) type { // MARK: PowerOfTwoPoolAllocator std.debug.assert(std.math.isPowerOfTwo(minSize)); std.debug.assert(std.math.isPowerOfTwo(maxSize));