Garbage Collection - The Great Return (#1680)

This is a first attempt to get rid of of reference counting #1413

Basically all threads execute a global sync point, this allows
determining a time when all temporary resources (such as meshes on the
client side used in lighting on other threads) have been freed with 100%
certainty.

Remaining work:
- [x] Implement the sync point
- [x] Implement free lists and free the resources
- [x] Determine if this is worth it performance wise
  - [x] Use this for chunk meshes
- [x] Remove reference counting and the locks on the chunk storage data
structure
- [x] Measure performance of gathering many light samples and compare it
with master → around 15% reduction in time
- [x] Cleanup some unused things (mesh free list)
This commit is contained in:
IntegratedQuantum 2025-07-21 22:20:17 +02:00 committed by GitHub
parent a8470ea78a
commit 6baf7fc067
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 256 additions and 260 deletions

View File

@ -212,7 +212,7 @@ pub fn generateData(
} }
const sidePos = pos + side.relPos(); 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(); const canConnectToSide = currentBlock.mode() == sideBlock.mode() and currentBlock.modeData() == sideBlock.modeData();
if(canConnectToSide) { if(canConnectToSide) {

View File

@ -401,8 +401,7 @@ pub const BlockEntityTypes = struct {
pub fn updateTextFromClient(pos: Vec3i, newText: []const u8) void { pub fn updateTextFromClient(pos: Vec3i, newText: []const u8) void {
{ {
const mesh = main.renderer.mesh_storage.getMeshAndIncreaseRefCount(.initFromWorldPos(pos, 1)) orelse return; const mesh = main.renderer.mesh_storage.getMesh(.initFromWorldPos(pos, 1)) orelse return;
defer mesh.decreaseRefCount();
mesh.mutex.lock(); mesh.mutex.lock();
defer mesh.mutex.unlock(); defer mesh.mutex.unlock();
const index = mesh.chunk.getLocalBlockIndex(pos); const index = mesh.chunk.getLocalBlockIndex(pos);
@ -478,8 +477,7 @@ pub const BlockEntityTypes = struct {
signData.renderedTexture.?.bindTo(0); signData.renderedTexture.?.bindTo(0);
c.glUniform1i(uniforms.quadIndex, @intFromEnum(quad)); c.glUniform1i(uniforms.quadIndex, @intFromEnum(quad));
const mesh = main.renderer.mesh_storage.getMeshAndIncreaseRefCount(main.chunk.ChunkPosition.initFromWorldPos(signData.blockPos, 1)) orelse continue :outer; const mesh = main.renderer.mesh_storage.getMesh(main.chunk.ChunkPosition.initFromWorldPos(signData.blockPos, 1)) orelse continue :outer;
defer mesh.decreaseRefCount();
mesh.lightingData[0].lock.lockRead(); mesh.lightingData[0].lock.lockRead();
defer mesh.lightingData[0].lock.unlockRead(); defer mesh.lightingData[0].lock.unlockRead();
mesh.lightingData[1].lock.lockRead(); mesh.lightingData[1].lock.lockRead();

View File

@ -190,6 +190,8 @@ pub const ChunkPosition = struct { // MARK: ChunkPosition
return self.equals(notNull); return self.equals(notNull);
} }
return false; 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) { } 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; 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) { } else if(@typeInfo(@TypeOf(other)) == .pointer) {

View File

@ -260,7 +260,7 @@ pub const collision = struct {
while(y <= maxY) : (y += 1) { while(y <= maxY) : (y += 1) {
var z: i32 = maxZ; var z: i32 = maxZ;
while(z >= minZ) : (z -= 1) { 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(_block) |block| {
if(collideWithBlock(block, x, y, z, boundingBoxCenter, fullBoundingBoxExtent, directionVector)) |res| { if(collideWithBlock(block, x, y, z, boundingBoxCenter, fullBoundingBoxExtent, directionVector)) |res| {
if(res.dist < minDistance) { if(res.dist < minDistance) {
@ -298,7 +298,7 @@ pub const collision = struct {
while(x <= maxX) : (x += 1) { while(x <= maxX) : (x += 1) {
var y = minY; var y = minY;
while(y <= maxY) : (y += 1) { 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| { if(_block) |block| {
const blockPos: Vec3d = .{@floatFromInt(x), @floatFromInt(y), @floatFromInt(z)}; const blockPos: Vec3d = .{@floatFromInt(x), @floatFromInt(y), @floatFromInt(z)};
@ -368,7 +368,7 @@ pub const collision = struct {
while(y <= maxY) : (y += 1) { while(y <= maxY) : (y += 1) {
var z: i32 = maxZ; var z: i32 = maxZ;
while(z >= minZ) : (z -= 1) { 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 = .{ const totalBox: Box = .{
.min = @floatFromInt(Vec3i{x, y, z}), .min = @floatFromInt(Vec3i{x, y, z}),
.max = @floatFromInt(Vec3i{x + 1, y + 1, z + 1}), .max = @floatFromInt(Vec3i{x + 1, y + 1, z + 1}),
@ -480,7 +480,7 @@ pub const collision = struct {
var posZ: i32 = minZ; var posZ: i32 = minZ;
while(posZ <= maxZ) : (posZ += 1) { while(posZ <= maxZ) : (posZ += 1) {
const block: ?Block = 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) if(block == null or block.?.touchFunction() == null)
continue; continue;
const touchX: bool = isBlockIntersecting(block.?, posX, posY, posZ, center, extentX); const touchX: bool = isBlockIntersecting(block.?, posX, posY, posZ, center, extentX);
@ -643,9 +643,9 @@ pub const Player = struct { // MARK: Player
pub fn placeBlock() void { pub fn placeBlock() void {
if(main.renderer.MeshSelection.selectedBlockPos) |blockPos| { if(main.renderer.MeshSelection.selectedBlockPos) |blockPos| {
if(!main.KeyBoard.key("shift").pressed) { 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(); const gui = block.gui();
if(gui.len != 0 and !main.KeyBoard.key("shift").pressed) { if(gui.len != 0 and !main.KeyBoard.key("shift").pressed) {
main.gui.openWindow(gui); main.gui.openWindow(gui);
@ -677,7 +677,7 @@ pub const Player = struct { // MARK: Player
pub fn acquireSelectedBlock() void { pub fn acquireSelectedBlock() void {
if(main.renderer.MeshSelection.selectedBlockPos) |selectedPos| { 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 item: items.Item = for(0..items.itemListSize) |idx| {
const baseItem: main.items.BaseItemIndex = @enumFromInt(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 airFrictionCoefficient = gravity/airTerminalVelocity; // λ = a/v in equillibrium
const playerDensity = 1.2; const playerDensity = 1.2;
var move: Vec3d = .{0, 0, 0}; 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 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 effectiveGravity = gravity*(playerDensity - volumeProperties.density)/playerDensity;
const volumeFrictionCoeffecient: f32 = @floatCast(gravity/volumeProperties.terminalVelocity); const volumeFrictionCoeffecient: f32 = @floatCast(gravity/volumeProperties.terminalVelocity);

View File

@ -55,10 +55,12 @@ pub fn initThreadLocals() void {
seed = @bitCast(@as(i64, @truncate(std.time.nanoTimestamp()))); seed = @bitCast(@as(i64, @truncate(std.time.nanoTimestamp())));
stackAllocatorBase = heap.StackAllocator.init(globalAllocator, 1 << 23); stackAllocatorBase = heap.StackAllocator.init(globalAllocator, 1 << 23);
stackAllocator = stackAllocatorBase.allocator(); stackAllocator = stackAllocatorBase.allocator();
heap.GarbageCollection.addThread();
} }
pub fn deinitThreadLocals() void { pub fn deinitThreadLocals() void {
stackAllocatorBase.deinit(); stackAllocatorBase.deinit();
heap.GarbageCollection.removeThread();
} }
fn cacheStringImpl(comptime len: usize, comptime str: [len]u8) []const u8 { 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) { defer if(global_gpa.deinit() == .leak) {
std.log.err("Memory leak", .{}); std.log.err("Memory leak", .{});
}; };
defer heap.GarbageCollection.assertAllThreadsStopped();
initThreadLocals(); initThreadLocals();
defer deinitThreadLocals(); defer deinitThreadLocals();
@ -672,6 +675,7 @@ pub fn main() void { // MARK: main()
audio.setMusic("cubyz:cubyz"); audio.setMusic("cubyz:cubyz");
while(c.glfwWindowShouldClose(Window.window) == 0) { while(c.glfwWindowShouldClose(Window.window) == 0) {
heap.GarbageCollection.syncPoint();
const isHidden = c.glfwGetWindowAttrib(Window.window, c.GLFW_ICONIFIED) == c.GLFW_TRUE; const isHidden = c.glfwGetWindowAttrib(Window.window, c.GLFW_ICONIFIED) == c.GLFW_TRUE;
if(!isHidden) { if(!isHidden) {
c.glfwSwapBuffers(Window.window); c.glfwSwapBuffers(Window.window);

View File

@ -587,6 +587,7 @@ pub const ConnectionManager = struct { // MARK: ConnectionManager
var lastTime: i64 = networkTimestamp(); var lastTime: i64 = networkTimestamp();
while(self.running.load(.monotonic)) { while(self.running.load(.monotonic)) {
main.heap.GarbageCollection.syncPoint();
self.waitingToFinishReceive.broadcast(); self.waitingToFinishReceive.broadcast();
var source: Address = undefined; var source: Address = undefined;
if(self.socket.receive(&self.receiveBuffer, 1, &source)) |data| { 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 { pub fn sendClientDataUpdateToServer(conn: *Connection, pos: Vec3i) void {
const mesh = main.renderer.mesh_storage.getMeshAndIncreaseRefCount(.initFromWorldPos(pos, 1)) orelse return; const mesh = main.renderer.mesh_storage.getMesh(.initFromWorldPos(pos, 1)) orelse return;
defer mesh.decreaseRefCount();
mesh.mutex.lock(); mesh.mutex.lock();
defer mesh.mutex.unlock(); defer mesh.mutex.unlock();
const index = mesh.chunk.getLocalBlockIndex(pos); const index = mesh.chunk.getLocalBlockIndex(pos);

View File

@ -287,7 +287,7 @@ pub fn renderWorld(world: *World, ambientLight: Vec3f, skyColor: Vec3f, playerPo
worldFrameBuffer.bindTexture(c.GL_TEXTURE3); 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) { if(settings.bloom) {
Bloom.render(lastWidth, lastHeight, playerBlock, playerPos, game.camera.viewMatrix); Bloom.render(lastWidth, lastHeight, playerBlock, playerPos, game.camera.viewMatrix);
@ -915,7 +915,7 @@ pub const MeshSelection = struct { // MARK: MeshSelection
selectedBlockPos = null; selectedBlockPos = null;
while(total_tMax < closestDistance) { 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: { if(block.typ != 0) blk: {
const fluidPlaceable = item != null and item.? == .baseItem and item.?.baseItem.hasTag(.fluidPlaceable); const fluidPlaceable = item != null and item.? == .baseItem and item.?.baseItem.hasTag(.fluidPlaceable);
for(block.blockTags()) |tag| { for(block.blockTags()) |tag| {
@ -971,7 +971,7 @@ pub const MeshSelection = struct { // MARK: MeshSelection
pub fn placeBlock(inventory: main.items.Inventory, slot: u32) void { pub fn placeBlock(inventory: main.items.Inventory, slot: u32) void {
if(selectedBlockPos) |selectedPos| { 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; var block = oldBlock;
if(inventory.getItem(slot)) |item| { if(inventory.getItem(slot)) |item| {
switch(item) { switch(item) {
@ -999,7 +999,7 @@ pub const MeshSelection = struct { // MARK: MeshSelection
neighborDir = selectedPos - posBeforeBlock; neighborDir = selectedPos - posBeforeBlock;
const relPos: Vec3f = @floatCast(lastPos - @as(Vec3d, @floatFromInt(neighborPos))); const relPos: Vec3f = @floatCast(lastPos - @as(Vec3d, @floatFromInt(neighborPos)));
const neighborBlock = block; 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; block = oldBlock;
if(block.typ == itemBlock) { if(block.typ == itemBlock) {
if(rotationMode.generateData(main.game.world.?, neighborPos, relPos, lastDir, neighborDir, neighborOfSelection, &block, neighborBlock, false)) { 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; lastSelectedBlockPos = selectedPos;
currentBlockProgress = 0; 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)) { if(block.hasTag(.fluid) or block.hasTag(.air)) {
return; return;
} }

View File

@ -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) { if(x == x & chunk.chunkMask and y == y & chunk.chunkMask and z == z & chunk.chunkMask) {
return getValues(parent, wx, wy, wz); 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}; const neighborMesh = mesh_storage.getMesh(.{.wx = wx, .wy = wy, .wz = wz, .voxelSize = parent.pos.voxelSize}) orelse return .{0, 0, 0, 0, 0, 0};
defer neighborMesh.decreaseRefCount();
neighborMesh.lightingData[0].lock.lockRead(); neighborMesh.lightingData[0].lock.lockRead();
neighborMesh.lightingData[1].lock.lockRead(); neighborMesh.lightingData[1].lock.lockRead();
defer neighborMesh.lightingData[0].lock.unlockRead(); defer neighborMesh.lightingData[0].lock.unlockRead();
@ -692,7 +691,8 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh
blockBreakingFacesSortingData: []SortingData = &.{}, blockBreakingFacesSortingData: []SortingData = &.{},
blockBreakingFacesChanged: bool = false, 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{ self.* = ChunkMesh{
.pos = pos, .pos = pos,
.size = chunk.chunkSize*pos.voxelSize, .size = chunk.chunkSize*pos.voxelSize,
@ -709,10 +709,10 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh
}, },
.blockBreakingFaces = .init(main.globalAllocator), .blockBreakingFaces = .init(main.globalAllocator),
}; };
return self;
} }
pub fn deinit(self: *ChunkMesh) void { pub fn deinit(self: *ChunkMesh, _: usize) void {
std.debug.assert(self.refCount.load(.monotonic) == 0);
chunkBuffer.free(self.chunkAllocation); chunkBuffer.free(self.chunkAllocation);
self.opaqueMesh.deinit(); self.opaqueMesh.deinit();
self.transparentMesh.deinit(); self.transparentMesh.deinit();
@ -727,35 +727,14 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh
main.globalAllocator.free(self.blockBreakingFacesSortingData); main.globalAllocator.free(self.blockBreakingFacesSortingData);
main.globalAllocator.free(self.lightList); main.globalAllocator.free(self.lightList);
lightBuffers[std.math.log2_int(u32, self.pos.voxelSize)].free(self.lightAllocation); lightBuffers[std.math.log2_int(u32, self.pos.voxelSize)].free(self.lightAllocation);
mesh_storage.meshMemoryPool.destroy(self);
} }
pub fn increaseRefCount(self: *ChunkMesh) void { pub fn scheduleLightRefresh(pos: chunk.ChunkPosition) void {
const prevVal = self.refCount.fetchAdd(1, .monotonic); LightRefreshTask.schedule(pos);
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);
} }
const LightRefreshTask = struct { const LightRefreshTask = struct {
mesh: *ChunkMesh, pos: chunk.ChunkPosition,
pub const vtable = main.utils.ThreadPool.VTable{ pub const vtable = main.utils.ThreadPool.VTable{
.getPriority = main.utils.castFunctionSelfToAnyopaque(getPriority), .getPriority = main.utils.castFunctionSelfToAnyopaque(getPriority),
@ -765,10 +744,10 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh
.taskType = .misc, .taskType = .misc,
}; };
pub fn scheduleAndDecreaseRefCount(mesh: *ChunkMesh) void { pub fn schedule(pos: chunk.ChunkPosition) void {
const task = main.globalAllocator.create(LightRefreshTask); const task = main.globalAllocator.create(LightRefreshTask);
task.* = .{ task.* = .{
.mesh = mesh, .pos = pos,
}; };
main.threadPool.addTask(task, &vtable); main.threadPool.addTask(task, &vtable);
} }
@ -778,23 +757,21 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh
} }
pub fn isStillNeeded(_: *LightRefreshTask) bool { pub fn isStillNeeded(_: *LightRefreshTask) bool {
return true; // TODO: Is it worth checking for this? return true;
} }
pub fn run(self: *LightRefreshTask) void { pub fn run(self: *LightRefreshTask) void {
if(self.mesh.needsLightRefresh.swap(false, .acq_rel)) { defer main.globalAllocator.destroy(self);
self.mesh.mutex.lock(); const mesh = mesh_storage.getMesh(self.pos) orelse return;
self.mesh.finishData(); if(mesh.needsLightRefresh.swap(false, .acq_rel)) {
self.mesh.mutex.unlock(); mesh.mutex.lock();
mesh_storage.addToUpdateListAndDecreaseRefCount(self.mesh); mesh.finishData();
} else { mesh.mutex.unlock();
self.mesh.decreaseRefCount(); mesh_storage.addToUpdateList(mesh);
} }
main.globalAllocator.destroy(self);
} }
pub fn clean(self: *LightRefreshTask) void { pub fn clean(self: *LightRefreshTask) void {
self.mesh.decreaseRefCount();
main.globalAllocator.destroy(self); 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()]); 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(); self.mutex.lock();
var lightEmittingBlocks = main.List([3]u8).init(main.stackAllocator); var lightEmittingBlocks = main.List([3]u8).init(main.stackAllocator);
defer lightEmittingBlocks.deinit(); 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); 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(); defer lightRefreshList.deinit();
self.initLight(&lightRefreshList); self.initLight(&lightRefreshList);
@ -876,8 +853,7 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh
pos.wx +%= pos.voxelSize*chunk.chunkSize*dx; pos.wx +%= pos.voxelSize*chunk.chunkSize*dx;
pos.wy +%= pos.voxelSize*chunk.chunkSize*dy; pos.wy +%= pos.voxelSize*chunk.chunkSize*dy;
pos.wz +%= pos.voxelSize*chunk.chunkSize*dz; pos.wz +%= pos.voxelSize*chunk.chunkSize*dz;
const neighborMesh = mesh_storage.getMeshAndIncreaseRefCount(pos) orelse continue; const neighborMesh = mesh_storage.getMesh(pos) orelse continue;
defer neighborMesh.decreaseRefCount();
const shiftSelf: u5 = @intCast(((dx + 1)*3 + dy + 1)*3 + dz + 1); const shiftSelf: u5 = @intCast(((dx + 1)*3 + dy + 1)*3 + dz + 1);
const shiftOther: 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| { for(lightRefreshList.items) |pos| {
if(other.needsLightRefresh.load(.unordered)) { scheduleLightRefresh(pos);
other.scheduleLightRefreshAndDecreaseRefCount();
} else {
other.decreaseRefCount();
}
} }
} }
@ -913,7 +885,7 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh
model.appendNeighborFacingQuadsToList(list, allocator, block, neighbor, x, y, z, backFace); 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; var alwaysViewThroughMask: [chunk.chunkSize][chunk.chunkSize]u32 = undefined;
@memset(std.mem.asBytes(&alwaysViewThroughMask), 0); @memset(std.mem.asBytes(&alwaysViewThroughMask), 0);
var alwaysViewThroughMask2: [chunk.chunkSize][chunk.chunkSize]u32 = undefined; var alwaysViewThroughMask2: [chunk.chunkSize][chunk.chunkSize]u32 = undefined;
@ -1213,7 +1185,7 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh
self.finishNeighbors(lightRefreshList); 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| { for(self.lightingData[0..]) |lightingData| {
lightingData.propagateLightsDestructive(&.{.{x, y, z}}, lightRefreshList); 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 x: u5 = @intCast(_x & chunk.chunkMask);
const y: u5 = @intCast(_y & chunk.chunkMask); const y: u5 = @intCast(_y & chunk.chunkMask);
const z: u5 = @intCast(_z & 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 nny: u5 = @intCast(ny & chunk.chunkMask);
const nnz: u5 = @intCast(nz & chunk.chunkMask); const nnz: u5 = @intCast(nz & chunk.chunkMask);
const neighborChunkMesh = mesh_storage.getNeighborAndIncreaseRefCount(self.pos, self.pos.voxelSize, neighbor) orelse continue; const neighborChunkMesh = mesh_storage.getNeighbor(self.pos, self.pos.voxelSize, neighbor) orelse continue;
defer neighborChunkMesh.decreaseRefCount();
const index = chunk.getIndex(nnx, nny, nnz); const index = chunk.getIndex(nnx, nny, nnz);
@ -1342,7 +1313,6 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh
return; return;
} }
} }
mesh.increaseRefCount();
list.append(mesh); 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| { 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: { if(nullNeighborMesh) |neighborMesh| sameLodBlock: {
defer neighborMesh.decreaseRefCount();
std.debug.assert(neighborMesh != self); std.debug.assert(neighborMesh != self);
deadlockFreeDoubleLock(&self.mutex, &neighborMesh.mutex); deadlockFreeDoubleLock(&self.mutex, &neighborMesh.mutex);
defer self.mutex.unlock(); defer self.mutex.unlock();
@ -1470,8 +1439,7 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh
neighborMesh.transparentMesh.replaceRange(.neighbor(neighbor.reverse()), transparentNeighbor.items); neighborMesh.transparentMesh.replaceRange(.neighbor(neighbor.reverse()), transparentNeighbor.items);
_ = neighborMesh.needsLightRefresh.store(true, .release); _ = neighborMesh.needsLightRefresh.store(true, .release);
neighborMesh.increaseRefCount(); lightRefreshList.append(neighborMesh.pos);
lightRefreshList.append(neighborMesh);
} else { } else {
self.mutex.lock(); self.mutex.lock();
defer self.mutex.unlock(); defer self.mutex.unlock();
@ -1483,7 +1451,7 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh
} }
// lod border: // lod border:
if(self.pos.voxelSize == @as(u31, 1) << settings.highestLod) continue; 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(); self.mutex.lock();
defer self.mutex.unlock(); defer self.mutex.unlock();
if(self.lastNeighborsHigherLod[neighbor.toInt()] != null) { if(self.lastNeighborsHigherLod[neighbor.toInt()] != null) {
@ -1493,7 +1461,6 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh
} }
continue; continue;
}; };
defer neighborMesh.decreaseRefCount();
deadlockFreeDoubleLock(&self.mutex, &neighborMesh.mutex); deadlockFreeDoubleLock(&self.mutex, &neighborMesh.mutex);
defer self.mutex.unlock(); defer self.mutex.unlock();
defer neighborMesh.mutex.unlock(); defer neighborMesh.mutex.unlock();
@ -1557,7 +1524,7 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh
defer self.mutex.unlock(); defer self.mutex.unlock();
_ = self.needsLightRefresh.swap(false, .acq_rel); _ = self.needsLightRefresh.swap(false, .acq_rel);
self.finishData(); self.finishData();
mesh_storage.finishMesh(self); mesh_storage.finishMesh(self.pos);
} }
fn uploadChunkPosition(self: *ChunkMesh) void { fn uploadChunkPosition(self: *ChunkMesh) void {

View File

@ -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(.{}); var neighborLists: [6]main.ListUnmanaged(Entry) = @splat(.{});
defer { defer {
for(&neighborLists) |*list| { for(&neighborLists) |*list| {
@ -145,26 +145,24 @@ pub const ChannelChunk = struct {
for(chunk.Neighbor.iterable) |neighbor| { for(chunk.Neighbor.iterable) |neighbor| {
if(neighborLists[neighbor.toInt()].items.len == 0) continue; 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;
defer neighborMesh.decreaseRefCount();
neighborMesh.lightingData[@intFromBool(self.isSun)].propagateFromNeighbor(lightQueue, neighborLists[neighbor.toInt()].items, lightRefreshList); neighborMesh.lightingData[@intFromBool(self.isSun)].propagateFromNeighbor(lightQueue, neighborLists[neighbor.toInt()].items, lightRefreshList);
} }
} }
fn addSelfToLightRefreshList(self: *ChannelChunk, lightRefreshList: *main.List(*chunk_meshing.ChunkMesh)) void { fn addSelfToLightRefreshList(self: *ChannelChunk, lightRefreshList: *main.List(chunk.ChunkPosition)) void {
if(mesh_storage.getMeshAndIncreaseRefCount(self.ch.pos)) |mesh| { for(lightRefreshList.items) |other| {
for(lightRefreshList.items) |other| { if(self.ch.pos.equals(other)) {
if(mesh == other) { return;
mesh.decreaseRefCount();
return;
}
} }
}
if(mesh_storage.getMesh(self.ch.pos)) |mesh| {
mesh.needsLightRefresh.store(true, .release); 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 neighborLists: [6]main.ListUnmanaged(Entry) = @splat(.{});
var constructiveList: main.ListUnmanaged(PositionEntry) = .{}; var constructiveList: main.ListUnmanaged(PositionEntry) = .{};
defer { defer {
@ -238,7 +236,7 @@ pub const ChannelChunk = struct {
for(chunk.Neighbor.iterable) |neighbor| { for(chunk.Neighbor.iterable) |neighbor| {
if(neighborLists[neighbor.toInt()].items.len == 0) continue; 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, .{ constructiveEntries.append(main.stackAllocator, .{
.mesh = neighborMesh, .mesh = neighborMesh,
.entries = neighborMesh.lightingData[@intFromBool(self.isSun)].propagateDestructiveFromNeighbor(lightQueue, neighborLists[neighbor.toInt()].items, constructiveEntries, lightRefreshList), .entries = neighborMesh.lightingData[@intFromBool(self.isSun)].propagateDestructiveFromNeighbor(lightQueue, neighborLists[neighbor.toInt()].items, constructiveEntries, lightRefreshList),
@ -248,7 +246,7 @@ pub const ChannelChunk = struct {
return constructiveList; 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()); std.debug.assert(lightQueue.empty());
for(lights) |entry| { for(lights) |entry| {
const index = chunk.getIndex(entry.x, entry.y, entry.z); const index = chunk.getIndex(entry.x, entry.y, entry.z);
@ -259,7 +257,7 @@ pub const ChannelChunk = struct {
self.propagateDirect(lightQueue, lightRefreshList); 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()); std.debug.assert(lightQueue.empty());
for(lights) |entry| { for(lights) |entry| {
const index = chunk.getIndex(entry.x, entry.y, entry.z); 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); 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); var lightQueue = main.utils.CircularBufferQueue(Entry).init(main.stackAllocator, 1 << 12);
defer lightQueue.deinit(); defer lightQueue.deinit();
for(lights) |pos| { for(lights) |pos| {
@ -307,8 +305,7 @@ pub const ChannelChunk = struct {
const otherX = x +% neighbor.relX() & chunk.chunkMask; const otherX = x +% neighbor.relX() & chunk.chunkMask;
const otherY = y +% neighbor.relY() & chunk.chunkMask; const otherY = y +% neighbor.relY() & chunk.chunkMask;
const otherZ = z +% neighbor.relZ() & chunk.chunkMask; const otherZ = z +% neighbor.relZ() & chunk.chunkMask;
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;
defer neighborMesh.decreaseRefCount();
const neighborLightChunk = neighborMesh.lightingData[@intFromBool(self.isSun)]; const neighborLightChunk = neighborMesh.lightingData[@intFromBool(self.isSun)];
neighborLightChunk.lock.lockRead(); neighborLightChunk.lock.lockRead();
defer neighborLightChunk.lock.unlockRead(); defer neighborLightChunk.lock.unlockRead();
@ -331,7 +328,7 @@ pub const ChannelChunk = struct {
self.propagateDirect(&lightQueue, lightRefreshList); 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); std.debug.assert(self.isSun);
self.lock.lockWrite(); self.lock.lockWrite();
if(self.data.paletteLength != 1) { if(self.data.paletteLength != 1) {
@ -345,8 +342,7 @@ pub const ChannelChunk = struct {
defer lightQueue.deinit(); defer lightQueue.deinit();
for(chunk.Neighbor.iterable) |neighbor| { for(chunk.Neighbor.iterable) |neighbor| {
if(neighbor == .dirUp) continue; if(neighbor == .dirUp) 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;
defer neighborMesh.decreaseRefCount();
var list: [chunk.chunkSize*chunk.chunkSize]Entry = undefined; var list: [chunk.chunkSize*chunk.chunkSize]Entry = undefined;
for(0..chunk.chunkSize) |x| { for(0..chunk.chunkSize) |x| {
for(0..chunk.chunkSize) |y| { 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); var lightQueue = main.utils.CircularBufferQueue(Entry).init(main.stackAllocator, 1 << 12);
defer lightQueue.deinit(); defer lightQueue.deinit();
self.lock.lockRead(); self.lock.lockRead();
@ -396,7 +392,6 @@ pub const ChannelChunk = struct {
}); });
for(constructiveEntries.items) |entries| { for(constructiveEntries.items) |entries| {
const mesh = entries.mesh; const mesh = entries.mesh;
defer if(mesh) |_mesh| _mesh.decreaseRefCount();
var entryList = entries.entries; var entryList = entries.entries;
defer entryList.deinit(main.stackAllocator); defer entryList.deinit(main.stackAllocator);
const channelChunk = if(mesh) |_mesh| _mesh.lightingData[@intFromBool(self.isSun)] else self; const channelChunk = if(mesh) |_mesh| _mesh.lightingData[@intFromBool(self.isSun)] else self;

View File

@ -22,24 +22,22 @@ const chunk_meshing = @import("chunk_meshing.zig");
const ChunkMesh = chunk_meshing.ChunkMesh; const ChunkMesh = chunk_meshing.ChunkMesh;
const ChunkMeshNode = struct { const ChunkMeshNode = struct {
mesh: ?*chunk_meshing.ChunkMesh = null, mesh: Atomic(?*chunk_meshing.ChunkMesh) = .init(null),
active: bool = false, active: bool = false,
rendered: bool = false, rendered: bool = false,
finishedMeshing: bool = false, // Must be synced with mesh.finishedMeshing finishedMeshing: bool = false, // Must be synced with mesh.finishedMeshing
finishedMeshingHigherResolution: u8 = 0, // Must be synced with finishedMeshing of the 8 higher resolution chunks. finishedMeshingHigherResolution: u8 = 0, // Must be synced with finishedMeshing of the 8 higher resolution chunks.
pos: chunk.ChunkPosition = undefined, pos: chunk.ChunkPosition = undefined,
isNeighborLod: [6]bool = @splat(false), // Must be synced with mesh.isNeighborLod isNeighborLod: [6]bool = @splat(false), // Must be synced with mesh.isNeighborLod
mutex: std.Thread.Mutex = .{},
}; };
const storageSize = 64; const storageSize = 64;
const storageMask = storageSize - 1; const storageMask = storageSize - 1;
var storageLists: [settings.highestSupportedLod + 1]*[storageSize*storageSize*storageSize]ChunkMeshNode = undefined; var storageLists: [settings.highestSupportedLod + 1]*[storageSize*storageSize*storageSize]ChunkMeshNode = undefined;
var mapStorageLists: [settings.highestSupportedLod + 1]*[storageSize*storageSize]?*LightMap.LightMapFragment = undefined; var mapStorageLists: [settings.highestSupportedLod + 1]*[storageSize*storageSize]?*LightMap.LightMapFragment = undefined;
var meshList = main.List(*chunk_meshing.ChunkMesh).init(main.globalAllocator); var meshList = main.List(*chunk_meshing.ChunkMesh).init(main.globalAllocator);
var priorityMeshUpdateList: main.utils.ConcurrentQueue(*chunk_meshing.ChunkMesh) = undefined; var priorityMeshUpdateList: main.utils.ConcurrentQueue(chunk.ChunkPosition) = undefined;
pub var updatableList = main.List(*chunk_meshing.ChunkMesh).init(main.globalAllocator); pub var updatableList = main.List(chunk.ChunkPosition).init(main.globalAllocator);
var mapUpdatableList: main.utils.ConcurrentQueue(*LightMap.LightMapFragment) = undefined; var mapUpdatableList: main.utils.ConcurrentQueue(*LightMap.LightMapFragment) = undefined;
var clearList = main.List(*chunk_meshing.ChunkMesh).init(main.globalAllocator);
var lastPx: i32 = 0; var lastPx: i32 = 0;
var lastPy: i32 = 0; var lastPy: i32 = 0;
var lastPz: i32 = 0; var lastPz: i32 = 0;
@ -74,7 +72,7 @@ pub const BlockUpdate = struct {
var blockUpdateList: main.utils.ConcurrentQueue(BlockUpdate) = undefined; 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() pub fn init() void { // MARK: init()
lastRD = 0; lastRD = 0;
@ -104,6 +102,7 @@ pub fn deinit() void {
lastPz = 0; lastPz = 0;
lastRD = 0; lastRD = 0;
freeOldMeshes(olderPx, olderPy, olderPz, olderRD); freeOldMeshes(olderPx, olderPy, olderPz, olderRD);
main.heap.GarbageCollection.waitForFreeCompletion();
for(storageLists) |storageList| { for(storageLists) |storageList| {
main.globalAllocator.destroy(storageList); main.globalAllocator.destroy(storageList);
} }
@ -111,28 +110,17 @@ pub fn deinit() void {
main.globalAllocator.destroy(mapStorageList); main.globalAllocator.destroy(mapStorageList);
} }
for(updatableList.items) |mesh| {
mesh.decreaseRefCount();
}
updatableList.clearAndFree(); updatableList.clearAndFree();
while(mapUpdatableList.dequeue()) |map| { while(mapUpdatableList.dequeue()) |map| {
map.decreaseRefCount(); map.decreaseRefCount();
} }
mapUpdatableList.deinit(); mapUpdatableList.deinit();
while(priorityMeshUpdateList.dequeue()) |mesh| {
mesh.decreaseRefCount();
}
priorityMeshUpdateList.deinit(); priorityMeshUpdateList.deinit();
while(blockUpdateList.dequeue()) |blockUpdate| { while(blockUpdateList.dequeue()) |blockUpdate| {
blockUpdate.deinitManaged(main.globalAllocator); blockUpdate.deinitManaged(main.globalAllocator);
} }
blockUpdateList.deinit(); blockUpdateList.deinit();
meshList.clearAndFree(); meshList.clearAndFree();
for(clearList.items) |mesh| {
mesh.deinit();
meshMemoryPool.destroy(mesh);
}
clearList.clearAndFree();
meshMemoryPool.deinit(); meshMemoryPool.deinit();
} }
@ -191,20 +179,16 @@ pub fn getLightMapPieceAndIncreaseRefCount(x: i32, y: i32, voxelSize: u31) ?*Lig
return result; 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}); const node = getNodePointer(.{.wx = x, .wy = y, .wz = z, .voxelSize = 1});
node.mutex.lock(); const mesh = node.mesh.load(.acquire) orelse return null;
defer node.mutex.unlock();
const mesh = node.mesh orelse return null;
const block = mesh.chunk.getBlock(x & chunk.chunkMask, y & chunk.chunkMask, z & chunk.chunkMask); const block = mesh.chunk.getBlock(x & chunk.chunkMask, y & chunk.chunkMask, z & chunk.chunkMask);
return block; 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}); const node = getNodePointer(.{.wx = x, .wy = y, .wz = z, .voxelSize = 1});
node.mutex.lock(); const mesh = node.mesh.load(.acquire) orelse return .ignored;
defer node.mutex.unlock();
const mesh = node.mesh orelse return .ignored;
const block = mesh.chunk.getBlock(x & chunk.chunkMask, y & chunk.chunkMask, z & chunk.chunkMask); const block = mesh.chunk.getBlock(x & chunk.chunkMask, y & chunk.chunkMask, z & chunk.chunkMask);
if(block.blockEntity()) |blockEntity| { if(block.blockEntity()) |blockEntity| {
return blockEntity.onInteract(.{x, y, z}, mesh.chunk); 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 { pub fn getLight(wx: i32, wy: i32, wz: i32) ?[6]u8 {
const node = getNodePointer(.{.wx = wx, .wy = wy, .wz = wz, .voxelSize = 1}); const node = getNodePointer(.{.wx = wx, .wy = wy, .wz = wz, .voxelSize = 1});
node.mutex.lock(); const mesh = node.mesh.load(.acquire) orelse return null;
defer node.mutex.unlock();
const mesh = node.mesh orelse return null;
const x = (wx >> mesh.chunk.voxelSizeShift) & chunk.chunkMask; const x = (wx >> mesh.chunk.voxelSizeShift) & chunk.chunkMask;
const y = (wy >> mesh.chunk.voxelSizeShift) & chunk.chunkMask; const y = (wy >> mesh.chunk.voxelSizeShift) & chunk.chunkMask;
const z = (wz >> 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); 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; var lod: u5 = 0;
while(lod < settings.highestLod) : (lod += 1) { while(lod < settings.highestLod) : (lod += 1) {
const node = getNodePointer(.{.wx = x, .wy = y, .wz = z, .voxelSize = @as(u31, 1) << lod}); const node = getNodePointer(.{.wx = x, .wy = y, .wz = z, .voxelSize = @as(u31, 1) << lod});
node.mutex.lock(); const mesh = node.mesh.load(.acquire) orelse continue;
defer node.mutex.unlock();
const mesh = node.mesh orelse continue;
const block = mesh.chunk.getBlock(x & chunk.chunkMask << lod, y & chunk.chunkMask << lod, z & chunk.chunkMask << lod); const block = mesh.chunk.getBlock(x & chunk.chunkMask << lod, y & chunk.chunkMask << lod, z & chunk.chunkMask << lod);
return block; return block;
} }
return blocks.Block{.typ = 0, .data = 0}; 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 lod = std.math.log2_int(u31, pos.voxelSize);
const mask = ~((@as(i32, 1) << lod + chunk.chunkShift) - 1); const mask = ~((@as(i32, 1) << lod + chunk.chunkShift) - 1);
const node = getNodePointer(pos); const node = getNodePointer(pos);
node.mutex.lock(); const mesh = node.mesh.load(.acquire) orelse return null;
const mesh = node.mesh orelse {
node.mutex.unlock();
return null;
};
mesh.increaseRefCount();
node.mutex.unlock();
if(pos.wx & mask != mesh.pos.wx or pos.wy & mask != mesh.pos.wy or pos.wz & mask != mesh.pos.wz) { 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 null;
} }
return mesh; 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); var lod: u5 = @ctz(voxelSize);
while(lod < settings.highestLod) : (lod += 1) { 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 mesh orelse continue;
} }
return null; 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; var pos = _pos;
pos.wx +%= pos.voxelSize*chunk.chunkSize*neighbor.relX(); pos.wx +%= pos.voxelSize*chunk.chunkSize*neighbor.relX();
pos.wy +%= pos.voxelSize*chunk.chunkSize*neighbor.relY(); pos.wy +%= pos.voxelSize*chunk.chunkSize*neighbor.relY();
pos.wz +%= pos.voxelSize*chunk.chunkSize*neighbor.relZ(); pos.wz +%= pos.voxelSize*chunk.chunkSize*neighbor.relZ();
pos.voxelSize = resolution; pos.voxelSize = resolution;
return getMeshAndIncreaseRefCount(pos); return getMesh(pos);
} }
fn reduceRenderDistance(fullRenderDistance: i64, reduction: i64) i32 { 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 index = (xIndex*storageSize + yIndex)*storageSize + zIndex;
const node = &storageLists[_lod][@intCast(index)]; const node = &storageLists[_lod][@intCast(index)];
node.mutex.lock(); const oldMesh = node.mesh.swap(null, .monotonic);
const oldMesh = node.mesh;
node.mesh = null;
node.mutex.unlock();
node.pos = undefined; node.pos = undefined;
if(oldMesh) |mesh| { if(oldMesh) |mesh| {
node.finishedMeshing = false; node.finishedMeshing = false;
updateHigherLodNodeFinishedMeshing(mesh.pos, false); updateHigherLodNodeFinishedMeshing(mesh.pos, false);
mesh.decreaseRefCount(); main.heap.GarbageCollection.deferredFree(.{
.ptr = mesh,
.freeFunction = main.utils.castFunctionSelfToAnyopaque(ChunkMesh.deinit),
});
} }
node.isNeighborLod = @splat(false); 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 pos = chunk.ChunkPosition{.wx = x, .wy = y, .wz = z, .voxelSize = @as(u31, 1) << lod};
const node = &storageLists[_lod][@intCast(index)]; const node = &storageLists[_lod][@intCast(index)];
node.mutex.lock();
node.pos = pos; node.pos = pos;
if(node.mesh) |mesh| { if(node.mesh.load(.acquire)) |mesh| {
std.debug.assert(std.meta.eql(pos, mesh.pos)); std.debug.assert(std.meta.eql(pos, mesh.pos));
} else { } else {
meshRequests.append(pos); 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)) { 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; mesh.isNeighborLod = isNeighborLod;
node.isNeighborLod = isNeighborLod; node.isNeighborLod = isNeighborLod;
mesh.uploadData(); mesh.uploadData();
@ -766,14 +737,12 @@ pub noinline fn updateAndGetRenderChunks(conn: *network.Connection, frustum: *co
node.rendered = false; node.rendered = false;
if(!node.finishedMeshing) continue; 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) { if(mesh.needsMeshUpdate) {
mesh.uploadData(); mesh.uploadData();
mesh.needsMeshUpdate = false; mesh.needsMeshUpdate = false;
} }
node.mutex.unlock();
// Remove empty meshes. // Remove empty meshes.
if(!mesh.isEmpty()) { if(!mesh.isEmpty()) {
meshList.append(mesh); meshList.append(mesh);
@ -788,32 +757,14 @@ pub fn updateMeshes(targetTime: i64) void { // MARK: updateMeshes()=
mutex.lock(); mutex.lock();
defer mutex.unlock(); defer mutex.unlock();
for(clearList.items) |mesh| { while(priorityMeshUpdateList.dequeue()) |pos| {
mesh.deinit(); const mesh = getMesh(pos) orelse continue;
meshMemoryPool.destroy(mesh);
}
clearList.clearRetainingCapacity();
while(priorityMeshUpdateList.dequeue()) |mesh| {
if(!mesh.needsMeshUpdate) { if(!mesh.needsMeshUpdate) {
mutex.unlock();
defer mutex.lock();
mesh.decreaseRefCount();
continue; continue;
} }
mesh.needsMeshUpdate = false; 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(); mutex.unlock();
defer mutex.lock(); defer mutex.lock();
mesh.decreaseRefCount();
mesh.uploadData(); mesh.uploadData();
if(std.time.milliTimestamp() >= targetTime) break; // Update at least one mesh. 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; var i: usize = 0;
while(i < updatableList.items.len) { while(i < updatableList.items.len) {
const mesh = updatableList.items[i]; const pos = updatableList.items[i];
if(!isInRenderDistance(mesh.pos)) { if(!isInRenderDistance(pos)) {
_ = updatableList.swapRemove(i); _ = updatableList.swapRemove(i);
mutex.unlock(); mutex.unlock();
defer mutex.lock(); defer mutex.lock();
mesh.decreaseRefCount();
continue; continue;
} }
const priority = mesh.pos.getPriority(playerPos); const priority = pos.getPriority(playerPos);
if(priority > closestPriority) { if(priority > closestPriority) {
closestPriority = priority; closestPriority = priority;
closestIndex = i; closestIndex = i;
@ -853,32 +803,24 @@ pub fn updateMeshes(targetTime: i64) void { // MARK: updateMeshes()=
} }
if(updatableList.items.len == 0) break; if(updatableList.items.len == 0) break;
} }
const mesh = updatableList.swapRemove(closestIndex); const pos = updatableList.swapRemove(closestIndex);
mutex.unlock(); mutex.unlock();
defer mutex.lock(); defer mutex.lock();
if(isInRenderDistance(mesh.pos)) { if(isInRenderDistance(pos)) {
const node = getNodePointer(mesh.pos); const node = getNodePointer(pos);
std.debug.assert(std.meta.eql(node.pos, mesh.pos)); if(node.finishedMeshing) continue;
const mesh = getMesh(pos) orelse continue;
node.finishedMeshing = true; node.finishedMeshing = true;
mesh.finishedMeshing = true; mesh.finishedMeshing = true;
updateHigherLodNodeFinishedMeshing(mesh.pos, true); updateHigherLodNodeFinishedMeshing(pos, true);
mesh.uploadData(); 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. if(std.time.milliTimestamp() >= targetTime) break; // Update at least one mesh.
} }
} }
fn batchUpdateBlocks() void { fn batchUpdateBlocks() void {
var lightRefreshList = main.List(*ChunkMesh).init(main.stackAllocator); var lightRefreshList = main.List(chunk.ChunkPosition).init(main.stackAllocator);
defer lightRefreshList.deinit(); defer lightRefreshList.deinit();
var regenerateMeshList = main.List(*ChunkMesh).init(main.stackAllocator); var regenerateMeshList = main.List(*ChunkMesh).init(main.stackAllocator);
@ -888,74 +830,51 @@ fn batchUpdateBlocks() void {
while(blockUpdateList.dequeue()) |blockUpdate| { while(blockUpdateList.dequeue()) |blockUpdate| {
defer blockUpdate.deinitManaged(main.globalAllocator); defer blockUpdate.deinitManaged(main.globalAllocator);
const pos = chunk.ChunkPosition{.wx = blockUpdate.x, .wy = blockUpdate.y, .wz = blockUpdate.z, .voxelSize = 1}; 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, &regenerateMeshList); mesh.updateBlock(blockUpdate.x, blockUpdate.y, blockUpdate.z, blockUpdate.newBlock, blockUpdate.blockEntityData, &lightRefreshList, &regenerateMeshList);
mesh.decreaseRefCount();
} // TODO: It seems like we simply ignore the block update if we don't have the mesh yet. } // TODO: It seems like we simply ignore the block update if we don't have the mesh yet.
} }
for(regenerateMeshList.items) |mesh| { for(regenerateMeshList.items) |mesh| {
mesh.generateMesh(&lightRefreshList); mesh.generateMesh(&lightRefreshList);
} }
{ for(lightRefreshList.items) |pos| {
for(lightRefreshList.items) |mesh| { ChunkMesh.scheduleLightRefresh(pos);
if(mesh.needsLightRefresh.load(.unordered)) {
mesh.scheduleLightRefreshAndDecreaseRefCount();
} else {
mesh.decreaseRefCount();
}
}
} }
for(regenerateMeshList.items) |mesh| { for(regenerateMeshList.items) |mesh| {
mesh.uploadData(); mesh.uploadData();
mesh.decreaseRefCount();
} }
} }
// MARK: adders // MARK: adders
pub fn addMeshToClearListAndDecreaseRefCount(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();
clearList.append(mesh);
}
pub fn addToUpdateListAndDecreaseRefCount(mesh: *chunk_meshing.ChunkMesh) void {
std.debug.assert(mesh.refCount.load(.monotonic) != 0); std.debug.assert(mesh.refCount.load(.monotonic) != 0);
mutex.lock(); mutex.lock();
defer mutex.unlock(); defer mutex.unlock();
if(mesh.finishedMeshing) { if(mesh.finishedMeshing) {
priorityMeshUpdateList.enqueue(mesh); priorityMeshUpdateList.enqueue(mesh.pos);
mesh.needsMeshUpdate = true; 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(); mutex.lock();
defer mutex.unlock(); defer mutex.unlock();
if(isInRenderDistance(mesh.pos)) { if(!isInRenderDistance(mesh.pos)) {
const node = getNodePointer(mesh.pos); return error.NoLongerNeeded;
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();
} }
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(); mutex.lock();
defer mutex.unlock(); defer mutex.unlock();
mesh.increaseRefCount(); updatableList.append(pos);
updatableList.append(mesh);
} }
pub const MeshGenerationTask = struct { // MARK: MeshGenerationTask pub const MeshGenerationTask = struct { // MARK: MeshGenerationTask
@ -991,10 +910,8 @@ pub const MeshGenerationTask = struct { // MARK: MeshGenerationTask
pub fn run(self: *MeshGenerationTask) void { pub fn run(self: *MeshGenerationTask) void {
defer main.globalAllocator.destroy(self); defer main.globalAllocator.destroy(self);
const pos = self.mesh.pos; const pos = self.mesh.pos;
const mesh = meshMemoryPool.create(); const mesh = ChunkMesh.init(pos, self.mesh);
mesh.init(pos, self.mesh); mesh.generateLightingData() catch mesh.deinit(undefined);
defer mesh.decreaseRefCount();
mesh.generateLightingData() catch return;
} }
pub fn clean(self: *MeshGenerationTask) void { 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 animationFrame: usize = @intFromFloat(breakingProgress*@as(f32, @floatFromInt(main.blocks.meshes.blockBreakingTextures.items.len)));
const texture = main.blocks.meshes.blockBreakingTextures.items[animationFrame]; 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(); const model = main.blocks.meshes.model(block).model();
for(model.internalQuads) |quadIndex| { 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 { 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 worldPos = pos +% if(neighbor) |n| n.relPos() else Vec3i{0, 0, 0};
const relPos = worldPos & @as(Vec3i, @splat(main.chunk.chunkMask)); 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; const mesh = getMesh(.{.wx = worldPos[0], .wy = worldPos[1], .wz = worldPos[2], .voxelSize = 1}) orelse return;
defer mesh.decreaseRefCount();
mesh.mutex.lock(); mesh.mutex.lock();
defer mesh.mutex.unlock(); defer mesh.mutex.unlock();
const lightIndex = blk: { 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 { 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 worldPos = pos +% if(neighbor) |n| n.relPos() else Vec3i{0, 0, 0};
const relPos = worldPos & @as(Vec3i, @splat(main.chunk.chunkMask)); 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; const mesh = getMesh(.{.wx = worldPos[0], .wy = worldPos[1], .wz = worldPos[2], .voxelSize = 1}) orelse return;
defer mesh.decreaseRefCount();
for(mesh.blockBreakingFaces.items, 0..) |face, i| { 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) { 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); _ = mesh.blockBreakingFaces.swapRemove(i);
@ -1086,7 +1001,7 @@ fn removeBreakingAnimationFace(pos: Vec3i, quadIndex: main.models.QuadIndex, nei
} }
pub fn removeBreakingAnimation(pos: Vec3i) void { 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(); const model = main.blocks.meshes.model(block).model();
for(model.internalQuads) |quadIndex| { for(model.internalQuads) |quadIndex| {

View File

@ -442,6 +442,7 @@ pub fn start(name: []const u8, port: ?u16) void {
defer deinit(); defer deinit();
running.store(true, .release); running.store(true, .release);
while(running.load(.monotonic)) { while(running.load(.monotonic)) {
main.heap.GarbageCollection.syncPoint();
const newTime = std.time.nanoTimestamp(); const newTime = std.time.nanoTimestamp();
if(newTime -% lastTime < updateNanoTime) { if(newTime -% lastTime < updateNanoTime) {
std.Thread.sleep(@intCast(lastTime +% updateNanoTime -% newTime)); std.Thread.sleep(@intCast(lastTime +% updateNanoTime -% newTime));

View File

@ -749,15 +749,17 @@ pub fn BlockingMaxHeap(comptime T: type) type { // MARK: BlockingMaxHeap
/// Returns the biggest element and removes it from the heap. /// Returns the biggest element and removes it from the heap.
/// If empty blocks until a new object is added or the datastructure is closed. /// 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(); self.mutex.lock();
defer self.mutex.unlock(); defer self.mutex.unlock();
const startTime = std.time.nanoTimestamp();
while(true) { while(true) {
if(self.size == 0) { if(self.size == 0) {
self.waitingThreadCount += 1; self.waitingThreadCount += 1;
self.waitingThreads.wait(&self.mutex); defer self.waitingThreadCount -= 1;
self.waitingThreadCount -= 1; try self.waitingThreads.timedWait(&self.mutex, 10_000_000);
} else { } else {
const ret = self.array[0]; const ret = self.array[0];
self.removeIndex(0); self.removeIndex(0);
@ -766,6 +768,7 @@ pub fn BlockingMaxHeap(comptime T: type) type { // MARK: BlockingMaxHeap
if(self.closed) { if(self.closed) {
return error.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(); defer main.deinitThreadLocals();
var lastUpdate = std.time.milliTimestamp(); 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); self.currentTasks[id].store(task.vtable, .monotonic);
const start = std.time.microTimestamp(); const start = std.time.microTimestamp();
task.vtable.run(task.self); task.vtable.run(task.self);

View File

@ -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 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(minSize));
std.debug.assert(std.math.isPowerOfTwo(maxSize)); std.debug.assert(std.math.isPowerOfTwo(maxSize));