mirror of
https://gitlab.bixilon.de/bixilon/minosoft.git
synced 2025-09-16 19:05:02 -04:00
mesh loading queue
This commit is contained in:
parent
f3d8b74607
commit
50dc60d3f1
@ -23,7 +23,6 @@ import de.bixilon.kutil.concurrent.pool.ThreadPool.Priorities.LOW
|
||||
import de.bixilon.kutil.concurrent.pool.ThreadPoolRunnable
|
||||
import de.bixilon.kutil.latch.CountUpAndDownLatch
|
||||
import de.bixilon.kutil.observer.DataObserver.Companion.observe
|
||||
import de.bixilon.kutil.time.TimeUtil.millis
|
||||
import de.bixilon.minosoft.config.key.KeyActions
|
||||
import de.bixilon.minosoft.config.key.KeyBinding
|
||||
import de.bixilon.minosoft.config.key.KeyCodes
|
||||
@ -43,7 +42,6 @@ import de.bixilon.minosoft.gui.rendering.system.base.RenderingCapabilities
|
||||
import de.bixilon.minosoft.gui.rendering.system.base.phases.OpaqueDrawable
|
||||
import de.bixilon.minosoft.gui.rendering.system.base.phases.TranslucentDrawable
|
||||
import de.bixilon.minosoft.gui.rendering.system.base.phases.TransparentDrawable
|
||||
import de.bixilon.minosoft.gui.rendering.util.VecUtil.empty
|
||||
import de.bixilon.minosoft.gui.rendering.util.VecUtil.inSectionHeight
|
||||
import de.bixilon.minosoft.gui.rendering.util.VecUtil.of
|
||||
import de.bixilon.minosoft.gui.rendering.util.vec.vec2.Vec2iUtil.EMPTY
|
||||
@ -60,6 +58,7 @@ import de.bixilon.minosoft.gui.rendering.world.preparer.FluidSectionPreparer
|
||||
import de.bixilon.minosoft.gui.rendering.world.preparer.SolidSectionPreparer
|
||||
import de.bixilon.minosoft.gui.rendering.world.preparer.cull.FluidCullSectionPreparer
|
||||
import de.bixilon.minosoft.gui.rendering.world.preparer.cull.SolidCullSectionPreparer
|
||||
import de.bixilon.minosoft.gui.rendering.world.queue.MeshLoadingQueue
|
||||
import de.bixilon.minosoft.gui.rendering.world.queue.MeshUnloadingQueue
|
||||
import de.bixilon.minosoft.gui.rendering.world.shader.WorldShader
|
||||
import de.bixilon.minosoft.gui.rendering.world.shader.WorldTextShader
|
||||
@ -86,7 +85,7 @@ class WorldRenderer(
|
||||
) : Renderer, OpaqueDrawable, TranslucentDrawable, TransparentDrawable {
|
||||
private val profile = connection.profiles.block
|
||||
override val renderSystem: RenderSystem = renderWindow.renderSystem
|
||||
private val visibilityGraph = renderWindow.camera.visibilityGraph
|
||||
val visibilityGraph = renderWindow.camera.visibilityGraph
|
||||
private val shader = renderSystem.createShader("minosoft:world".toResourceLocation()) { WorldShader(it, false) }
|
||||
private val transparentShader = renderSystem.createShader("minosoft:world".toResourceLocation()) { WorldShader(it, true) }
|
||||
private val textShader = renderSystem.createShader("minosoft:world/text".toResourceLocation()) { WorldTextShader(it) }
|
||||
@ -94,8 +93,8 @@ class WorldRenderer(
|
||||
private val solidSectionPreparer: SolidSectionPreparer = SolidCullSectionPreparer(renderWindow)
|
||||
private val fluidSectionPreparer: FluidSectionPreparer = FluidCullSectionPreparer(renderWindow)
|
||||
|
||||
private val loadedMeshes: MutableMap<Vec2i, Int2ObjectOpenHashMap<WorldMesh>> = mutableMapOf() // all prepared (and up to date) meshes
|
||||
private val loadedMeshesLock = SimpleLock()
|
||||
val loadedMeshes: MutableMap<Vec2i, Int2ObjectOpenHashMap<WorldMesh>> = mutableMapOf() // all prepared (and up to date) meshes
|
||||
val loadedMeshesLock = SimpleLock()
|
||||
|
||||
val maxPreparingTasks = maxOf(DefaultThreadPool.threadCount - 2, 1)
|
||||
private val preparingTasks: MutableSet<SectionPrepareTask> = mutableSetOf() // current running section preparing tasks
|
||||
@ -110,9 +109,7 @@ class WorldRenderer(
|
||||
private val culledQueueLock = SimpleLock()
|
||||
|
||||
val maxMeshesToLoad = if (SystemInformation.RUNTIME.maxMemory() > 1_000_000_000) 150 else 80
|
||||
private val meshesToLoad: MutableList<WorldQueueItem> = mutableListOf() // prepared meshes, that can be loaded in the (next) frame
|
||||
private val meshesToLoadSet: MutableSet<WorldQueueItem> = HashSet()
|
||||
private val meshesToLoadLock = SimpleLock()
|
||||
private val loadingQueue = MeshLoadingQueue(this)
|
||||
private 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, ...)
|
||||
@ -122,14 +119,14 @@ class WorldRenderer(
|
||||
private var previousViewDistance = connection.world.view.viewDistance
|
||||
|
||||
private var cameraPosition = Vec3.EMPTY
|
||||
private var cameraChunkPosition = Vec2i.EMPTY
|
||||
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 meshesToLoad::size
|
||||
val meshesToLoadSize: Int by loadingQueue::size
|
||||
val queueSize: Int by queue::size
|
||||
val preparingTasksSize: Int by preparingTasks::size
|
||||
|
||||
@ -263,7 +260,7 @@ class WorldRenderer(
|
||||
// Unload all chunks(-sections) that are out of view distance
|
||||
culledQueueLock.lock()
|
||||
queueLock.lock()
|
||||
meshesToLoadLock.lock()
|
||||
loadingQueue.lock()
|
||||
unloadingQueue.lock()
|
||||
loadedMeshesLock.lock()
|
||||
|
||||
@ -289,8 +286,7 @@ class WorldRenderer(
|
||||
queue.removeAll { !visibilityGraph.isChunkVisible(it.chunkPosition) }
|
||||
queueSet.removeAll { !visibilityGraph.isChunkVisible(it.chunkPosition) }
|
||||
|
||||
meshesToLoad.removeAll { !visibilityGraph.isChunkVisible(it.chunkPosition) }
|
||||
meshesToLoadSet.removeAll { !visibilityGraph.isChunkVisible(it.chunkPosition) }
|
||||
loadingQueue.cleanup(false)
|
||||
|
||||
preparingTasksLock.acquire()
|
||||
for (task in preparingTasks) {
|
||||
@ -303,7 +299,7 @@ class WorldRenderer(
|
||||
loadedMeshesLock.unlock()
|
||||
queueLock.unlock()
|
||||
culledQueueLock.unlock()
|
||||
meshesToLoadLock.unlock()
|
||||
loadingQueue.unlock()
|
||||
unloadingQueue.unlock()
|
||||
} else {
|
||||
prepareWorld()
|
||||
@ -334,7 +330,7 @@ class WorldRenderer(
|
||||
private fun unloadWorld() {
|
||||
culledQueueLock.lock()
|
||||
queueLock.lock()
|
||||
meshesToLoadLock.lock()
|
||||
loadingQueue.lock()
|
||||
loadedMeshesLock.lock()
|
||||
|
||||
unloadingQueue.lock()
|
||||
@ -347,8 +343,7 @@ class WorldRenderer(
|
||||
loadedMeshes.clear()
|
||||
queue.clear()
|
||||
queueSet.clear()
|
||||
meshesToLoad.clear()
|
||||
meshesToLoadSet.clear()
|
||||
loadingQueue.clear()
|
||||
|
||||
clearVisibleNextFrame = true
|
||||
|
||||
@ -361,13 +356,13 @@ class WorldRenderer(
|
||||
loadedMeshesLock.unlock()
|
||||
queueLock.unlock()
|
||||
culledQueueLock.unlock()
|
||||
meshesToLoadLock.unlock()
|
||||
loadingQueue.unlock()
|
||||
}
|
||||
|
||||
private fun unloadChunk(chunkPosition: Vec2i) {
|
||||
culledQueueLock.lock()
|
||||
queueLock.lock()
|
||||
meshesToLoadLock.lock()
|
||||
loadingQueue.lock()
|
||||
unloadingQueue.lock()
|
||||
loadedMeshesLock.lock()
|
||||
|
||||
@ -378,8 +373,7 @@ class WorldRenderer(
|
||||
queue.removeAll { it.chunkPosition == chunkPosition }
|
||||
queueSet.removeAll { it.chunkPosition == chunkPosition }
|
||||
|
||||
meshesToLoad.removeAll { it.chunkPosition == chunkPosition }
|
||||
meshesToLoadSet.removeAll { it.chunkPosition == chunkPosition }
|
||||
loadingQueue.abort(chunkPosition, false)
|
||||
|
||||
preparingTasksLock.acquire()
|
||||
for (task in preparingTasks) {
|
||||
@ -392,7 +386,7 @@ class WorldRenderer(
|
||||
|
||||
loadedMeshesLock.unlock()
|
||||
culledQueueLock.unlock()
|
||||
meshesToLoadLock.unlock()
|
||||
loadingQueue.unlock()
|
||||
unloadingQueue.unlock()
|
||||
queueLock.unlock()
|
||||
}
|
||||
@ -411,7 +405,7 @@ class WorldRenderer(
|
||||
|
||||
private fun workQueue() {
|
||||
val size = preparingTasks.size
|
||||
if (queue.isEmpty() || size >= maxPreparingTasks || meshesToLoad.size >= maxMeshesToLoad) {
|
||||
if (queue.isEmpty() || size >= maxPreparingTasks || loadingQueue.size >= maxMeshesToLoad) {
|
||||
return
|
||||
}
|
||||
if (workingOnQueue) {
|
||||
@ -466,18 +460,7 @@ class WorldRenderer(
|
||||
}
|
||||
mesh.finish()
|
||||
item.mesh = mesh
|
||||
meshesToLoadLock.lock()
|
||||
if (meshesToLoadSet.remove(item)) {
|
||||
meshesToLoad.remove(item) // Remove duplicates
|
||||
}
|
||||
if (item.chunkPosition == cameraChunkPosition) {
|
||||
// still higher priority
|
||||
meshesToLoad.add(0, item)
|
||||
} else {
|
||||
meshesToLoad += item
|
||||
}
|
||||
meshesToLoadSet += item
|
||||
meshesToLoadLock.unlock()
|
||||
loadingQueue.queue(mesh)
|
||||
} catch (exception: Throwable) {
|
||||
if (exception !is InterruptedException) {
|
||||
// otherwise task got interrupted (probably because of chunk unload)
|
||||
@ -496,7 +479,7 @@ class WorldRenderer(
|
||||
private fun queueItemUnload(item: WorldQueueItem) {
|
||||
culledQueueLock.lock()
|
||||
queueLock.lock()
|
||||
meshesToLoadLock.lock()
|
||||
loadingQueue.lock()
|
||||
unloadingQueue.lock()
|
||||
loadedMeshesLock.lock()
|
||||
loadedMeshes[item.chunkPosition]?.let {
|
||||
@ -517,9 +500,7 @@ class WorldRenderer(
|
||||
queue.remove(item)
|
||||
}
|
||||
|
||||
if (meshesToLoadSet.remove(item)) {
|
||||
meshesToLoad.remove(item)
|
||||
}
|
||||
loadingQueue.abort(item.chunkPosition, false)
|
||||
|
||||
preparingTasksLock.acquire()
|
||||
for (task in preparingTasks) {
|
||||
@ -532,7 +513,7 @@ class WorldRenderer(
|
||||
loadedMeshesLock.unlock()
|
||||
queueLock.unlock()
|
||||
culledQueueLock.unlock()
|
||||
meshesToLoadLock.unlock()
|
||||
loadingQueue.unlock()
|
||||
unloadingQueue.unlock()
|
||||
}
|
||||
|
||||
@ -613,47 +594,6 @@ class WorldRenderer(
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadMeshes() {
|
||||
meshesToLoadLock.lock()
|
||||
if (meshesToLoad.isEmpty()) {
|
||||
meshesToLoadLock.unlock()
|
||||
return
|
||||
}
|
||||
|
||||
var addedMeshes = 0
|
||||
val start = millis()
|
||||
val maxTime = if (connection.player.velocity.empty) 50L else 20L // If the player is still, then we can load more chunks (to not cause lags)
|
||||
|
||||
while (meshesToLoad.isNotEmpty() && (millis() - start < maxTime)) {
|
||||
val item = meshesToLoad.removeAt(0)
|
||||
meshesToLoadSet.remove(item)
|
||||
val mesh = item.mesh ?: continue
|
||||
|
||||
mesh.load()
|
||||
|
||||
loadedMeshesLock.lock()
|
||||
val meshes = loadedMeshes.getOrPut(item.chunkPosition) { Int2ObjectOpenHashMap() }
|
||||
|
||||
meshes.put(item.sectionHeight, mesh)?.let {
|
||||
this.visible.removeMesh(it)
|
||||
it.unload()
|
||||
}
|
||||
loadedMeshesLock.unlock()
|
||||
|
||||
val visible = visibilityGraph.isSectionVisible(item.chunkPosition, item.sectionHeight, mesh.minPosition, mesh.maxPosition, true)
|
||||
if (visible) {
|
||||
addedMeshes++
|
||||
this.visible.addMesh(mesh)
|
||||
}
|
||||
}
|
||||
meshesToLoadLock.unlock()
|
||||
|
||||
if (addedMeshes > 0) {
|
||||
visible.sort()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun postPrepareDraw() {
|
||||
if (clearVisibleNextFrame) {
|
||||
visible.clear()
|
||||
@ -661,7 +601,7 @@ class WorldRenderer(
|
||||
}
|
||||
|
||||
unloadingQueue.work()
|
||||
loadMeshes()
|
||||
loadingQueue.work()
|
||||
}
|
||||
|
||||
override fun setupOpaque() {
|
||||
|
@ -21,5 +21,5 @@ object WorldRendererUtil {
|
||||
|
||||
|
||||
// If the player is still, then we can load more chunks (to not cause lags)
|
||||
val WorldRenderer.loadingTime: Long get() = if (connection.player.velocity.empty) STILL_LOADING_TIME else MOVING_LOADING_TIME // TODO: get of camera
|
||||
val WorldRenderer.maxBusyTime: Long get() = if (connection.player.velocity.empty) STILL_LOADING_TIME else MOVING_LOADING_TIME // TODO: get of camera
|
||||
}
|
||||
|
@ -0,0 +1,132 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
|
||||
*/
|
||||
|
||||
package de.bixilon.minosoft.gui.rendering.world.queue
|
||||
|
||||
import de.bixilon.kutil.concurrent.lock.simple.SimpleLock
|
||||
import de.bixilon.kutil.time.TimeUtil
|
||||
import de.bixilon.minosoft.data.world.positions.ChunkPosition
|
||||
import de.bixilon.minosoft.gui.rendering.world.WorldRenderer
|
||||
import de.bixilon.minosoft.gui.rendering.world.WorldRendererUtil.maxBusyTime
|
||||
import de.bixilon.minosoft.gui.rendering.world.mesh.WorldMesh
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap
|
||||
|
||||
class MeshLoadingQueue(
|
||||
private val renderer: WorldRenderer,
|
||||
) {
|
||||
|
||||
private val meshes: MutableList<WorldMesh> = mutableListOf() // prepared meshes, that can be loaded in the (next) frame
|
||||
private val positions: MutableSet<ChunkPosition> = HashSet()
|
||||
private val lock = SimpleLock()
|
||||
|
||||
val size: Int get() = meshes.size
|
||||
|
||||
|
||||
fun work() {
|
||||
lock.lock()
|
||||
if (meshes.isEmpty()) {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
|
||||
var count = 0
|
||||
val start = TimeUtil.millis()
|
||||
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)
|
||||
this.positions -= mesh.chunkPosition
|
||||
|
||||
mesh.load()
|
||||
|
||||
val meshes = renderer.loadedMeshes.getOrPut(mesh.chunkPosition) { Int2ObjectOpenHashMap() }
|
||||
|
||||
meshes.put(mesh.sectionHeight, mesh)?.let {
|
||||
renderer.visible.removeMesh(it)
|
||||
it.unload()
|
||||
}
|
||||
|
||||
val visible = renderer.visibilityGraph.isSectionVisible(mesh.chunkPosition, mesh.sectionHeight, mesh.minPosition, mesh.maxPosition, true)
|
||||
if (visible) {
|
||||
count++
|
||||
renderer.visible.addMesh(mesh)
|
||||
}
|
||||
}
|
||||
renderer.loadedMeshesLock.unlock()
|
||||
|
||||
lock.unlock()
|
||||
|
||||
if (count > 0) {
|
||||
renderer.visible.sort()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun queue(mesh: WorldMesh) {
|
||||
lock.lock()
|
||||
if (!this.positions.add(mesh.chunkPosition)) {
|
||||
// already inside, remove
|
||||
meshes.remove(mesh)
|
||||
}
|
||||
if (mesh.chunkPosition == renderer.cameraChunkPosition) {
|
||||
// still higher priority
|
||||
meshes.add(0, mesh)
|
||||
} else {
|
||||
meshes += mesh
|
||||
}
|
||||
lock.unlock()
|
||||
}
|
||||
|
||||
fun abort(position: ChunkPosition, lock: Boolean = true) {
|
||||
if (lock) this.lock.lock()
|
||||
if (this.positions.remove(position)) {
|
||||
this.meshes.removeIf { it.chunkPosition == position }
|
||||
}
|
||||
if (lock) this.lock.unlock()
|
||||
}
|
||||
|
||||
|
||||
fun cleanup(lock: Boolean) {
|
||||
val remove: MutableSet<ChunkPosition> = mutableSetOf()
|
||||
|
||||
if (lock) this.lock.lock()
|
||||
this.positions.removeAll {
|
||||
if (renderer.visibilityGraph.isChunkVisible(it)) {
|
||||
return@removeAll false
|
||||
}
|
||||
remove += it
|
||||
return@removeAll true
|
||||
}
|
||||
|
||||
this.meshes.removeAll { it.chunkPosition in remove }
|
||||
if (lock) this.lock.unlock()
|
||||
}
|
||||
|
||||
fun clear(lock: Boolean) {
|
||||
if (lock) this.lock.lock()
|
||||
this.positions.clear()
|
||||
this.meshes.clear()
|
||||
if (lock) this.lock.unlock()
|
||||
}
|
||||
|
||||
|
||||
fun lock() {
|
||||
this.lock.lock()
|
||||
}
|
||||
|
||||
fun unlock() {
|
||||
this.lock.unlock()
|
||||
}
|
||||
}
|
@ -17,7 +17,7 @@ import de.bixilon.kutil.concurrent.lock.simple.SimpleLock
|
||||
import de.bixilon.kutil.time.TimeUtil
|
||||
import de.bixilon.minosoft.data.world.positions.ChunkPosition
|
||||
import de.bixilon.minosoft.gui.rendering.world.WorldRenderer
|
||||
import de.bixilon.minosoft.gui.rendering.world.WorldRendererUtil.loadingTime
|
||||
import de.bixilon.minosoft.gui.rendering.world.WorldRendererUtil.maxBusyTime
|
||||
import de.bixilon.minosoft.gui.rendering.world.mesh.WorldMesh
|
||||
|
||||
class MeshUnloadingQueue(
|
||||
@ -34,7 +34,7 @@ class MeshUnloadingQueue(
|
||||
}
|
||||
|
||||
val time = TimeUtil.millis()
|
||||
val maxTime = renderer.loadingTime
|
||||
val maxTime = renderer.maxBusyTime
|
||||
|
||||
while (meshes.isNotEmpty() && (TimeUtil.millis() - time < maxTime)) {
|
||||
val mesh = meshes.removeAt(0)
|
||||
|
Loading…
x
Reference in New Issue
Block a user