wip: chunk cache

This commit is contained in:
Bixilon 2021-11-14 18:45:34 +01:00
parent d4a562e429
commit 5c3b1f31b8
No known key found for this signature in database
GPG Key ID: 5CAD791931B09AC4
8 changed files with 154 additions and 20 deletions

View File

@ -70,7 +70,7 @@ data class DimensionProperties(
hasCeiling = data["has_ceiling"]?.toBoolean() ?: false,
ultraWarm = data["ultrawarm"]?.toBoolean() ?: false,
height = data["height"]?.toInt() ?: 256,
supports3DBiomes = data["supports_3d_biomes"]?.toBoolean() ?: false,
supports3DBiomes = data["supports_3d_biomes"]?.toBoolean() ?: true,
)
}
}

View File

@ -13,11 +13,16 @@
package de.bixilon.minosoft.data.world
import de.bixilon.minosoft.data.entities.block.BlockEntity
import de.bixilon.minosoft.data.registries.biomes.Biome
import de.bixilon.minosoft.data.registries.blocks.BlockState
import de.bixilon.minosoft.data.world.ChunkSection.Companion.index
import de.bixilon.minosoft.data.world.biome.accessor.BiomeAccessor
import de.bixilon.minosoft.data.world.biome.source.BiomeSource
import de.bixilon.minosoft.gui.rendering.util.VecUtil.inChunkPosition
import de.bixilon.minosoft.gui.rendering.util.VecUtil.inChunkSectionPosition
import de.bixilon.minosoft.gui.rendering.util.VecUtil.sectionHeight
import de.bixilon.minosoft.modding.event.EventInitiators
import de.bixilon.minosoft.modding.event.events.ChunkDataChangeEvent
import de.bixilon.minosoft.protocol.network.connection.play.PlayConnection
import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition
import glm_.vec2.Vec2i
@ -28,21 +33,22 @@ import glm_.vec3.Vec3i
*/
class Chunk(
private val connection: PlayConnection,
private val chunkPosition: Vec2i,
private var sections: Array<ChunkSection?>? = null,
var biomeSource: BiomeSource? = null,
) : Iterable<ChunkSection?> {
) : Iterable<ChunkSection?>, BiomeAccessor {
var bottomLight: IntArray? = null
var topLight: IntArray? = null
val lowestSection = connection.world.dimension!!.lowestSection
val highestSection = connection.world.dimension!!.highestSection
var blocksInitialized: Boolean = false
val biomesInitialized
get() = biomeSource != null
var blocksInitialized = false // All block data was received
var biomesInitialized = false // All biome data is initialized (aka. cache built, or similar)
var lightInitialized = false
var neighboursLoaded = false
val isFullyLoaded: Boolean
get() = blocksInitialized && biomesInitialized && lightInitialized
get() = blocksInitialized && biomesInitialized && lightInitialized && neighboursLoaded
operator fun get(sectionHeight: Int): ChunkSection? = sections?.getOrNull(sectionHeight - lowestSection)
@ -115,7 +121,10 @@ class Chunk(
topLight = it
lightInitialized = true
}
data.biomeSource?.let { this.biomeSource = it }
data.biomeSource?.let {
this.biomeSource = it
}
connection.world.onChunkUpdate(chunkPosition, this)
}
@Synchronized
@ -126,6 +135,10 @@ class Chunk(
var section = sections[sectionIndex]
if (section == null) {
section = ChunkSection(connection.registries)
val cacheBiomeAccessor = connection.world.cacheBiomeAccessor
if (cacheBiomeAccessor != null && biomesInitialized && neighboursLoaded) {
section.buildBiomeCache(chunkPosition, sectionHeight, cacheBiomeAccessor)
}
sections[sectionIndex] = section
}
return section
@ -154,7 +167,27 @@ class Chunk(
return get(position.sectionHeight)?.light?.get(index) ?: 0xFF
}
fun buildBiomeCache() {
val cacheBiomeAccessor = connection.world.cacheBiomeAccessor ?: return
check(neighboursLoaded)
// val neighbours = connection.world.getChunkNeighbours(chunkPosition)
for ((sectionIndex, section) in sections!!.withIndex()) {
section ?: continue
val sectionHeight = sectionIndex + lowestSection
section.buildBiomeCache(chunkPosition, sectionHeight, cacheBiomeAccessor)
}
biomesInitialized = true
connection.fireEvent(ChunkDataChangeEvent(connection, EventInitiators.UNKNOWN, chunkPosition, this))
}
override fun iterator(): Iterator<ChunkSection?> {
return sections!!.iterator()
}
override fun getBiome(blockPosition: Vec3i): Biome? {
if (connection.world.cacheBiomeAccessor != null) {
return get(blockPosition.sectionHeight)?.biomes?.get(blockPosition.x, blockPosition.sectionHeight, blockPosition.z)
}
return biomeSource?.getBiome(blockPosition.inChunkPosition)
}
}

View File

@ -16,6 +16,7 @@ import de.bixilon.minosoft.data.entities.block.BlockEntity
import de.bixilon.minosoft.data.registries.biomes.Biome
import de.bixilon.minosoft.data.registries.blocks.BlockState
import de.bixilon.minosoft.data.registries.registries.Registries
import de.bixilon.minosoft.data.world.biome.accessor.BiomeAccessor
import de.bixilon.minosoft.data.world.container.RegistrySectionDataProvider
import de.bixilon.minosoft.data.world.container.SectionDataProvider
import de.bixilon.minosoft.gui.rendering.util.VecUtil.of
@ -30,7 +31,6 @@ import glm_.vec3.Vec3i
*/
class ChunkSection(
var blocks: RegistrySectionDataProvider<BlockState?>,
@Deprecated("TODO")
var biomes: RegistrySectionDataProvider<Biome>,
var blockEntities: SectionDataProvider<BlockEntity?>,
var light: IntArray, // packed (skyLight: 0xF0, blockLight: 0x0F)
@ -84,4 +84,10 @@ class ChunkSection(
return y shl 8 or (z shl 4) or x
}
}
fun buildBiomeCache(chunkPosition: Vec2i, sectionHeight: Int, biomeAccessor: BiomeAccessor) {
for (blockIndex in 0 until ProtocolDefinition.BLOCKS_PER_SECTION) {
biomes[blockIndex] = biomeAccessor.getBiome(Vec3i.of(chunkPosition, sectionHeight, blockIndex.indexPosition))!!
}
}
}

View File

@ -22,7 +22,7 @@ import de.bixilon.minosoft.data.registries.blocks.types.FluidBlock
import de.bixilon.minosoft.data.registries.dimension.DimensionProperties
import de.bixilon.minosoft.data.registries.sounds.SoundEvent
import de.bixilon.minosoft.data.world.biome.accessor.BiomeAccessor
import de.bixilon.minosoft.data.world.biome.accessor.NullBiomeAccessor
import de.bixilon.minosoft.data.world.biome.accessor.WorldBiomeAccessor
import de.bixilon.minosoft.gui.rendering.particle.ParticleRenderer
import de.bixilon.minosoft.gui.rendering.particle.types.Particle
import de.bixilon.minosoft.gui.rendering.sound.AudioPlayer
@ -39,6 +39,8 @@ import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition
import de.bixilon.minosoft.util.KUtil.synchronizedMapOf
import de.bixilon.minosoft.util.KUtil.toSynchronizedMap
import de.bixilon.minosoft.util.MMath
import de.bixilon.minosoft.util.chunk.ChunkUtil
import de.bixilon.minosoft.util.chunk.ChunkUtil.fullyLoaded
import de.bixilon.minosoft.util.collections.SynchronizedMap
import glm_.func.common.clamp
import glm_.vec2.Vec2i
@ -55,6 +57,7 @@ import kotlin.random.Random
class World(
val connection: PlayConnection,
) : BiomeAccessor {
var cacheBiomeAccessor: BiomeAccessor? = null
val chunks: SynchronizedMap<Vec2i, Chunk> = synchronizedMapOf()
val entities = WorldEntities()
var hardcore = false
@ -62,7 +65,7 @@ class World(
var difficulty: Difficulties? = null
var difficultyLocked = false
var hashedSeed = 0L
var biomeAccessor: BiomeAccessor = NullBiomeAccessor
val biomeAccessor: BiomeAccessor = WorldBiomeAccessor(this)
var time = 0L
var age = 0L
var raining = false
@ -82,7 +85,7 @@ class World(
}
fun getOrCreateChunk(chunkPosition: Vec2i): Chunk {
return chunks.getOrPut(chunkPosition) { Chunk(connection) }
return chunks.getOrPut(chunkPosition) { Chunk(connection, chunkPosition) }
}
fun setBlockState(blockPosition: Vec3i, blockState: BlockState?) {
@ -121,6 +124,9 @@ class World(
fun unloadChunk(chunkPosition: Vec2i) {
chunks.remove(chunkPosition) ?: return
for (neighbour in getChunkNeighbours(chunkPosition)) {
neighbour?.neighboursLoaded = false
}
connection.fireEvent(ChunkUnloadEvent(connection, EventInitiators.UNKNOWN, chunkPosition))
}
@ -235,6 +241,56 @@ class World(
return base * 0.8 + 0.2
}
/**
* @return All 8 neighbour chunks
*/
fun getChunkNeighbours(neighbourPositions: Array<Vec2i>): Array<Chunk?> {
val chunks: Array<Chunk?> = arrayOfNulls(neighbourPositions.size)
for ((index, neighbourPosition) in neighbourPositions.withIndex()) {
chunks[index] = this[neighbourPosition] ?: continue
}
return chunks
}
fun getChunkNeighbours(chunkPosition: Vec2i): Array<Chunk?> {
return getChunkNeighbours(ChunkUtil.getChunkNeighbourPositions(chunkPosition))
}
fun onChunkUpdate(chunkPosition: Vec2i, chunk: Chunk = get(chunkPosition)!!) {
val neighbourPositions = ChunkUtil.getChunkNeighbourPositions(chunkPosition)
val neighbours = getChunkNeighbours(neighbourPositions)
if (chunk.neighboursLoaded) {
return
}
if (neighbours.fullyLoaded) {
chunk.neighboursLoaded = true
chunk.buildBiomeCache()
}
for ((index, neighbourPosition) in neighbourPositions.withIndex()) {
if (neighbourPosition == chunkPosition) {
continue
}
val neighbourChunk = neighbours[index] ?: continue
if (neighbourChunk.neighboursLoaded) {
continue
}
var biomeSourceLoaded = true
for (neighbourNeighbourChunk in getChunkNeighbours(neighbourPosition)) {
if (neighbourNeighbourChunk?.biomeSource == null) {
biomeSourceLoaded = false
break
}
}
if (biomeSourceLoaded) {
neighbourChunk.neighboursLoaded = true
neighbourChunk.buildBiomeCache()
}
}
}
companion object {
const val MAX_SIZE = 29999999
const val MAX_SIZEf = MAX_SIZE.toFloat()

View File

@ -14,16 +14,14 @@
package de.bixilon.minosoft.data.world.biome.accessor
import de.bixilon.minosoft.data.registries.biomes.Biome
import de.bixilon.minosoft.data.world.World
import de.bixilon.minosoft.gui.rendering.util.VecUtil.chunkPosition
import de.bixilon.minosoft.gui.rendering.util.VecUtil.inChunkPosition
import glm_.vec3.Vec3i
class BlockBiomeAccessor(private val world: World) : BiomeAccessor {
class WorldBiomeAccessor(val world: World) : BiomeAccessor {
override fun getBiome(blockPosition: Vec3i): Biome? {
val biomePosition = blockPosition.inChunkPosition
return world[blockPosition.chunkPosition]?.biomeSource?.getBiome(biomePosition)
return world[blockPosition.chunkPosition]?.getBiome(blockPosition.inChunkPosition)
}
}

View File

@ -18,6 +18,7 @@ import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition
import glm_.vec3.Vec3i
class XZBiomeArray(private val biomes: Array<Biome>) : BiomeSource {
init {
check(biomes.size == ProtocolDefinition.SECTION_WIDTH_X * ProtocolDefinition.SECTION_WIDTH_Z) { "Biome array size does not match the xz block count!" }
}

View File

@ -20,7 +20,6 @@ import de.bixilon.minosoft.data.registries.ResourceLocation
import de.bixilon.minosoft.data.registries.dimension.Dimension
import de.bixilon.minosoft.data.registries.dimension.DimensionProperties
import de.bixilon.minosoft.data.registries.other.game.event.handlers.gamemode.GamemodeChangeEvent
import de.bixilon.minosoft.data.world.biome.accessor.BlockBiomeAccessor
import de.bixilon.minosoft.data.world.biome.accessor.NoiseBiomeAccessor
import de.bixilon.minosoft.modding.channels.DefaultPluginChannels
import de.bixilon.minosoft.modding.event.EventInitiators
@ -162,10 +161,8 @@ class JoinGameS2CP(buffer: PlayInByteBuffer) : PlayS2CPacket() {
connection.world.entities.add(entityId, null, playerEntity)
connection.world.hashedSeed = hashedSeed
connection.world.biomeAccessor = if (connection.version.versionId < ProtocolVersions.V_19W36A) {
BlockBiomeAccessor(connection.world)
} else {
NoiseBiomeAccessor(connection.world)
if (connection.version.versionId >= ProtocolVersions.V_19W36A) {
connection.world.cacheBiomeAccessor = NoiseBiomeAccessor(connection.world)
}
TimeWorker.addTask(TimeWorkerTask(150, true) { // ToDo: Temp workaround
connection.sendPacket(ClientSettingsC2SP("en_us"))

View File

@ -16,7 +16,9 @@ package de.bixilon.minosoft.util.chunk
import de.bixilon.minosoft.data.registries.biomes.Biome
import de.bixilon.minosoft.data.registries.blocks.BlockState
import de.bixilon.minosoft.data.registries.dimension.DimensionProperties
import de.bixilon.minosoft.data.world.Chunk
import de.bixilon.minosoft.data.world.ChunkData
import de.bixilon.minosoft.data.world.ChunkSection
import de.bixilon.minosoft.data.world.biome.source.XZBiomeArray
import de.bixilon.minosoft.data.world.container.RegistrySectionDataProvider
import de.bixilon.minosoft.data.world.palette.Palette.Companion.choosePalette
@ -24,6 +26,7 @@ import de.bixilon.minosoft.protocol.protocol.PlayInByteBuffer
import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition
import de.bixilon.minosoft.protocol.protocol.ProtocolVersions.*
import de.bixilon.minosoft.util.KUtil.unsafeCast
import glm_.vec2.Vec2i
import java.util.*
@ -221,4 +224,44 @@ object ChunkUtil {
}
return XZBiomeArray(biomes.toTypedArray())
}
val Array<Chunk?>.fullyLoaded: Boolean
get() {
for (neighbour in this) {
if (neighbour?.isFullyLoaded != true) {
return false
}
}
return true
}
fun getChunkNeighbourPositions(chunkPosition: Vec2i): Array<Vec2i> {
return arrayOf(
chunkPosition + Vec2i(-1, -1),
chunkPosition + Vec2i(-1, 0),
chunkPosition + Vec2i(-1, 1),
chunkPosition + Vec2i(0, -1),
chunkPosition + Vec2i(0, 1),
chunkPosition + Vec2i(1, -1),
chunkPosition + Vec2i(1, 0),
chunkPosition + Vec2i(1, 1),
)
}
/**
* @param neighbourChunks: **Fully loaded** neighbour chunks
*/
private fun getSectionNeighbours(neighbourChunks: Array<Chunk>, chunk: Chunk, sectionHeight: Int): Array<ChunkSection?> {
return arrayOf(
chunk[sectionHeight - 1],
chunk[sectionHeight + 1],
neighbourChunks[3][sectionHeight],
neighbourChunks[4][sectionHeight],
neighbourChunks[1][sectionHeight],
neighbourChunks[6][sectionHeight],
)
}
}