diff --git a/Sources/Voxelotl/CMakeLists.txt b/Sources/Voxelotl/CMakeLists.txt index 1c73f5d..ad668af 100644 --- a/Sources/Voxelotl/CMakeLists.txt +++ b/Sources/Voxelotl/CMakeLists.txt @@ -54,6 +54,7 @@ add_executable(Voxelotl MACOSX_BUNDLE # Game logic classes Chunk.swift ChunkGeneration.swift + ChunkMeshGeneration.swift WorldGenerator.swift CubeMeshBuilder.swift ChunkMeshBuilder.swift diff --git a/Sources/Voxelotl/ChunkMeshBuilder.swift b/Sources/Voxelotl/ChunkMeshBuilder.swift index ff880b4..be0b928 100644 --- a/Sources/Voxelotl/ChunkMeshBuilder.swift +++ b/Sources/Voxelotl/ChunkMeshBuilder.swift @@ -1,7 +1,5 @@ struct ChunkMeshBuilder { - public static func build(world: World, chunkID: SIMD3) -> Mesh { - guard let chunk = world.getChunk(id: chunkID) else { return .empty } - + public static func build(world: World, chunk: Chunk) -> Mesh { var vertices = [VertexPositionNormalColorTexcoord]() var indices = [UInt16]() chunk.forEach { block, position in diff --git a/Sources/Voxelotl/ChunkMeshGeneration.swift b/Sources/Voxelotl/ChunkMeshGeneration.swift new file mode 100644 index 0000000..3e1256f --- /dev/null +++ b/Sources/Voxelotl/ChunkMeshGeneration.swift @@ -0,0 +1,47 @@ +import Foundation + +public struct ChunkMeshGeneration { + private let queue: OperationQueue + private let localReadyMeshes = ConcurrentDictionary, RendererMesh>() + + weak var game: Game? + weak var renderer: Renderer? + + init(queue: DispatchQueue) { + self.queue = OperationQueue() + self.queue.underlyingQueue = queue + self.queue.maxConcurrentOperationCount = 8 + self.queue.qualityOfService = .userInitiated + } + + public mutating func generate(chunkID: SIMD3, chunk: Chunk) { + self.queueGenerateJob(chunkID: chunkID, chunk: chunk) + } + + func queueGenerateJob(chunkID: SIMD3, chunk: Chunk) { + self.queue.addOperation { + guard let game = self.game else { + return + } + + guard let renderer = self.renderer else { + return + } + + let mesh = ChunkMeshBuilder.build(world: game.world, chunk: chunk) + self.localReadyMeshes[chunkID] = renderer.createMesh(mesh) + } + } + + public mutating func acceptReadyMeshes() { + guard let game = self.game else { + return + } + + queue.waitUntilAllOperationsAreFinished() + + for (chunkID, mesh) in self.localReadyMeshes.take() { + game.renderChunks.updateValue(mesh, forKey: chunkID) + } + } +} diff --git a/Sources/Voxelotl/Game.swift b/Sources/Voxelotl/Game.swift index 6f0e445..d6e3843 100644 --- a/Sources/Voxelotl/Game.swift +++ b/Sources/Voxelotl/Game.swift @@ -27,6 +27,7 @@ class Game: GameDelegate { var world = World() var cubeMesh: RendererMesh? var renderChunks = [SIMD3: RendererMesh]() + var chunkMeshGeneration: ChunkMeshGeneration! func create(_ renderer: Renderer) { self.resetPlayer() @@ -35,6 +36,9 @@ class Game: GameDelegate { self.cubeMesh = renderer.createMesh(CubeMeshBuilder.build(bound: .fromUnitCube(position: .zero, scale: .one))) renderer.clearColor = Color.black.mix(.white, 0.1).linear + self.chunkMeshGeneration = .init(queue: .global(qos: .userInitiated)) + self.chunkMeshGeneration.game = self + self.chunkMeshGeneration.renderer = renderer } private func resetPlayer() { @@ -110,13 +114,9 @@ class Game: GameDelegate { // Update chunk meshes if needed self.world.handleRenderDamagedChunks { id, chunk in - let mesh = ChunkMeshBuilder.build(world: self.world, chunkID: id) - if let renderMesh = renderer.createMesh(mesh) { - self.renderChunks[id] = renderMesh - } else { - self.renderChunks.removeValue(forKey: id) - } + self.chunkMeshGeneration.generate(chunkID: id, chunk: chunk) } + self.chunkMeshGeneration.acceptReadyMeshes() for (id, chunk) in self.renderChunks { let drawPos = SIMD3(id &<< Chunk.shift)