From f2b3b078326dcc8af8905909ff941671fb95635c Mon Sep 17 00:00:00 2001 From: Alex Zenla Date: Sat, 7 Sep 2024 02:38:54 -0400 Subject: [PATCH] Spatial Chunk Generation --- Sources/Voxelotl/ChunkGeneration.swift | 93 +++++++++++++++++-- .../Common/ConcurrentDictionary.swift | 12 +++ Sources/Voxelotl/Game.swift | 2 +- Sources/Voxelotl/World.swift | 3 +- 4 files changed, 101 insertions(+), 9 deletions(-) diff --git a/Sources/Voxelotl/ChunkGeneration.swift b/Sources/Voxelotl/ChunkGeneration.swift index aa9267a..fb33538 100644 --- a/Sources/Voxelotl/ChunkGeneration.swift +++ b/Sources/Voxelotl/ChunkGeneration.swift @@ -1,8 +1,10 @@ import Foundation +import Spatial public struct ChunkGeneration { private let queue: OperationQueue private let localReadyChunks = ConcurrentDictionary, Chunk>() + private var chunkGenerationOperations = ConcurrentDictionary, WeakChunkGenerationOperationHolder>() private var generatingChunkSet = Set>() weak var world: World? @@ -28,15 +30,54 @@ public struct ChunkGeneration { } func queueGenerateJob(chunkID: SIMD3) { - self.queue.addOperation { - guard let world = self.world else { - return - } - let chunk = world.generateSingleChunkUncommitted(chunkID: chunkID) - self.localReadyChunks[chunkID] = chunk - } + let operation = ChunkGenerationOperation(chunkID: chunkID) + operation.world = self.world + operation.localReadyChunks = self.localReadyChunks + operation.chunkGenerationOperations = self.chunkGenerationOperations + self.queue.addOperation(operation) + let holder = WeakChunkGenerationOperationHolder() + holder.operation = operation + self.chunkGenerationOperations[chunkID] = holder } + public mutating func updatePriorityPosition(position: SIMD3) { + let centerChunkID = World.makeID(position: position) + let centerChunkPoint = Point3D(x: Double(centerChunkID.x), y: Double(centerChunkID.y), z: Double(centerChunkID.z)) + + self.chunkGenerationOperations.with { operations in + var remove: [SIMD3] = [] + for (chunkID, operation) in operations { + if operation.operation == nil { + remove.append(chunkID) + continue + } + let chunkPoint = Point3D(x: Double(chunkID.x), y: Double(chunkID.y), z: Double(chunkID.z)) + let distance = abs(chunkPoint.distance(to: centerChunkPoint)) + let priority: Operation.QueuePriority + if distance < 3 { + priority = .veryHigh + } else if distance < 6 { + priority = .normal + } else if distance < 10 { + priority = .low + } else { + priority = .veryLow + } + + if priority == .veryLow { + operation.operation?.cancel() + self.generatingChunkSet.remove(chunkID) + } else { + operation.operation?.queuePriority = priority + } + } + + for item in remove { + operations.removeValue(forKey: item) + } + } + } + public mutating func generateAdjacentIfNeeded(position: SIMD3) { guard let world = self.world else { return @@ -74,3 +115,41 @@ public struct ChunkGeneration { } } } + +class WeakChunkGenerationOperationHolder { + weak var operation: ChunkGenerationOperation? +} + +class ChunkGenerationOperation: Operation, @unchecked Sendable { + let chunkID: SIMD3 + + weak var world: World? + weak var localReadyChunks: ConcurrentDictionary, Chunk>? + weak var chunkGenerationOperations: ConcurrentDictionary, WeakChunkGenerationOperationHolder>? + + init(chunkID: SIMD3) { + self.chunkID = chunkID + } + + override func main() { + if isCancelled { + return + } + + guard let world = self.world else { + return + } + + guard let localReadyChunks = self.localReadyChunks else { + return + } + + let chunk = world.generateSingleChunkUncommitted(chunkID: self.chunkID) + localReadyChunks[chunkID] = chunk + + guard let chunkGenerationOperations = self.chunkGenerationOperations else { + return + } + chunkGenerationOperations.remove(key: chunkID) + } +} diff --git a/Sources/Voxelotl/Common/ConcurrentDictionary.swift b/Sources/Voxelotl/Common/ConcurrentDictionary.swift index 7bf480a..c333699 100644 --- a/Sources/Voxelotl/Common/ConcurrentDictionary.swift +++ b/Sources/Voxelotl/Common/ConcurrentDictionary.swift @@ -75,6 +75,18 @@ public class ConcurrentDictionary: Collection { self.inner.removeAll(keepingCapacity: keep) } } + + @discardableResult public func remove(key: V) -> T? { + self.locked { + self.inner.removeValue(forKey: key) + } + } + + public func with(_ perform: (inout [V : T]) -> Void) { + self.locked { + perform(&self.inner) + } + } fileprivate func locked(_ perform: () -> X) -> X { self.lock.lock() diff --git a/Sources/Voxelotl/Game.swift b/Sources/Voxelotl/Game.swift index 6b56126..3fa0923 100644 --- a/Sources/Voxelotl/Game.swift +++ b/Sources/Voxelotl/Game.swift @@ -83,7 +83,7 @@ class Game: GameDelegate { } self.world.generateAdjacentChunksIfNeeded(position: self.player.position) - self.world.update() + self.world.update(priorityPosition: self.player.position) } public static let material = Material( diff --git a/Sources/Voxelotl/World.swift b/Sources/Voxelotl/World.swift index 214154c..a3b69fa 100644 --- a/Sources/Voxelotl/World.swift +++ b/Sources/Voxelotl/World.swift @@ -107,7 +107,8 @@ public class World { } } - public func update() { + public func update(priorityPosition: SIMD3) { + self._chunkGeneration.updatePriorityPosition(position: priorityPosition) self._chunkGeneration.acceptReadyChunks() }