mirror of
https://gitlab.bixilon.de/bixilon/minosoft.git
synced 2025-09-17 03:15:35 -04:00
world renderer: split chunk meshing, event listening
This commit is contained in:
parent
592aef6bda
commit
563eebd476
@ -0,0 +1,69 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
import de.bixilon.kutil.concurrent.pool.ThreadPoolRunnable
|
||||||
|
import de.bixilon.minosoft.gui.rendering.world.mesh.WorldMesh
|
||||||
|
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.meshing.tasks.MeshPrepareTask
|
||||||
|
import de.bixilon.minosoft.gui.rendering.world.util.WorldRendererUtil.smallMesh
|
||||||
|
|
||||||
|
class ChunkMesher(
|
||||||
|
private val renderer: WorldRenderer,
|
||||||
|
) {
|
||||||
|
private val solidSectionPreparer: SolidSectionPreparer = SolidCullSectionPreparer(renderer.renderWindow)
|
||||||
|
private val fluidSectionPreparer: FluidSectionPreparer = FluidCullSectionPreparer(renderer.renderWindow)
|
||||||
|
|
||||||
|
private fun mesh(item: WorldQueueItem): WorldMesh? {
|
||||||
|
if (item.section.blocks.isEmpty) {
|
||||||
|
renderer.queueItemUnload(item)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
val mesh = WorldMesh(renderer.renderWindow, item.chunkPosition, item.sectionHeight, item.section.smallMesh)
|
||||||
|
solidSectionPreparer.prepareSolid(item.chunkPosition, item.sectionHeight, item.chunk, item.section, item.neighbours, item.chunkNeighbours, mesh)
|
||||||
|
|
||||||
|
if (item.section.blocks.fluidCount > 0) {
|
||||||
|
fluidSectionPreparer.prepareFluid(item.chunkPosition, item.sectionHeight, item.chunk, item.section, item.neighbours, item.chunkNeighbours, mesh)
|
||||||
|
}
|
||||||
|
|
||||||
|
return mesh
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun mesh(item: WorldQueueItem, runnable: ThreadPoolRunnable) {
|
||||||
|
val mesh = mesh(item) ?: return
|
||||||
|
runnable.interruptable = false
|
||||||
|
if (Thread.interrupted()) return
|
||||||
|
if (mesh.clearEmpty() == 0) {
|
||||||
|
return renderer.queueItemUnload(item)
|
||||||
|
}
|
||||||
|
mesh.finish()
|
||||||
|
item.mesh = mesh
|
||||||
|
renderer.loadingQueue.queue(mesh)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun tryMesh(item: WorldQueueItem, task: MeshPrepareTask, runnable: ThreadPoolRunnable) {
|
||||||
|
try {
|
||||||
|
mesh(item, runnable)
|
||||||
|
} catch (ignored: InterruptedException) {
|
||||||
|
} finally {
|
||||||
|
task.runnable.interruptable = false
|
||||||
|
if (Thread.interrupted()) throw InterruptedException()
|
||||||
|
renderer.queue.tasks -= task
|
||||||
|
renderer.queue.work()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -24,10 +24,11 @@ import java.util.*
|
|||||||
class WorldQueueItem(
|
class WorldQueueItem(
|
||||||
val chunkPosition: Vec2i,
|
val chunkPosition: Vec2i,
|
||||||
val sectionHeight: Int,
|
val sectionHeight: Int,
|
||||||
val chunk: Chunk?,
|
val chunk: Chunk,
|
||||||
val section: ChunkSection?,
|
val section: ChunkSection,
|
||||||
val center: Vec3,
|
val center: Vec3,
|
||||||
var neighbours: Array<ChunkSection?>?,
|
val chunkNeighbours: Array<Chunk>,
|
||||||
|
var neighbours: Array<ChunkSection?>,
|
||||||
) {
|
) {
|
||||||
val sectionPosition = Vec3i(chunkPosition.x, sectionHeight, chunkPosition.y)
|
val sectionPosition = Vec3i(chunkPosition.x, sectionHeight, chunkPosition.y)
|
||||||
var mesh: WorldMesh? = null
|
var mesh: WorldMesh? = null
|
||||||
|
@ -17,13 +17,11 @@ import de.bixilon.kotlinglm.vec2.Vec2i
|
|||||||
import de.bixilon.kotlinglm.vec3.Vec3
|
import de.bixilon.kotlinglm.vec3.Vec3
|
||||||
import de.bixilon.kotlinglm.vec3.Vec3i
|
import de.bixilon.kotlinglm.vec3.Vec3i
|
||||||
import de.bixilon.kutil.concurrent.lock.simple.SimpleLock
|
import de.bixilon.kutil.concurrent.lock.simple.SimpleLock
|
||||||
import de.bixilon.kutil.concurrent.pool.ThreadPoolRunnable
|
|
||||||
import de.bixilon.kutil.latch.CountUpAndDownLatch
|
import de.bixilon.kutil.latch.CountUpAndDownLatch
|
||||||
import de.bixilon.kutil.observer.DataObserver.Companion.observe
|
import de.bixilon.kutil.observer.DataObserver.Companion.observe
|
||||||
import de.bixilon.minosoft.config.key.KeyActions
|
import de.bixilon.minosoft.config.key.KeyActions
|
||||||
import de.bixilon.minosoft.config.key.KeyBinding
|
import de.bixilon.minosoft.config.key.KeyBinding
|
||||||
import de.bixilon.minosoft.config.key.KeyCodes
|
import de.bixilon.minosoft.config.key.KeyCodes
|
||||||
import de.bixilon.minosoft.data.direction.Directions
|
|
||||||
import de.bixilon.minosoft.data.registries.ResourceLocation
|
import de.bixilon.minosoft.data.registries.ResourceLocation
|
||||||
import de.bixilon.minosoft.data.world.World
|
import de.bixilon.minosoft.data.world.World
|
||||||
import de.bixilon.minosoft.data.world.chunk.Chunk
|
import de.bixilon.minosoft.data.world.chunk.Chunk
|
||||||
@ -39,41 +37,24 @@ 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.OpaqueDrawable
|
||||||
import de.bixilon.minosoft.gui.rendering.system.base.phases.TranslucentDrawable
|
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.system.base.phases.TransparentDrawable
|
||||||
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.VecUtil.of
|
||||||
import de.bixilon.minosoft.gui.rendering.util.vec.vec2.Vec2iUtil.EMPTY
|
import de.bixilon.minosoft.gui.rendering.util.vec.vec2.Vec2iUtil.EMPTY
|
||||||
import de.bixilon.minosoft.gui.rendering.util.vec.vec3.Vec3Util.EMPTY
|
import de.bixilon.minosoft.gui.rendering.util.vec.vec3.Vec3Util.EMPTY
|
||||||
import de.bixilon.minosoft.gui.rendering.util.vec.vec3.Vec3Util.blockPosition
|
import de.bixilon.minosoft.gui.rendering.util.vec.vec3.Vec3Util.blockPosition
|
||||||
import de.bixilon.minosoft.gui.rendering.util.vec.vec3.Vec3iUtil.chunkPosition
|
import de.bixilon.minosoft.gui.rendering.util.vec.vec3.Vec3iUtil.chunkPosition
|
||||||
import de.bixilon.minosoft.gui.rendering.util.vec.vec3.Vec3iUtil.inChunkSectionPosition
|
|
||||||
import de.bixilon.minosoft.gui.rendering.util.vec.vec3.Vec3iUtil.sectionHeight
|
|
||||||
import de.bixilon.minosoft.gui.rendering.util.vec.vec3.Vec3iUtil.toVec3
|
import de.bixilon.minosoft.gui.rendering.util.vec.vec3.Vec3iUtil.toVec3
|
||||||
import de.bixilon.minosoft.gui.rendering.world.mesh.VisibleMeshes
|
import de.bixilon.minosoft.gui.rendering.world.mesh.VisibleMeshes
|
||||||
import de.bixilon.minosoft.gui.rendering.world.mesh.WorldMesh
|
|
||||||
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.MeshLoadingQueue
|
||||||
import de.bixilon.minosoft.gui.rendering.world.queue.MeshUnloadingQueue
|
import de.bixilon.minosoft.gui.rendering.world.queue.MeshUnloadingQueue
|
||||||
import de.bixilon.minosoft.gui.rendering.world.queue.meshing.ChunkMeshingQueue
|
import de.bixilon.minosoft.gui.rendering.world.queue.meshing.ChunkMeshingQueue
|
||||||
import de.bixilon.minosoft.gui.rendering.world.queue.meshing.tasks.MeshPrepareTask
|
|
||||||
import de.bixilon.minosoft.gui.rendering.world.shader.WorldShader
|
import de.bixilon.minosoft.gui.rendering.world.shader.WorldShader
|
||||||
import de.bixilon.minosoft.gui.rendering.world.shader.WorldTextShader
|
import de.bixilon.minosoft.gui.rendering.world.shader.WorldTextShader
|
||||||
import de.bixilon.minosoft.modding.event.events.RespawnEvent
|
import de.bixilon.minosoft.gui.rendering.world.util.WorldRendererChangeListener
|
||||||
import de.bixilon.minosoft.modding.event.events.blocks.BlockDataChangeEvent
|
|
||||||
import de.bixilon.minosoft.modding.event.events.blocks.BlockSetEvent
|
|
||||||
import de.bixilon.minosoft.modding.event.events.blocks.BlocksSetEvent
|
|
||||||
import de.bixilon.minosoft.modding.event.events.blocks.chunk.ChunkDataChangeEvent
|
|
||||||
import de.bixilon.minosoft.modding.event.events.blocks.chunk.ChunkUnloadEvent
|
|
||||||
import de.bixilon.minosoft.modding.event.events.blocks.chunk.LightChangeEvent
|
|
||||||
import de.bixilon.minosoft.modding.event.listener.CallbackEventListener.Companion.listen
|
import de.bixilon.minosoft.modding.event.listener.CallbackEventListener.Companion.listen
|
||||||
import de.bixilon.minosoft.protocol.network.connection.play.PlayConnection
|
import de.bixilon.minosoft.protocol.network.connection.play.PlayConnection
|
||||||
import de.bixilon.minosoft.protocol.network.connection.play.PlayConnectionStates
|
|
||||||
import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition
|
import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition
|
||||||
import de.bixilon.minosoft.util.KUtil.toResourceLocation
|
import de.bixilon.minosoft.util.KUtil.toResourceLocation
|
||||||
import de.bixilon.minosoft.util.chunk.ChunkUtil
|
import de.bixilon.minosoft.util.chunk.ChunkUtil
|
||||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap
|
|
||||||
import it.unimi.dsi.fastutil.ints.IntOpenHashSet
|
import it.unimi.dsi.fastutil.ints.IntOpenHashSet
|
||||||
|
|
||||||
class WorldRenderer(
|
class WorldRenderer(
|
||||||
@ -86,9 +67,7 @@ class WorldRenderer(
|
|||||||
private val shader = renderSystem.createShader("minosoft:world".toResourceLocation()) { WorldShader(it, false) }
|
private val shader = renderSystem.createShader("minosoft:world".toResourceLocation()) { WorldShader(it, false) }
|
||||||
private val transparentShader = renderSystem.createShader("minosoft:world".toResourceLocation()) { WorldShader(it, true) }
|
private val transparentShader = renderSystem.createShader("minosoft:world".toResourceLocation()) { WorldShader(it, true) }
|
||||||
private val textShader = renderSystem.createShader("minosoft:world/text".toResourceLocation()) { WorldTextShader(it) }
|
private val textShader = renderSystem.createShader("minosoft:world/text".toResourceLocation()) { WorldTextShader(it) }
|
||||||
private val world: World = connection.world
|
val world: World = connection.world
|
||||||
private val solidSectionPreparer: SolidSectionPreparer = SolidCullSectionPreparer(renderWindow)
|
|
||||||
private val fluidSectionPreparer: FluidSectionPreparer = FluidCullSectionPreparer(renderWindow)
|
|
||||||
|
|
||||||
val loaded = LoadedMeshes(this)
|
val loaded = LoadedMeshes(this)
|
||||||
|
|
||||||
@ -99,6 +78,8 @@ class WorldRenderer(
|
|||||||
val loadingQueue = MeshLoadingQueue(this)
|
val loadingQueue = MeshLoadingQueue(this)
|
||||||
val unloadingQueue = MeshUnloadingQueue(this)
|
val unloadingQueue = MeshUnloadingQueue(this)
|
||||||
|
|
||||||
|
val mesher = ChunkMesher(this)
|
||||||
|
|
||||||
// all meshes that will be rendered in the next frame (might be changed, when the frustum changes or a chunk gets loaded, ...)
|
// 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
|
private var clearVisibleNextFrame = false
|
||||||
var visible = VisibleMeshes() // This name might be confusing. Those faces are from blocks.
|
var visible = VisibleMeshes() // This name might be confusing. Those faces are from blocks.
|
||||||
@ -109,12 +90,7 @@ class WorldRenderer(
|
|||||||
var cameraChunkPosition = Vec2i.EMPTY
|
var cameraChunkPosition = Vec2i.EMPTY
|
||||||
var cameraSectionHeight = 0
|
var cameraSectionHeight = 0
|
||||||
|
|
||||||
@Deprecated("alias?", ReplaceWith("visible.sizeString")) val visibleSize: String get() = visible.sizeString
|
|
||||||
@Deprecated("alias?", ReplaceWith("loaded.size")) val loadedMeshesSize: Int get() = loaded.size
|
|
||||||
@Deprecated("alias?") val culledQueuedSize: Int get() = culledQueue.size
|
@Deprecated("alias?") val culledQueuedSize: Int get() = culledQueue.size
|
||||||
@Deprecated("alias?", ReplaceWith("loadingQueue.size")) val meshesToLoadSize: Int get() = loadingQueue.size
|
|
||||||
@Deprecated("alias?", ReplaceWith("queue.size")) val queueSize: Int get() = queue.size
|
|
||||||
@Deprecated("alias?", ReplaceWith("queue.tasks.size")) val preparingTasksSize: Int get() = queue.tasks.size
|
|
||||||
|
|
||||||
override fun init(latch: CountUpAndDownLatch) {
|
override fun init(latch: CountUpAndDownLatch) {
|
||||||
renderWindow.modelLoader.load(latch)
|
renderWindow.modelLoader.load(latch)
|
||||||
@ -128,93 +104,7 @@ class WorldRenderer(
|
|||||||
|
|
||||||
connection.events.listen<VisibilityGraphChangeEvent> { onFrustumChange() }
|
connection.events.listen<VisibilityGraphChangeEvent> { onFrustumChange() }
|
||||||
|
|
||||||
connection.events.listen<RespawnEvent> { if (it.dimensionChange) unloadWorld() }
|
WorldRendererChangeListener.register(this)
|
||||||
connection.events.listen<ChunkDataChangeEvent> { queueChunk(it.chunkPosition, it.chunk) }
|
|
||||||
connection.events.listen<BlockSetEvent> {
|
|
||||||
val chunkPosition = it.blockPosition.chunkPosition
|
|
||||||
val sectionHeight = it.blockPosition.sectionHeight
|
|
||||||
val chunk = world[chunkPosition] ?: return@listen
|
|
||||||
val neighbours = chunk.neighbours.get() ?: return@listen
|
|
||||||
queueSection(chunkPosition, sectionHeight, chunk, neighbours = neighbours)
|
|
||||||
val inChunkSectionPosition = it.blockPosition.inChunkSectionPosition
|
|
||||||
|
|
||||||
if (inChunkSectionPosition.y == 0) {
|
|
||||||
queueSection(chunkPosition, sectionHeight - 1, chunk, neighbours = neighbours)
|
|
||||||
} else if (inChunkSectionPosition.y == ProtocolDefinition.SECTION_MAX_Y) {
|
|
||||||
queueSection(chunkPosition, sectionHeight + 1, chunk, neighbours = neighbours)
|
|
||||||
}
|
|
||||||
if (inChunkSectionPosition.z == 0) {
|
|
||||||
queueSection(Vec2i(chunkPosition.x, chunkPosition.y - 1), sectionHeight, chunk = neighbours[3])
|
|
||||||
} else if (inChunkSectionPosition.z == ProtocolDefinition.SECTION_MAX_Z) {
|
|
||||||
queueSection(Vec2i(chunkPosition.x, chunkPosition.y + 1), sectionHeight, chunk = neighbours[4])
|
|
||||||
}
|
|
||||||
if (inChunkSectionPosition.x == 0) {
|
|
||||||
queueSection(Vec2i(chunkPosition.x - 1, chunkPosition.y), sectionHeight, chunk = neighbours[1])
|
|
||||||
} else if (inChunkSectionPosition.x == ProtocolDefinition.SECTION_MAX_X) {
|
|
||||||
queueSection(Vec2i(chunkPosition.x + 1, chunkPosition.y), sectionHeight, chunk = neighbours[6])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
connection.events.listen<LightChangeEvent> {
|
|
||||||
if (it.blockChange) {
|
|
||||||
// change is already covered
|
|
||||||
return@listen
|
|
||||||
}
|
|
||||||
queueSection(it.chunkPosition, it.sectionHeight, it.chunk)
|
|
||||||
}
|
|
||||||
|
|
||||||
connection.events.listen<BlocksSetEvent> {
|
|
||||||
val chunk = world[it.chunkPosition] ?: return@listen // should not happen
|
|
||||||
if (!chunk.isFullyLoaded) {
|
|
||||||
return@listen
|
|
||||||
}
|
|
||||||
val sectionHeights: Int2ObjectOpenHashMap<BooleanArray> = Int2ObjectOpenHashMap()
|
|
||||||
for (blockPosition in it.blocks.keys) {
|
|
||||||
val neighbours = sectionHeights.getOrPut(blockPosition.sectionHeight) { BooleanArray(Directions.SIZE) }
|
|
||||||
val inSectionHeight = blockPosition.y.inSectionHeight
|
|
||||||
if (inSectionHeight == 0) {
|
|
||||||
neighbours[0] = true
|
|
||||||
} else if (inSectionHeight == ProtocolDefinition.SECTION_MAX_Y) {
|
|
||||||
neighbours[1] = true
|
|
||||||
}
|
|
||||||
if (blockPosition.z == 0) {
|
|
||||||
neighbours[2] = true
|
|
||||||
} else if (blockPosition.z == ProtocolDefinition.SECTION_MAX_Z) {
|
|
||||||
neighbours[3] = true
|
|
||||||
}
|
|
||||||
if (blockPosition.x == 0) {
|
|
||||||
neighbours[4] = true
|
|
||||||
} else if (blockPosition.x == ProtocolDefinition.SECTION_MAX_X) {
|
|
||||||
neighbours[5] = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val neighbours = chunk.neighbours.get() ?: return@listen
|
|
||||||
for ((sectionHeight, neighbourUpdates) in sectionHeights) {
|
|
||||||
queueSection(it.chunkPosition, sectionHeight, chunk, neighbours = neighbours)
|
|
||||||
|
|
||||||
if (neighbourUpdates[0]) {
|
|
||||||
queueSection(it.chunkPosition, sectionHeight - 1, chunk, neighbours = neighbours)
|
|
||||||
}
|
|
||||||
if (neighbourUpdates[1]) {
|
|
||||||
queueSection(it.chunkPosition, sectionHeight + 1, chunk, neighbours = neighbours)
|
|
||||||
}
|
|
||||||
if (neighbourUpdates[2]) {
|
|
||||||
queueSection(Vec2i(it.chunkPosition.x, it.chunkPosition.y - 1), sectionHeight, chunk = neighbours[3])
|
|
||||||
}
|
|
||||||
if (neighbourUpdates[3]) {
|
|
||||||
queueSection(Vec2i(it.chunkPosition.x, it.chunkPosition.y + 1), sectionHeight, chunk = neighbours[4])
|
|
||||||
}
|
|
||||||
if (neighbourUpdates[4]) {
|
|
||||||
queueSection(Vec2i(it.chunkPosition.x - 1, it.chunkPosition.y), sectionHeight, chunk = neighbours[1])
|
|
||||||
}
|
|
||||||
if (neighbourUpdates[5]) {
|
|
||||||
queueSection(Vec2i(it.chunkPosition.x + 1, it.chunkPosition.y), sectionHeight, chunk = neighbours[6])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
connection.events.listen<ChunkUnloadEvent> { unloadChunk(it.chunkPosition) }
|
|
||||||
connection::state.observe(this) { if (it == PlayConnectionStates.DISCONNECTED) unloadWorld() }
|
|
||||||
|
|
||||||
var paused = false
|
var paused = false
|
||||||
renderWindow::state.observe(this) {
|
renderWindow::state.observe(this) {
|
||||||
@ -226,7 +116,6 @@ class WorldRenderer(
|
|||||||
paused = false
|
paused = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
connection.events.listen<BlockDataChangeEvent> { queueSection(it.blockPosition.chunkPosition, it.blockPosition.sectionHeight) }
|
|
||||||
|
|
||||||
renderWindow.inputHandler.registerKeyCallback(
|
renderWindow.inputHandler.registerKeyCallback(
|
||||||
"minosoft:clear_chunk_cache".toResourceLocation(),
|
"minosoft:clear_chunk_cache".toResourceLocation(),
|
||||||
@ -298,7 +187,7 @@ class WorldRenderer(
|
|||||||
world.lock.release()
|
world.lock.release()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun unloadWorld() {
|
fun unloadWorld() {
|
||||||
culledQueueLock.lock()
|
culledQueueLock.lock()
|
||||||
queue.lock()
|
queue.lock()
|
||||||
loadingQueue.lock()
|
loadingQueue.lock()
|
||||||
@ -323,7 +212,7 @@ class WorldRenderer(
|
|||||||
loadingQueue.unlock()
|
loadingQueue.unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun unloadChunk(chunkPosition: Vec2i) {
|
fun unloadChunk(chunkPosition: Vec2i) {
|
||||||
culledQueueLock.lock()
|
culledQueueLock.lock()
|
||||||
queue.lock()
|
queue.lock()
|
||||||
loadingQueue.lock()
|
loadingQueue.lock()
|
||||||
@ -349,42 +238,8 @@ class WorldRenderer(
|
|||||||
queue.unlock()
|
queue.unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun prepareItem(item: WorldQueueItem, task: MeshPrepareTask, runnable: ThreadPoolRunnable) {
|
|
||||||
try {
|
|
||||||
val chunk = item.chunk ?: world[item.chunkPosition] ?: return
|
|
||||||
val section = chunk[item.sectionHeight] ?: return
|
|
||||||
if (section.blocks.isEmpty) {
|
|
||||||
return queueItemUnload(item)
|
|
||||||
}
|
|
||||||
val neighbourChunks: Array<Chunk> = chunk.neighbours.get() ?: return queueSection(item.chunkPosition, item.sectionHeight, chunk, section, neighbours = null)
|
|
||||||
val neighbours = item.neighbours ?: ChunkUtil.getDirectNeighbours(neighbourChunks, chunk, item.sectionHeight)
|
|
||||||
val mesh = WorldMesh(renderWindow, item.chunkPosition, item.sectionHeight, section.blocks.count < ProtocolDefinition.SECTION_MAX_X * ProtocolDefinition.SECTION_MAX_Z)
|
|
||||||
solidSectionPreparer.prepareSolid(item.chunkPosition, item.sectionHeight, chunk, section, neighbours, neighbourChunks, mesh)
|
|
||||||
if (section.blocks.fluidCount > 0) {
|
|
||||||
fluidSectionPreparer.prepareFluid(item.chunkPosition, item.sectionHeight, chunk, section, neighbours, neighbourChunks, mesh)
|
|
||||||
}
|
|
||||||
runnable.interruptable = false
|
|
||||||
if (Thread.interrupted()) return
|
|
||||||
if (mesh.clearEmpty() == 0) {
|
|
||||||
return queueItemUnload(item)
|
|
||||||
}
|
|
||||||
mesh.finish()
|
|
||||||
item.mesh = mesh
|
|
||||||
loadingQueue.queue(mesh)
|
|
||||||
} catch (exception: Throwable) {
|
|
||||||
if (exception !is InterruptedException) {
|
|
||||||
// otherwise task got interrupted (probably because of chunk unload)
|
|
||||||
throw exception
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
task.runnable.interruptable = false
|
|
||||||
if (Thread.interrupted()) throw InterruptedException()
|
|
||||||
queue.tasks -= task
|
|
||||||
queue.work()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun queueItemUnload(item: WorldQueueItem) {
|
fun queueItemUnload(item: WorldQueueItem) {
|
||||||
culledQueueLock.lock()
|
culledQueueLock.lock()
|
||||||
queue.lock()
|
queue.lock()
|
||||||
loadingQueue.lock()
|
loadingQueue.lock()
|
||||||
@ -438,7 +293,7 @@ class WorldRenderer(
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun queueSection(chunkPosition: Vec2i, sectionHeight: Int, chunk: Chunk? = world.chunks[chunkPosition], section: ChunkSection? = chunk?.get(sectionHeight), ignoreFrustum: Boolean = false, neighbours: Array<Chunk>? = chunk?.neighbours?.get()) {
|
fun queueSection(chunkPosition: Vec2i, sectionHeight: Int, chunk: Chunk? = world.chunks[chunkPosition], section: ChunkSection? = chunk?.get(sectionHeight), ignoreFrustum: Boolean = false, neighbours: Array<Chunk>? = chunk?.neighbours?.get()) {
|
||||||
if (chunk == null || neighbours == null || section == null || renderWindow.state == RenderingStates.PAUSED) {
|
if (chunk == null || neighbours == null || section == null || renderWindow.state == RenderingStates.PAUSED) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -450,7 +305,7 @@ class WorldRenderer(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun queueChunk(chunkPosition: Vec2i, chunk: Chunk) {
|
fun queueChunk(chunkPosition: Vec2i, chunk: Chunk) {
|
||||||
val neighbours = chunk.neighbours.get()
|
val neighbours = chunk.neighbours.get()
|
||||||
if (neighbours == null || !chunk.isFullyLoaded || renderWindow.state == RenderingStates.PAUSED) {
|
if (neighbours == null || !chunk.isFullyLoaded || renderWindow.state == RenderingStates.PAUSED) {
|
||||||
return
|
return
|
||||||
|
@ -18,8 +18,8 @@ import de.bixilon.kutil.concurrent.lock.simple.SimpleLock
|
|||||||
import de.bixilon.kutil.time.TimeUtil
|
import de.bixilon.kutil.time.TimeUtil
|
||||||
import de.bixilon.minosoft.data.world.positions.ChunkPosition
|
import de.bixilon.minosoft.data.world.positions.ChunkPosition
|
||||||
import de.bixilon.minosoft.gui.rendering.world.WorldRenderer
|
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 de.bixilon.minosoft.gui.rendering.world.mesh.WorldMesh
|
||||||
|
import de.bixilon.minosoft.gui.rendering.world.util.WorldRendererUtil.maxBusyTime
|
||||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap
|
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap
|
||||||
|
|
||||||
class MeshLoadingQueue(
|
class MeshLoadingQueue(
|
||||||
|
@ -16,8 +16,8 @@ package de.bixilon.minosoft.gui.rendering.world.queue
|
|||||||
import de.bixilon.kutil.concurrent.lock.simple.SimpleLock
|
import de.bixilon.kutil.concurrent.lock.simple.SimpleLock
|
||||||
import de.bixilon.kutil.time.TimeUtil
|
import de.bixilon.kutil.time.TimeUtil
|
||||||
import de.bixilon.minosoft.gui.rendering.world.WorldRenderer
|
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 de.bixilon.minosoft.gui.rendering.world.mesh.WorldMesh
|
||||||
|
import de.bixilon.minosoft.gui.rendering.world.util.WorldRendererUtil.maxBusyTime
|
||||||
|
|
||||||
class MeshUnloadingQueue(
|
class MeshUnloadingQueue(
|
||||||
private val renderer: WorldRenderer,
|
private val renderer: WorldRenderer,
|
||||||
|
@ -81,7 +81,7 @@ class ChunkMeshingQueue(
|
|||||||
for (item in items) {
|
for (item in items) {
|
||||||
val runnable = ThreadPoolRunnable(if (item.chunkPosition == renderer.cameraChunkPosition) ThreadPool.HIGH else ThreadPool.LOW, interruptable = true) // Our own chunk is the most important one ToDo: Also make neighbour chunks important
|
val runnable = ThreadPoolRunnable(if (item.chunkPosition == renderer.cameraChunkPosition) ThreadPool.HIGH else ThreadPool.LOW, interruptable = true) // Our own chunk is the most important one ToDo: Also make neighbour chunks important
|
||||||
val task = MeshPrepareTask(item.chunkPosition, item.sectionHeight, runnable)
|
val task = MeshPrepareTask(item.chunkPosition, item.sectionHeight, runnable)
|
||||||
task.runnable.runnable = Runnable { renderer.prepareItem(item, task, task.runnable) }
|
task.runnable.runnable = Runnable { renderer.mesher.prepareItem(item, task, task.runnable) }
|
||||||
tasks += task
|
tasks += task
|
||||||
}
|
}
|
||||||
working = false
|
working = false
|
||||||
|
@ -0,0 +1,139 @@
|
|||||||
|
/*
|
||||||
|
* 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.util
|
||||||
|
|
||||||
|
import de.bixilon.kotlinglm.vec2.Vec2i
|
||||||
|
import de.bixilon.kutil.observer.DataObserver.Companion.observe
|
||||||
|
import de.bixilon.minosoft.data.direction.Directions
|
||||||
|
import de.bixilon.minosoft.gui.rendering.util.VecUtil.inSectionHeight
|
||||||
|
import de.bixilon.minosoft.gui.rendering.util.vec.vec3.Vec3iUtil.chunkPosition
|
||||||
|
import de.bixilon.minosoft.gui.rendering.util.vec.vec3.Vec3iUtil.inChunkSectionPosition
|
||||||
|
import de.bixilon.minosoft.gui.rendering.util.vec.vec3.Vec3iUtil.sectionHeight
|
||||||
|
import de.bixilon.minosoft.gui.rendering.world.WorldRenderer
|
||||||
|
import de.bixilon.minosoft.modding.event.events.RespawnEvent
|
||||||
|
import de.bixilon.minosoft.modding.event.events.blocks.BlockDataChangeEvent
|
||||||
|
import de.bixilon.minosoft.modding.event.events.blocks.BlockSetEvent
|
||||||
|
import de.bixilon.minosoft.modding.event.events.blocks.BlocksSetEvent
|
||||||
|
import de.bixilon.minosoft.modding.event.events.blocks.chunk.ChunkDataChangeEvent
|
||||||
|
import de.bixilon.minosoft.modding.event.events.blocks.chunk.ChunkUnloadEvent
|
||||||
|
import de.bixilon.minosoft.modding.event.events.blocks.chunk.LightChangeEvent
|
||||||
|
import de.bixilon.minosoft.modding.event.listener.CallbackEventListener.Companion.listen
|
||||||
|
import de.bixilon.minosoft.protocol.network.connection.play.PlayConnectionStates
|
||||||
|
import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition
|
||||||
|
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap
|
||||||
|
|
||||||
|
object WorldRendererChangeListener {
|
||||||
|
|
||||||
|
private fun listenBlockSet(renderer: WorldRenderer) {
|
||||||
|
renderer.connection.events.listen<BlockSetEvent> {
|
||||||
|
val chunkPosition = it.blockPosition.chunkPosition
|
||||||
|
val sectionHeight = it.blockPosition.sectionHeight
|
||||||
|
val chunk = renderer.world[chunkPosition] ?: return@listen
|
||||||
|
val neighbours = chunk.neighbours.get() ?: return@listen
|
||||||
|
renderer.queueSection(chunkPosition, sectionHeight, chunk, neighbours = neighbours)
|
||||||
|
val inChunkSectionPosition = it.blockPosition.inChunkSectionPosition
|
||||||
|
|
||||||
|
if (inChunkSectionPosition.y == 0) {
|
||||||
|
renderer.queueSection(chunkPosition, sectionHeight - 1, chunk, neighbours = neighbours)
|
||||||
|
} else if (inChunkSectionPosition.y == ProtocolDefinition.SECTION_MAX_Y) {
|
||||||
|
renderer.queueSection(chunkPosition, sectionHeight + 1, chunk, neighbours = neighbours)
|
||||||
|
}
|
||||||
|
if (inChunkSectionPosition.z == 0) {
|
||||||
|
renderer.queueSection(Vec2i(chunkPosition.x, chunkPosition.y - 1), sectionHeight, chunk = neighbours[3])
|
||||||
|
} else if (inChunkSectionPosition.z == ProtocolDefinition.SECTION_MAX_Z) {
|
||||||
|
renderer.queueSection(Vec2i(chunkPosition.x, chunkPosition.y + 1), sectionHeight, chunk = neighbours[4])
|
||||||
|
}
|
||||||
|
if (inChunkSectionPosition.x == 0) {
|
||||||
|
renderer.queueSection(Vec2i(chunkPosition.x - 1, chunkPosition.y), sectionHeight, chunk = neighbours[1])
|
||||||
|
} else if (inChunkSectionPosition.x == ProtocolDefinition.SECTION_MAX_X) {
|
||||||
|
renderer.queueSection(Vec2i(chunkPosition.x + 1, chunkPosition.y), sectionHeight, chunk = neighbours[6])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun listenBlocksSet(renderer: WorldRenderer) {
|
||||||
|
renderer.connection.events.listen<BlocksSetEvent> {
|
||||||
|
val chunk = renderer.world[it.chunkPosition] ?: return@listen // should not happen
|
||||||
|
if (!chunk.isFullyLoaded) {
|
||||||
|
return@listen
|
||||||
|
}
|
||||||
|
val sectionHeights: Int2ObjectOpenHashMap<BooleanArray> = Int2ObjectOpenHashMap()
|
||||||
|
for (blockPosition in it.blocks.keys) {
|
||||||
|
val neighbours = sectionHeights.getOrPut(blockPosition.sectionHeight) { BooleanArray(Directions.SIZE) }
|
||||||
|
val inSectionHeight = blockPosition.y.inSectionHeight
|
||||||
|
if (inSectionHeight == 0) {
|
||||||
|
neighbours[0] = true
|
||||||
|
} else if (inSectionHeight == ProtocolDefinition.SECTION_MAX_Y) {
|
||||||
|
neighbours[1] = true
|
||||||
|
}
|
||||||
|
if (blockPosition.z == 0) {
|
||||||
|
neighbours[2] = true
|
||||||
|
} else if (blockPosition.z == ProtocolDefinition.SECTION_MAX_Z) {
|
||||||
|
neighbours[3] = true
|
||||||
|
}
|
||||||
|
if (blockPosition.x == 0) {
|
||||||
|
neighbours[4] = true
|
||||||
|
} else if (blockPosition.x == ProtocolDefinition.SECTION_MAX_X) {
|
||||||
|
neighbours[5] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val neighbours = chunk.neighbours.get() ?: return@listen
|
||||||
|
for ((sectionHeight, neighbourUpdates) in sectionHeights) {
|
||||||
|
renderer.queueSection(it.chunkPosition, sectionHeight, chunk, neighbours = neighbours)
|
||||||
|
|
||||||
|
if (neighbourUpdates[0]) {
|
||||||
|
renderer.queueSection(it.chunkPosition, sectionHeight - 1, chunk, neighbours = neighbours)
|
||||||
|
}
|
||||||
|
if (neighbourUpdates[1]) {
|
||||||
|
renderer.queueSection(it.chunkPosition, sectionHeight + 1, chunk, neighbours = neighbours)
|
||||||
|
}
|
||||||
|
if (neighbourUpdates[2]) {
|
||||||
|
renderer.queueSection(Vec2i(it.chunkPosition.x, it.chunkPosition.y - 1), sectionHeight, chunk = neighbours[3])
|
||||||
|
}
|
||||||
|
if (neighbourUpdates[3]) {
|
||||||
|
renderer.queueSection(Vec2i(it.chunkPosition.x, it.chunkPosition.y + 1), sectionHeight, chunk = neighbours[4])
|
||||||
|
}
|
||||||
|
if (neighbourUpdates[4]) {
|
||||||
|
renderer.queueSection(Vec2i(it.chunkPosition.x - 1, it.chunkPosition.y), sectionHeight, chunk = neighbours[1])
|
||||||
|
}
|
||||||
|
if (neighbourUpdates[5]) {
|
||||||
|
renderer.queueSection(Vec2i(it.chunkPosition.x + 1, it.chunkPosition.y), sectionHeight, chunk = neighbours[6])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun register(renderer: WorldRenderer) {
|
||||||
|
val events = renderer.connection.events
|
||||||
|
|
||||||
|
listenBlockSet(renderer)
|
||||||
|
listenBlocksSet(renderer)
|
||||||
|
|
||||||
|
events.listen<RespawnEvent> { if (it.dimensionChange) renderer.unloadWorld() }
|
||||||
|
events.listen<ChunkDataChangeEvent> { renderer.queueChunk(it.chunkPosition, it.chunk) }
|
||||||
|
|
||||||
|
events.listen<LightChangeEvent> {
|
||||||
|
if (it.blockChange) {
|
||||||
|
// change is already covered
|
||||||
|
return@listen
|
||||||
|
}
|
||||||
|
renderer.queueSection(it.chunkPosition, it.sectionHeight, it.chunk)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
events.listen<ChunkUnloadEvent> { renderer.unloadChunk(it.chunkPosition) }
|
||||||
|
renderer.connection::state.observe(this) { if (it == PlayConnectionStates.DISCONNECTED) renderer.unloadWorld() }
|
||||||
|
events.listen<BlockDataChangeEvent> { renderer.queueSection(it.blockPosition.chunkPosition, it.blockPosition.sectionHeight) }
|
||||||
|
}
|
||||||
|
}
|
@ -11,9 +11,12 @@
|
|||||||
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
|
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package de.bixilon.minosoft.gui.rendering.world
|
package de.bixilon.minosoft.gui.rendering.world.util
|
||||||
|
|
||||||
|
import de.bixilon.minosoft.data.world.chunk.ChunkSection
|
||||||
import de.bixilon.minosoft.gui.rendering.util.VecUtil.empty
|
import de.bixilon.minosoft.gui.rendering.util.VecUtil.empty
|
||||||
|
import de.bixilon.minosoft.gui.rendering.world.WorldRenderer
|
||||||
|
import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition
|
||||||
|
|
||||||
object WorldRendererUtil {
|
object WorldRendererUtil {
|
||||||
const val STILL_LOADING_TIME = 50L
|
const val STILL_LOADING_TIME = 50L
|
||||||
@ -22,4 +25,7 @@ object WorldRendererUtil {
|
|||||||
|
|
||||||
// If the player is still, then we can load more chunks (to not cause lags)
|
// If the player is still, then we can load more chunks (to not cause lags)
|
||||||
val WorldRenderer.maxBusyTime: 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
|
||||||
|
|
||||||
|
|
||||||
|
val ChunkSection.smallMesh: Boolean get() = blocks.count < ProtocolDefinition.SECTION_MAX_X * ProtocolDefinition.SECTION_MAX_Z
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user