diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/world/LoadedMeshes.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/world/LoadedMeshes.kt new file mode 100644 index 000000000..e6f2969e0 --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/world/LoadedMeshes.kt @@ -0,0 +1,123 @@ +/* + * Minosoft + * Copyright (C) 2020-2022 Moritz Zwerger + * + * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. If not, see . + * + * This software is not affiliated with Mojang AB, the original developer of Minecraft. + */ + +package de.bixilon.minosoft.gui.rendering.world + +import de.bixilon.kotlinglm.vec2.Vec2i +import de.bixilon.kutil.concurrent.lock.simple.SimpleLock +import de.bixilon.minosoft.data.world.positions.ChunkPosition +import de.bixilon.minosoft.gui.rendering.world.mesh.VisibleMeshes +import de.bixilon.minosoft.gui.rendering.world.mesh.WorldMesh +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap + +class LoadedMeshes( + private val renderer: WorldRenderer, +) { + val meshes: MutableMap> = mutableMapOf() // all prepared (and up to date) meshes + private val lock = SimpleLock() + + + val size: Int get() = meshes.size + + + fun cleanup(lock: Boolean) { + val remove: MutableSet = HashSet() + if (lock) this.lock.lock() + + for ((chunkPosition, sections) in meshes) { + if (renderer.visibilityGraph.isInViewDistance(chunkPosition)) { + continue + } + remove += chunkPosition + renderer.unloadingQueue.forceQueue(sections.values) + } + meshes -= remove + + if (lock) this.lock.unlock() + } + + fun clear(lock: Boolean) { + if (lock) this.lock.lock() + + for (sections in meshes.values) { + renderer.unloadingQueue.forceQueue(sections.values, lock) + } + + if (lock) this.lock.unlock() + } + + fun unload(position: ChunkPosition, lock: Boolean) { + if (lock) this.lock.lock() + + val meshes = this.meshes.remove(position) + + if (meshes != null) { + renderer.unloadingQueue.forceQueue(meshes.values, lock) + } + + if (lock) this.lock.unlock() + } + + fun unload(position: ChunkPosition, sectionHeight: Int, lock: Boolean) { + if (lock) this.lock.lock() + + val meshes = this.meshes[position] + + if (meshes != null) { + meshes.remove(sectionHeight)?.let { + renderer.unloadingQueue.forceQueue(it, lock) + + if (meshes.isEmpty()) { + this.meshes.remove(position) + } + } + } + + if (lock) this.lock.unlock() + } + + + operator fun contains(position: ChunkPosition): Boolean { + lock.acquire() + val contains = position in this.meshes + lock.release() + return contains + } + + + fun collect(visible: VisibleMeshes) { + lock.acquire() + for ((chunkPosition, meshes) in this.meshes) { + if (!renderer.visibilityGraph.isChunkVisible(chunkPosition)) { + continue + } + + for (entry in meshes.int2ObjectEntrySet()) { + val mesh = entry.value + if (!renderer.visibilityGraph.isSectionVisible(chunkPosition, entry.intKey, mesh.minPosition, mesh.maxPosition, false)) { + continue + } + visible.addMesh(mesh) + } + } + lock.release() + } + + fun lock() { + this.lock.lock() + } + + fun unlock() { + this.lock.unlock() + } +} diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/world/WorldRenderer.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/world/WorldRenderer.kt index 96d80732c..314f485e4 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/world/WorldRenderer.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/world/WorldRenderer.kt @@ -93,8 +93,7 @@ class WorldRenderer( private val solidSectionPreparer: SolidSectionPreparer = SolidCullSectionPreparer(renderWindow) private val fluidSectionPreparer: FluidSectionPreparer = FluidCullSectionPreparer(renderWindow) - val loadedMeshes: MutableMap> = mutableMapOf() // all prepared (and up to date) meshes - val loadedMeshesLock = SimpleLock() + val loaded = LoadedMeshes(this) val maxPreparingTasks = maxOf(DefaultThreadPool.threadCount - 2, 1) private val preparingTasks: MutableSet = mutableSetOf() // current running section preparing tasks @@ -110,7 +109,7 @@ class WorldRenderer( val maxMeshesToLoad = if (SystemInformation.RUNTIME.maxMemory() > 1_000_000_000) 150 else 80 private val loadingQueue = MeshLoadingQueue(this) - private val unloadingQueue = MeshUnloadingQueue(this) + val unloadingQueue = MeshUnloadingQueue(this) // all meshes that will be rendered in the next frame (might be changed, when the frustum changes or a chunk gets loaded, ...) private var clearVisibleNextFrame = false @@ -122,13 +121,12 @@ class WorldRenderer( var cameraChunkPosition = Vec2i.EMPTY private var cameraSectionHeight = 0 - val visibleSize: String - get() = visible.sizeString - val loadedMeshesSize: Int by loadedMeshes::size - val culledQueuedSize: Int by culledQueue::size - val meshesToLoadSize: Int by loadingQueue::size - val queueSize: Int by queue::size - val preparingTasksSize: Int by preparingTasks::size + val visibleSize: String get() = visible.sizeString + val loadedMeshesSize: Int get() = loaded.size + val culledQueuedSize: Int get() = culledQueue.size + val meshesToLoadSize: Int get() = loadingQueue.size + val queueSize: Int get() = queue.size + val preparingTasksSize: Int get() = preparingTasks.size override fun init(latch: CountUpAndDownLatch) { renderWindow.modelLoader.load(latch) @@ -262,17 +260,9 @@ class WorldRenderer( queueLock.lock() loadingQueue.lock() unloadingQueue.lock() - loadedMeshesLock.lock() + loaded.lock() - val loadedMeshesToRemove: MutableSet = HashSet() - for ((chunkPosition, sections) in loadedMeshes) { - if (visibilityGraph.isInViewDistance(chunkPosition)) { - continue - } - loadedMeshesToRemove += chunkPosition - unloadingQueue.forceQueue(sections.values) - } - loadedMeshes -= loadedMeshesToRemove + loaded.cleanup(false) val toRemove: MutableSet = HashSet() for (chunkPosition in culledQueue.keys) { @@ -296,7 +286,7 @@ class WorldRenderer( } preparingTasksLock.release() - loadedMeshesLock.unlock() + loaded.unlock() queueLock.unlock() culledQueueLock.unlock() loadingQueue.unlock() @@ -331,21 +321,9 @@ class WorldRenderer( culledQueueLock.lock() queueLock.lock() loadingQueue.lock() - loadedMeshesLock.lock() + loaded.lock() - unloadingQueue.lock() - for (sections in loadedMeshes.values) { - unloadingQueue.forceQueue(sections.values, false) - } - unloadingQueue.unlock() - culledQueue.clear() - loadedMeshes.clear() - queue.clear() - queueSet.clear() - loadingQueue.clear() - - clearVisibleNextFrame = true preparingTasksLock.acquire() for (task in preparingTasks) { @@ -353,7 +331,17 @@ class WorldRenderer( } preparingTasksLock.release() - loadedMeshesLock.unlock() + unloadingQueue.lock() + loaded.clear(false) + + culledQueue.clear() + queue.clear() + queueSet.clear() + loadingQueue.clear(false) + + clearVisibleNextFrame = true + + loaded.unlock() queueLock.unlock() culledQueueLock.unlock() loadingQueue.unlock() @@ -364,16 +352,8 @@ class WorldRenderer( queueLock.lock() loadingQueue.lock() unloadingQueue.lock() - loadedMeshesLock.lock() + loaded.lock() - val meshes = loadedMeshes.remove(chunkPosition) - - culledQueue.remove(chunkPosition) - - queue.removeAll { it.chunkPosition == chunkPosition } - queueSet.removeAll { it.chunkPosition == chunkPosition } - - loadingQueue.abort(chunkPosition, false) preparingTasksLock.acquire() for (task in preparingTasks) { @@ -382,9 +362,18 @@ class WorldRenderer( } } preparingTasksLock.release() - meshes?.let { unloadingQueue.forceQueue(meshes.values, false) } - loadedMeshesLock.unlock() + culledQueue.remove(chunkPosition) + + queue.removeAll { it.chunkPosition == chunkPosition } + queueSet.removeAll { it.chunkPosition == chunkPosition } + + loadingQueue.abort(chunkPosition, false) + + + val meshes = loaded.unload(chunkPosition, false) + + loaded.unlock() culledQueueLock.unlock() loadingQueue.unlock() unloadingQueue.unlock() @@ -481,13 +470,8 @@ class WorldRenderer( queueLock.lock() loadingQueue.lock() unloadingQueue.lock() - loadedMeshesLock.lock() - loadedMeshes[item.chunkPosition]?.let { - unloadingQueue.forceQueue(it.remove(item.sectionHeight) ?: return@let, false) - if (it.isEmpty()) { - loadedMeshes -= item.chunkPosition - } - } + loaded.lock() + loaded.unload(item.chunkPosition, item.sectionHeight, false) culledQueue[item.chunkPosition]?.let { it.remove(item.sectionHeight) @@ -510,7 +494,7 @@ class WorldRenderer( } preparingTasksLock.release() - loadedMeshesLock.unlock() + loaded.unlock() queueLock.unlock() culledQueueLock.unlock() loadingQueue.unlock() @@ -565,18 +549,16 @@ class WorldRenderer( } } - private fun queueChunk(chunkPosition: Vec2i, chunk: Chunk = world.chunks[chunkPosition]!!) { + private fun queueChunk(chunkPosition: Vec2i, chunk: Chunk) { val neighbours = chunk.neighbours.get() if (neighbours == null || !chunk.isFullyLoaded || renderWindow.state == RenderingStates.PAUSED) { return } - this.loadedMeshesLock.acquire() - if (this.loadedMeshes.containsKey(chunkPosition)) { - // ToDo: this also ignores light updates - this.loadedMeshesLock.release() + + // should not queue, it is already loaded + if (chunkPosition in loaded) { return } - this.loadedMeshesLock.release() // ToDo: Check if chunk is visible (not section, chunk) @@ -669,20 +651,7 @@ class WorldRenderer( val visible = VisibleMeshes(cameraPosition, this.visible) - loadedMeshesLock.acquire() - for ((chunkPosition, meshes) in this.loadedMeshes) { - if (!visibilityGraph.isChunkVisible(chunkPosition)) { - continue - } - - for (entry in meshes.int2ObjectEntrySet()) { - val mesh = entry.value - if (visibilityGraph.isSectionVisible(chunkPosition, entry.intKey, mesh.minPosition, mesh.maxPosition, false)) { - visible.addMesh(mesh) - } - } - } - loadedMeshesLock.release() + loaded.collect(visible) culledQueueLock.acquire() // The queue method needs the full lock of the culledQueue val nextQueue: MutableMap> = mutableMapOf() diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/world/queue/ChunkMeshingQueue.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/world/queue/ChunkMeshingQueue.kt new file mode 100644 index 000000000..8cf36366a --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/world/queue/ChunkMeshingQueue.kt @@ -0,0 +1,16 @@ +/* + * Minosoft + * Copyright (C) 2020-2022 Moritz Zwerger + * + * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. If not, see . + * + * This software is not affiliated with Mojang AB, the original developer of Minecraft. + */ + +package de.bixilon.minosoft.gui.rendering.world.queue + +class ChunkMeshingQueue diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/world/queue/MeshLoadingQueue.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/world/queue/MeshLoadingQueue.kt index fa9c6baa1..17ae67754 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/world/queue/MeshLoadingQueue.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/world/queue/MeshLoadingQueue.kt @@ -13,6 +13,7 @@ package de.bixilon.minosoft.gui.rendering.world.queue +import de.bixilon.kutil.cast.CastUtil.unsafeNull import de.bixilon.kutil.concurrent.lock.simple.SimpleLock import de.bixilon.kutil.time.TimeUtil import de.bixilon.minosoft.data.world.positions.ChunkPosition @@ -44,14 +45,20 @@ class MeshLoadingQueue( val maxTime = renderer.maxBusyTime // If the player is still, then we can load more chunks (to not cause lags) - renderer.loadedMeshesLock.lock() - while (meshes.isNotEmpty() && (TimeUtil.millis() - start < maxTime)) { - val mesh = meshes.removeAt(0) + renderer.loaded.lock() + var meshes: Int2ObjectOpenHashMap = unsafeNull() + var position: ChunkPosition? = null + while (this.meshes.isNotEmpty() && (TimeUtil.millis() - start < maxTime)) { + val mesh = this.meshes.removeAt(0) this.positions -= mesh.chunkPosition mesh.load() - val meshes = renderer.loadedMeshes.getOrPut(mesh.chunkPosition) { Int2ObjectOpenHashMap() } + if (position != mesh.chunkPosition) { + meshes = renderer.loaded.meshes.getOrPut(mesh.chunkPosition) { Int2ObjectOpenHashMap() } + position = mesh.chunkPosition + } + meshes.put(mesh.sectionHeight, mesh)?.let { renderer.visible.removeMesh(it) @@ -64,7 +71,7 @@ class MeshLoadingQueue( renderer.visible.addMesh(mesh) } } - renderer.loadedMeshesLock.unlock() + renderer.loaded.unlock() lock.unlock()