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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

View File

@ -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, &regenerateMeshList);
mesh.decreaseRefCount();
} // TODO: It seems like we simply ignore the block update if we don't have the mesh yet.
}
for(regenerateMeshList.items) |mesh| {
mesh.generateMesh(&lightRefreshList);
}
{
for(lightRefreshList.items) |mesh| {
if(mesh.needsLightRefresh.load(.unordered)) {
mesh.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| {

View File

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

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.
/// 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);

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