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