calculate biome cache on demand, refactor chunk section constructor

This makes things way faster and uses less memory. Biome is just needed for tinted blocks (and maybe sky color), most blocks don't require it. Calculating the biome is expensive (at least when joining).
This commit is contained in:
Moritz Zwerger 2023-12-07 23:49:12 +01:00
parent c4cd6c4856
commit 7f32520f6e
No known key found for this signature in database
GPG Key ID: 5CAD791931B09AC4
15 changed files with 127 additions and 124 deletions

View File

@ -90,9 +90,11 @@ class WorldBiomesTest {
val chunk = world.chunks[0, 0]!! val chunk = world.chunks[0, 0]!!
chunk.getOrPut(0) chunk.getOrPut(0)
assertEquals(source.counter, 4096) assertEquals(source.counter, 0) // biomes ore on demand
assertEquals(world.biomes[1, 2, 3], b1) assertEquals(world.biomes[1, 2, 3], b1)
assertEquals(source.counter, 4096) assertEquals(source.counter, 1)
assertEquals(world.biomes[1, 2, 3], b1)
assertEquals(source.counter, 1) // don't query again
assertTrue(chunk.cacheBiomes) assertTrue(chunk.cacheBiomes)
assertEquals(chunk[0]!!.biomes[1, 2, 3], b1) assertEquals(chunk[0]!!.biomes[1, 2, 3], b1)
} }

View File

@ -15,12 +15,14 @@ package de.bixilon.minosoft.data.world.chunk.manager
import de.bixilon.kotlinglm.vec2.Vec2i import de.bixilon.kotlinglm.vec2.Vec2i
import de.bixilon.minosoft.data.registries.biomes.Biome import de.bixilon.minosoft.data.registries.biomes.Biome
import de.bixilon.minosoft.data.registries.blocks.state.BlockState
import de.bixilon.minosoft.data.registries.blocks.types.stone.StoneTest0 import de.bixilon.minosoft.data.registries.blocks.types.stone.StoneTest0
import de.bixilon.minosoft.data.registries.identified.Namespaces.minosoft import de.bixilon.minosoft.data.registries.identified.Namespaces.minosoft
import de.bixilon.minosoft.data.world.biome.accessor.noise.VoronoiBiomeAccessor import de.bixilon.minosoft.data.world.biome.accessor.noise.VoronoiBiomeAccessor
import de.bixilon.minosoft.data.world.biome.source.BiomeSource import de.bixilon.minosoft.data.world.biome.source.BiomeSource
import de.bixilon.minosoft.data.world.biome.source.DummyBiomeSource import de.bixilon.minosoft.data.world.biome.source.DummyBiomeSource
import de.bixilon.minosoft.data.world.biome.source.SpatialBiomeArray import de.bixilon.minosoft.data.world.biome.source.SpatialBiomeArray
import de.bixilon.minosoft.data.world.chunk.ChunkSection.Companion.getIndex
import de.bixilon.minosoft.data.world.chunk.chunk.Chunk import de.bixilon.minosoft.data.world.chunk.chunk.Chunk
import de.bixilon.minosoft.data.world.chunk.chunk.ChunkPrototype import de.bixilon.minosoft.data.world.chunk.chunk.ChunkPrototype
import de.bixilon.minosoft.data.world.chunk.neighbours.ChunkNeighbours import de.bixilon.minosoft.data.world.chunk.neighbours.ChunkNeighbours
@ -31,13 +33,13 @@ import de.bixilon.minosoft.data.world.chunk.update.chunk.ChunkCreateUpdate
import de.bixilon.minosoft.data.world.chunk.update.chunk.ChunkUnloadUpdate import de.bixilon.minosoft.data.world.chunk.update.chunk.ChunkUnloadUpdate
import de.bixilon.minosoft.data.world.chunk.update.chunk.NeighbourChangeUpdate import de.bixilon.minosoft.data.world.chunk.update.chunk.NeighbourChangeUpdate
import de.bixilon.minosoft.data.world.chunk.update.chunk.prototype.PrototypeChangeUpdate import de.bixilon.minosoft.data.world.chunk.update.chunk.prototype.PrototypeChangeUpdate
import de.bixilon.minosoft.data.world.container.block.BlockSectionDataProvider
import de.bixilon.minosoft.data.world.positions.BlockPosition import de.bixilon.minosoft.data.world.positions.BlockPosition
import de.bixilon.minosoft.data.world.positions.ChunkPosition import de.bixilon.minosoft.data.world.positions.ChunkPosition
import de.bixilon.minosoft.data.world.positions.InChunkPosition import de.bixilon.minosoft.data.world.positions.InChunkPosition
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.modding.event.listener.CallbackEventListener.Companion.listen import de.bixilon.minosoft.modding.event.listener.CallbackEventListener.Companion.listen
import de.bixilon.minosoft.protocol.network.connection.play.ConnectionTestUtil.createConnection import de.bixilon.minosoft.protocol.network.connection.play.ConnectionTestUtil.createConnection
import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition
import org.testng.Assert.* import org.testng.Assert.*
import org.testng.annotations.Test import org.testng.annotations.Test
@ -191,9 +193,9 @@ class ChunkManagerTest {
manager[ChunkPosition(0, 1)]!![3, 16, 3] = StoneTest0.state manager[ChunkPosition(0, 1)]!![3, 16, 3] = StoneTest0.state
manager[ChunkPosition(0, 0)] = ChunkPrototype(blocks = arrayOf( manager[ChunkPosition(0, 0)] = ChunkPrototype(blocks = arrayOf(
BlockSectionDataProvider(null).apply { this[3, 3, 3] = StoneTest0.state }, arrayOfNulls<BlockState>(ProtocolDefinition.BLOCKS_PER_SECTION).apply { this[getIndex(3, 3, 3)] = StoneTest0.state },
BlockSectionDataProvider(null).apply { this[3, 3, 3] = StoneTest0.state }, arrayOfNulls<BlockState>(ProtocolDefinition.BLOCKS_PER_SECTION).apply { this[getIndex(3, 3, 3)] = StoneTest0.state },
BlockSectionDataProvider(null).apply { this[3, 3, 3] = StoneTest0.state }, arrayOfNulls<BlockState>(ProtocolDefinition.BLOCKS_PER_SECTION).apply { this[getIndex(3, 3, 3)] = StoneTest0.state },
null, null, null, null, null, null,
), ),
blockEntities = emptyMap(), blockEntities = emptyMap(),
@ -399,7 +401,7 @@ class ChunkManagerTest {
fired++ fired++
} }
manager.set(ChunkPosition(1, 1), ChunkPrototype(blocks = Array(16) { if (it == 0) BlockSectionDataProvider(null) else null }), false) manager.set(ChunkPosition(1, 1), ChunkPrototype(blocks = Array(16) { if (it == 0) arrayOfNulls(ProtocolDefinition.BLOCKS_PER_SECTION) else null }), false)
assertNotNull(manager[ChunkPosition(1, 1)]) assertNotNull(manager[ChunkPosition(1, 1)])
@ -443,8 +445,10 @@ class ChunkManagerTest {
val matrix = manager.createMatrix(source) val matrix = manager.createMatrix(source)
val chunk = matrix[1][1] val chunk = matrix[1][1]
assertEquals(chunk.getOrPut(0)!!.biomes.count, 4096) val section = chunk.getOrPut(0)!!.biomes
assertEquals(chunk.getOrPut(0)!!.biomes[0], biome) assertEquals(section[3, 3, 3], biome)
assertEquals(section[3, 3, 3], biome)
assertEquals(section[0], biome)
assertEquals(manager.world.biomes.getBiome(BlockPosition(5, 5, 5)), biome) assertEquals(manager.world.biomes.getBiome(BlockPosition(5, 5, 5)), biome)
assertEquals(chunk.getBiome(BlockPosition(5, 5, 5)), biome) assertEquals(chunk.getBiome(BlockPosition(5, 5, 5)), biome)

View File

@ -1,7 +1,22 @@
/*
* Minosoft
* Copyright (C) 2020-2023 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.data.world.container.block package de.bixilon.minosoft.data.world.container.block
import de.bixilon.kotlinglm.vec3.Vec3i import de.bixilon.kotlinglm.vec3.Vec3i
import de.bixilon.minosoft.data.registries.blocks.types.stone.StoneTest0 import de.bixilon.minosoft.data.registries.blocks.types.stone.StoneTest0
import de.bixilon.minosoft.data.world.chunk.ChunkSection
import de.bixilon.minosoft.test.ITUtil.allocate
import org.testng.Assert.* import org.testng.Assert.*
import org.testng.annotations.Test import org.testng.annotations.Test
@ -9,7 +24,8 @@ import org.testng.annotations.Test
class BlockSectionDataProviderTest { class BlockSectionDataProviderTest {
private fun create(): BlockSectionDataProvider { private fun create(): BlockSectionDataProvider {
return BlockSectionDataProvider(null) val section = ChunkSection::class.java.allocate()
return BlockSectionDataProvider(null, section)
} }
fun `initial empty`() { fun `initial empty`() {

View File

@ -19,8 +19,10 @@ import de.bixilon.kutil.observer.DataObserver
import de.bixilon.kutil.reflection.ReflectionUtil.forceSet import de.bixilon.kutil.reflection.ReflectionUtil.forceSet
import de.bixilon.mbf.MBFBinaryReader import de.bixilon.mbf.MBFBinaryReader
import de.bixilon.minosoft.data.registries.blocks.MinecraftBlocks import de.bixilon.minosoft.data.registries.blocks.MinecraftBlocks
import de.bixilon.minosoft.data.registries.blocks.state.BlockState
import de.bixilon.minosoft.data.registries.blocks.types.building.stone.StoneBlock import de.bixilon.minosoft.data.registries.blocks.types.building.stone.StoneBlock
import de.bixilon.minosoft.data.registries.dimension.DimensionProperties import de.bixilon.minosoft.data.registries.dimension.DimensionProperties
import de.bixilon.minosoft.data.world.chunk.ChunkSection.Companion.getIndex
import de.bixilon.minosoft.protocol.network.connection.play.ConnectionTestUtil.createConnection import de.bixilon.minosoft.protocol.network.connection.play.ConnectionTestUtil.createConnection
import de.bixilon.minosoft.protocol.network.connection.play.PlayConnection import de.bixilon.minosoft.protocol.network.connection.play.PlayConnection
import de.bixilon.minosoft.protocol.packets.s2c.play.block.chunk.ChunkS2CP import de.bixilon.minosoft.protocol.packets.s2c.play.block.chunk.ChunkS2CP
@ -37,6 +39,10 @@ class ChunkS2CPTest {
registries.update(version, mbf.data.unsafeCast()) registries.update(version, mbf.data.unsafeCast())
} }
private operator fun Array<BlockState?>.get(x: Int, y: Int, z: Int): BlockState? {
return this[getIndex(x, y, z)]
}
private fun read(name: String, version: String, connection: PlayConnection = createConnection(version = version), dimension: DimensionProperties): ChunkS2CP { private fun read(name: String, version: String, connection: PlayConnection = createConnection(version = version), dimension: DimensionProperties): ChunkS2CP {
val data = ChunkS2CPTest::class.java.getResourceAsStream("/packets/chunk/$name.bin")!!.readAllBytes() val data = ChunkS2CPTest::class.java.getResourceAsStream("/packets/chunk/$name.bin")!!.readAllBytes()
connection.world::dimension.forceSet(DataObserver(dimension)) connection.world::dimension.forceSet(DataObserver(dimension))

View File

@ -14,19 +14,14 @@ package de.bixilon.minosoft.data.world.chunk
import de.bixilon.kotlinglm.vec2.Vec2i import de.bixilon.kotlinglm.vec2.Vec2i
import de.bixilon.kotlinglm.vec3.Vec3i import de.bixilon.kotlinglm.vec3.Vec3i
import de.bixilon.kutil.array.ArrayUtil.cast
import de.bixilon.kutil.cast.CastUtil.unsafeNull
import de.bixilon.kutil.reflection.ReflectionUtil.jvmField
import de.bixilon.minosoft.data.entities.block.BlockEntity import de.bixilon.minosoft.data.entities.block.BlockEntity
import de.bixilon.minosoft.data.registries.biomes.Biome
import de.bixilon.minosoft.data.world.biome.accessor.noise.NoiseBiomeAccessor
import de.bixilon.minosoft.data.world.chunk.chunk.Chunk import de.bixilon.minosoft.data.world.chunk.chunk.Chunk
import de.bixilon.minosoft.data.world.chunk.light.SectionLight import de.bixilon.minosoft.data.world.chunk.light.SectionLight
import de.bixilon.minosoft.data.world.container.SectionDataProvider import de.bixilon.minosoft.data.world.container.SectionDataProvider
import de.bixilon.minosoft.data.world.container.biome.BiomeSectionDataProvider
import de.bixilon.minosoft.data.world.container.block.BlockSectionDataProvider import de.bixilon.minosoft.data.world.container.block.BlockSectionDataProvider
import de.bixilon.minosoft.gui.rendering.util.VecUtil.of import de.bixilon.minosoft.gui.rendering.util.VecUtil.of
import de.bixilon.minosoft.protocol.network.connection.play.PlayConnection import de.bixilon.minosoft.protocol.network.connection.play.PlayConnection
import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition
import java.util.* import java.util.*
/** /**
@ -34,12 +29,12 @@ import java.util.*
*/ */
class ChunkSection( class ChunkSection(
val sectionHeight: Int, val sectionHeight: Int,
chunk: Chunk? = null, val chunk: Chunk,
var blocks: BlockSectionDataProvider = BlockSectionDataProvider(chunk?.lock),
var biomes: SectionDataProvider<Biome> = SectionDataProvider(chunk?.lock, checkSize = false),
var blockEntities: SectionDataProvider<BlockEntity?> = SectionDataProvider(chunk?.lock, checkSize = true),
) { ) {
val chunk: Chunk = chunk ?: unsafeNull() var blocks = BlockSectionDataProvider(chunk.lock, this)
var biomes = BiomeSectionDataProvider(chunk.lock, this)
var blockEntities: SectionDataProvider<BlockEntity?> = SectionDataProvider(chunk.lock, checkSize = true)
var light = SectionLight(this) var light = SectionLight(this)
var neighbours: Array<ChunkSection?>? = null var neighbours: Array<ChunkSection?>? = null
@ -66,14 +61,6 @@ class ChunkSection(
} }
} }
fun buildBiomeCache(noise: NoiseBiomeAccessor) {
val biomes: Array<Biome?> = arrayOfNulls(ProtocolDefinition.BLOCKS_PER_SECTION)
for (index in 0 until ProtocolDefinition.BLOCKS_PER_SECTION) {
biomes[index] = noise.get(index and 0x0F, (index shr 8) and 0x0F, (index shr 4) and 0x0F, chunk)
}
this.biomes.setData(biomes.cast())
}
fun clear() { fun clear() {
blocks.clear() blocks.clear()
@ -81,16 +68,7 @@ class ChunkSection(
blockEntities.clear() blockEntities.clear()
} }
fun updateChunk(chunk: Chunk) {
CHUNK[this] = chunk
blocks.lock = chunk.lock
// biomes?
blockEntities.lock = chunk.lock
}
companion object { companion object {
private val CHUNK = ChunkSection::chunk.jvmField
inline val Vec3i.index: Int inline val Vec3i.index: Int
get() = getIndex(x, y, z) get() = getIndex(x, y, z)

View File

@ -28,7 +28,6 @@ import de.bixilon.minosoft.data.world.chunk.light.ChunkLight
import de.bixilon.minosoft.data.world.chunk.neighbours.ChunkNeighbours import de.bixilon.minosoft.data.world.chunk.neighbours.ChunkNeighbours
import de.bixilon.minosoft.data.world.chunk.update.block.ChunkLocalBlockUpdate import de.bixilon.minosoft.data.world.chunk.update.block.ChunkLocalBlockUpdate
import de.bixilon.minosoft.data.world.chunk.update.block.SingleBlockUpdate import de.bixilon.minosoft.data.world.chunk.update.block.SingleBlockUpdate
import de.bixilon.minosoft.data.world.container.block.BlockSectionDataProvider.Companion.unsafeSetSection
import de.bixilon.minosoft.data.world.positions.ChunkPosition import de.bixilon.minosoft.data.world.positions.ChunkPosition
import de.bixilon.minosoft.data.world.positions.InChunkPosition import de.bixilon.minosoft.data.world.positions.InChunkPosition
import de.bixilon.minosoft.data.world.positions.SectionHeight import de.bixilon.minosoft.data.world.positions.SectionHeight
@ -44,7 +43,6 @@ import java.util.*
class Chunk( class Chunk(
val connection: PlayConnection, val connection: PlayConnection,
val chunkPosition: ChunkPosition, val chunkPosition: ChunkPosition,
var sections: Array<ChunkSection?>,
var biomeSource: BiomeSource, var biomeSource: BiomeSource,
) : Iterable<ChunkSection?>, BiomeAccessor { ) : Iterable<ChunkSection?>, BiomeAccessor {
val lock = ThreadLock() val lock = ThreadLock()
@ -53,6 +51,7 @@ class Chunk(
val minSection = world.dimension.minSection val minSection = world.dimension.minSection
val maxSection = world.dimension.maxSection val maxSection = world.dimension.maxSection
val cacheBiomes = world.biomes.noise != null val cacheBiomes = world.biomes.noise != null
var sections: Array<ChunkSection?> = arrayOfNulls(world.dimension.sections)
val neighbours = ChunkNeighbours(this) val neighbours = ChunkNeighbours(this)
@ -184,7 +183,6 @@ class Chunk(
var section = sections[index] // get another time, it might have changed already var section = sections[index] // get another time, it might have changed already
if (section == null) { if (section == null) {
section = ChunkSection(sectionHeight, chunk = this) section = ChunkSection(sectionHeight, chunk = this)
section.blocks.unsafeSetSection(section)
val neighbours = this.neighbours.get() val neighbours = this.neighbours.get()
if (neighbours != null) { if (neighbours != null) {
this.neighbours.completeSection(neighbours, section, sectionHeight, world.biomes.noise) this.neighbours.completeSection(neighbours, section, sectionHeight, world.biomes.noise)

View File

@ -15,20 +15,18 @@ package de.bixilon.minosoft.data.world.chunk.chunk
import de.bixilon.kotlinglm.vec3.Vec3i import de.bixilon.kotlinglm.vec3.Vec3i
import de.bixilon.kutil.json.JsonObject import de.bixilon.kutil.json.JsonObject
import de.bixilon.kutil.reflection.ReflectionUtil.forceSet
import de.bixilon.kutil.reflection.ReflectionUtil.jvmField
import de.bixilon.minosoft.config.StaticConfiguration import de.bixilon.minosoft.config.StaticConfiguration
import de.bixilon.minosoft.data.registries.blocks.state.BlockState
import de.bixilon.minosoft.data.registries.blocks.types.entity.BlockWithEntity import de.bixilon.minosoft.data.registries.blocks.types.entity.BlockWithEntity
import de.bixilon.minosoft.data.world.biome.source.BiomeSource import de.bixilon.minosoft.data.world.biome.source.BiomeSource
import de.bixilon.minosoft.data.world.chunk.ChunkSection import de.bixilon.minosoft.data.world.chunk.ChunkSection
import de.bixilon.minosoft.data.world.container.block.BlockSectionDataProvider
import de.bixilon.minosoft.data.world.positions.ChunkPosition import de.bixilon.minosoft.data.world.positions.ChunkPosition
import de.bixilon.minosoft.protocol.network.connection.play.PlayConnection import de.bixilon.minosoft.protocol.network.connection.play.PlayConnection
import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition
import it.unimi.dsi.fastutil.ints.IntOpenHashSet import it.unimi.dsi.fastutil.ints.IntOpenHashSet
class ChunkPrototype( class ChunkPrototype(
var blocks: Array<BlockSectionDataProvider?>? = null, var blocks: Array<Array<BlockState?>?>? = null,
var blockEntities: Map<Vec3i, JsonObject>? = null, var blockEntities: Map<Vec3i, JsonObject>? = null,
var biomeSource: BiomeSource? = null, var biomeSource: BiomeSource? = null,
var light: Array<ByteArray?>? = null, var light: Array<ByteArray?>? = null,
@ -53,28 +51,23 @@ class ChunkPrototype(
val dimension = connection.world.dimension val dimension = connection.world.dimension
val sections: Array<ChunkSection?> = arrayOfNulls(dimension.sections)
val light = this.light val light = this.light
for ((index, provider) in blocks.withIndex()) { val chunk = Chunk(connection, position, biomeSource)
if (provider == null) continue
val section = ChunkSection(index + dimension.minSection, null, provider) for ((index, blockData) in blocks.withIndex()) {
SECTION[provider] = section if (blockData == null) continue
val section = ChunkSection(index + dimension.minSection, chunk)
section.blocks.setData(blockData)
if (!StaticConfiguration.IGNORE_SERVER_LIGHT) { if (!StaticConfiguration.IGNORE_SERVER_LIGHT) {
light?.get(index)?.let { section.light.light = it } light?.get(index)?.let { section.light.light = it }
} }
sections[index] = section chunk.sections[index] = section
} }
this.blockEntities.update(dimension.minSection, sections, null, connection) this.blockEntities.update(dimension.minSection, chunk, null, connection)
val chunk = Chunk(connection, position, sections, biomeSource)
for (section in sections) {
if (section == null) continue
section.updateChunk(chunk)
}
if (!StaticConfiguration.IGNORE_SERVER_LIGHT) { if (!StaticConfiguration.IGNORE_SERVER_LIGHT) {
this.topLight?.let { chunk.light.top.update(it) } this.topLight?.let { chunk.light.top.update(it) }
this.bottomLight?.let { chunk.light.bottom.update(it) } this.bottomLight?.let { chunk.light.bottom.update(it) }
@ -84,7 +77,7 @@ class ChunkPrototype(
return chunk return chunk
} }
private fun Array<BlockSectionDataProvider?>.update(chunk: Chunk, replace: Boolean, affected: IntOpenHashSet) { private fun Array<Array<BlockState?>?>.update(chunk: Chunk, replace: Boolean, affected: IntOpenHashSet) {
for ((index, provider) in this.withIndex()) { for ((index, provider) in this.withIndex()) {
var section = chunk.sections[index] var section = chunk.sections[index]
val sectionHeight = index - chunk.minSection val sectionHeight = index - chunk.minSection
@ -101,16 +94,15 @@ class ChunkPrototype(
} else { } else {
section.blockEntities.clear() section.blockEntities.clear()
} }
SECTION.forceSet(provider, section)
section.blocks = provider section.blocks.setData(provider)
affected += sectionHeight affected += sectionHeight
} }
} }
private fun Map<Vec3i, JsonObject>?.update(minSection: Int, sections: Array<ChunkSection?>, affected: IntOpenHashSet?, connection: PlayConnection) { private fun Map<Vec3i, JsonObject>?.update(minSection: Int, chunk: Chunk, affected: IntOpenHashSet?, connection: PlayConnection) {
val position = Vec3i() val position = Vec3i()
for ((index, section) in sections.withIndex()) { for ((index, section) in chunk.sections.withIndex()) {
if (section == null || section.blocks.isEmpty) continue if (section == null || section.blocks.isEmpty) continue
val blocks = section.blocks val blocks = section.blocks
val sectionHeight = (index + minSection) val sectionHeight = (index + minSection)
@ -142,7 +134,7 @@ class ChunkPrototype(
fun updateChunk(chunk: Chunk, replace: Boolean): IntOpenHashSet? { fun updateChunk(chunk: Chunk, replace: Boolean): IntOpenHashSet? {
val affected = IntOpenHashSet() val affected = IntOpenHashSet()
this.blocks?.update(chunk, replace, affected) this.blocks?.update(chunk, replace, affected)
this.blockEntities?.update(chunk.minSection, chunk.sections, affected, chunk.connection) this.blockEntities?.update(chunk.minSection, chunk, affected, chunk.connection)
this.biomeSource?.let { chunk.biomeSource = it } // TODO: invalidate cache this.biomeSource?.let { chunk.biomeSource = it } // TODO: invalidate cache
@ -155,8 +147,4 @@ class ChunkPrototype(
if (affected.isEmpty()) return null if (affected.isEmpty()) return null
return affected return affected
} }
private companion object {
private val SECTION = BlockSectionDataProvider::section.jvmField
}
} }

View File

@ -161,7 +161,7 @@ class ChunkManager(val world: World, chunkCapacity: Int = 0, prototypeCapacity:
fun create(position: ChunkPosition, biome: BiomeSource = DummyBiomeSource(null)): Chunk { fun create(position: ChunkPosition, biome: BiomeSource = DummyBiomeSource(null)): Chunk {
if (!world.isValidPosition(position)) throw IllegalArgumentException("Chunk position $position is not valid!") if (!world.isValidPosition(position)) throw IllegalArgumentException("Chunk position $position is not valid!")
chunks.lock.lock() chunks.lock.lock()
val chunk = chunks.unsafe.getOrPut(position) { Chunk(world.connection, position, arrayOfNulls(world.dimension.sections), biome) } val chunk = chunks.unsafe.getOrPut(position) { Chunk(world.connection, position, biome) }
val updates = onChunkCreate(chunk) val updates = onChunkCreate(chunk)
chunks.lock.unlock() chunks.lock.unlock()

View File

@ -72,9 +72,6 @@ class ChunkNeighbours(val chunk: Chunk) : Iterable<Chunk?> {
fun completeSection(neighbours: Array<Chunk>, section: ChunkSection, sectionHeight: SectionHeight, noise: NoiseBiomeAccessor?) { fun completeSection(neighbours: Array<Chunk>, section: ChunkSection, sectionHeight: SectionHeight, noise: NoiseBiomeAccessor?) {
section.neighbours = ChunkUtil.getDirectNeighbours(neighbours, chunk, sectionHeight) section.neighbours = ChunkUtil.getDirectNeighbours(neighbours, chunk, sectionHeight)
if (noise != null) {
section.buildBiomeCache(noise)
}
} }
private fun complete(neighbours: Array<Chunk>) { private fun complete(neighbours: Array<Chunk>) {

View File

@ -23,36 +23,25 @@ import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition
open class SectionDataProvider<T>( open class SectionDataProvider<T>(
var lock: Lock?, var lock: Lock?,
data: Array<T>? = null,
val checkSize: Boolean = false, val checkSize: Boolean = false,
calculateInitial: Boolean = true,
) : Iterable<T> { ) : Iterable<T> {
protected var data: Array<Any?>? = data?.unsafeCast() protected var data: Array<Any?>? = null
private set private set
var count: Int = 0 var count: Int = 0
private set private set
val isEmpty: Boolean val isEmpty: Boolean
get() = count == 0 get() = count == 0
lateinit var minPosition: Vec3i var minPosition = Vec3i(ProtocolDefinition.CHUNK_SECTION_SIZE)
private set private set
lateinit var maxPosition: Vec3i var maxPosition = Vec3i.EMPTY
private set private set
init {
if (data != null && calculateInitial) {
recalculate()
} else {
minPosition = Vec3i(ProtocolDefinition.CHUNK_SECTION_SIZE)
maxPosition = Vec3i.EMPTY
}
}
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
operator fun get(index: Int): T { open operator fun get(index: Int): T {
return data?.get(index) as T return data?.get(index) as T
} }
operator fun get(x: Int, y: Int, z: Int): T { open operator fun get(x: Int, y: Int, z: Int): T {
return get(ChunkSection.getIndex(x, y, z)) return get(ChunkSection.getIndex(x, y, z))
} }
@ -193,13 +182,6 @@ open class SectionDataProvider<T>(
lock?.unlock() lock?.unlock()
} }
fun copy(): SectionDataProvider<T> {
lock?.acquire()
val clone = SectionDataProvider<T>(null, data?.clone()?.unsafeCast())
lock?.release()
return clone
}
fun clear() { fun clear() {
lock?.lock() lock?.lock()

View File

@ -0,0 +1,41 @@
/*
* Minosoft
* Copyright (C) 2020-2023 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.data.world.container.biome
import de.bixilon.kutil.concurrent.lock.Lock
import de.bixilon.minosoft.data.registries.biomes.Biome
import de.bixilon.minosoft.data.world.chunk.ChunkSection
import de.bixilon.minosoft.data.world.container.SectionDataProvider
class BiomeSectionDataProvider(
lock: Lock? = null,
val section: ChunkSection,
) : SectionDataProvider<Biome?>(lock, false) {
override fun get(index: Int): Biome? {
var biome = super.get(index)
if (biome != null) return biome
biome = section.chunk.world.biomes.noise?.get(index and 0x0F, (index shr 8) and 0x0F, (index shr 4) and 0x0F, section.chunk)
unsafeSet(index, biome)
return biome
}
override fun get(x: Int, y: Int, z: Int): Biome? {
var biome = super.get(x, y, z)
if (biome != null) return biome
biome = section.chunk.world.biomes.noise?.get(x, y, z, section.chunk)
unsafeSet(x, y, z, biome)
return biome
}
}

View File

@ -13,9 +13,7 @@
package de.bixilon.minosoft.data.world.container.block package de.bixilon.minosoft.data.world.container.block
import de.bixilon.kutil.cast.CastUtil.unsafeNull
import de.bixilon.kutil.concurrent.lock.Lock import de.bixilon.kutil.concurrent.lock.Lock
import de.bixilon.kutil.reflection.ReflectionUtil.jvmField
import de.bixilon.minosoft.data.registries.blocks.state.BlockState import de.bixilon.minosoft.data.registries.blocks.state.BlockState
import de.bixilon.minosoft.data.registries.blocks.types.fluid.FluidHolder import de.bixilon.minosoft.data.registries.blocks.types.fluid.FluidHolder
import de.bixilon.minosoft.data.registries.fluid.fluids.WaterFluid.Companion.isWaterlogged import de.bixilon.minosoft.data.registries.fluid.fluids.WaterFluid.Companion.isWaterlogged
@ -25,9 +23,8 @@ import de.bixilon.minosoft.data.world.container.SectionDataProvider
class BlockSectionDataProvider( class BlockSectionDataProvider(
lock: Lock? = null, lock: Lock? = null,
data: Array<BlockState?>? = null, val section: ChunkSection,
) : SectionDataProvider<BlockState?>(lock, data, true, false) { ) : SectionDataProvider<BlockState?>(lock, true) {
val section: ChunkSection = unsafeNull()
val occlusion = SectionOcclusion(this) val occlusion = SectionOcclusion(this)
var fluidCount = 0 var fluidCount = 0
private set private set
@ -89,13 +86,4 @@ class BlockSectionDataProvider(
} }
return this.isWaterlogged() return this.isWaterlogged()
} }
companion object {
private val sections = BlockSectionDataProvider::section.jvmField
@Deprecated("properly integrate in constructor")
fun BlockSectionDataProvider.unsafeSetSection(section: ChunkSection) {
this@Companion.sections[this] = section
}
}
} }

View File

@ -131,7 +131,7 @@ class SolidSectionMesher(
} }
val tints = tints.getBlockTint(state, chunk.getBiome(position.x and 0x0F, y, position.z and 0x0F), x, y, z) val tints = tints.getBlockTint(state, chunk, x, position.y, z)
var rendered = false var rendered = false
model?.render(position, floatOffset, mesh, random, state, neighbourBlocks, light, tints, blockEntity.unsafeCast())?.let { if (it) rendered = true } model?.render(position, floatOffset, mesh, random, state, neighbourBlocks, light, tints, blockEntity.unsafeCast())?.let { if (it) rendered = true }

View File

@ -17,7 +17,6 @@ import de.bixilon.kotlinglm.vec3.Vec3i
import de.bixilon.kutil.primitive.IntUtil.toInt import de.bixilon.kutil.primitive.IntUtil.toInt
import de.bixilon.minosoft.assets.AssetsManager import de.bixilon.minosoft.assets.AssetsManager
import de.bixilon.minosoft.data.container.stack.ItemStack import de.bixilon.minosoft.data.container.stack.ItemStack
import de.bixilon.minosoft.data.registries.biomes.Biome
import de.bixilon.minosoft.data.registries.blocks.state.BlockState import de.bixilon.minosoft.data.registries.blocks.state.BlockState
import de.bixilon.minosoft.data.registries.fluid.Fluid import de.bixilon.minosoft.data.registries.fluid.Fluid
import de.bixilon.minosoft.data.text.formatting.color.RGBColor import de.bixilon.minosoft.data.text.formatting.color.RGBColor
@ -47,10 +46,11 @@ class TintManager(val connection: PlayConnection) {
DefaultTints.init(this) DefaultTints.init(this)
} }
fun getBlockTint(state: BlockState, biome: Biome?, x: Int, y: Int, z: Int): IntArray? { fun getBlockTint(state: BlockState, chunk: Chunk?, x: Int, y: Int, z: Int): IntArray? {
if (state.block !is TintedBlock) return null if (state.block !is TintedBlock) return null
val tintProvider = state.block.tintProvider ?: return null val tintProvider = state.block.tintProvider ?: return null
val tints = IntArray(if (tintProvider is MultiTintProvider) tintProvider.tints else 1) val tints = IntArray(if (tintProvider is MultiTintProvider) tintProvider.tints else 1)
val biome = chunk?.getBiome(x, y, z)
for (tintIndex in tints.indices) { for (tintIndex in tints.indices) {
tints[tintIndex] = tintProvider.getBlockColor(state, biome, x, y, z, tintIndex) tints[tintIndex] = tintProvider.getBlockColor(state, biome, x, y, z, tintIndex)
@ -70,10 +70,6 @@ class TintManager(val connection: PlayConnection) {
return getParticleTint(blockState, position.x, position.y, position.z) return getParticleTint(blockState, position.x, position.y, position.z)
} }
fun getBlockTint(blockState: BlockState, biome: Biome? = null, blockPosition: Vec3i): IntArray? {
return getBlockTint(blockState, biome, blockPosition.x, blockPosition.y, blockPosition.z)
}
fun getFluidTint(chunk: Chunk, fluid: Fluid, height: Float, x: Int, y: Int, z: Int): Int? { fun getFluidTint(chunk: Chunk, fluid: Fluid, height: Float, x: Int, y: Int, z: Int): Int? {
val biome = chunk.getBiome(x and 0x0F, y, z and 0x0F) val biome = chunk.getBiome(x and 0x0F, y, z and 0x0F)
return fluid.model?.tint?.getFluidTint(fluid, biome, height, x, y, z) return fluid.model?.tint?.getFluidTint(fluid, biome, height, x, y, z)

View File

@ -24,7 +24,6 @@ import de.bixilon.minosoft.data.world.chunk.ChunkSection
import de.bixilon.minosoft.data.world.chunk.chunk.Chunk import de.bixilon.minosoft.data.world.chunk.chunk.Chunk
import de.bixilon.minosoft.data.world.chunk.chunk.ChunkPrototype import de.bixilon.minosoft.data.world.chunk.chunk.ChunkPrototype
import de.bixilon.minosoft.data.world.chunk.neighbours.ChunkNeighbours import de.bixilon.minosoft.data.world.chunk.neighbours.ChunkNeighbours
import de.bixilon.minosoft.data.world.container.block.BlockSectionDataProvider
import de.bixilon.minosoft.data.world.container.palette.PalettedContainer import de.bixilon.minosoft.data.world.container.palette.PalettedContainer
import de.bixilon.minosoft.data.world.container.palette.PalettedContainerReader import de.bixilon.minosoft.data.world.container.palette.PalettedContainerReader
import de.bixilon.minosoft.data.world.container.palette.palettes.BiomePaletteFactory import de.bixilon.minosoft.data.world.container.palette.palettes.BiomePaletteFactory
@ -74,7 +73,7 @@ object ChunkUtil {
// parse data // parse data
var index = 0 var index = 0
val sections: Array<BlockSectionDataProvider?> = arrayOfNulls(dimension.sections) val sections: Array<Array<BlockState?>?> = arrayOfNulls(dimension.sections)
for ((sectionIndex, sectionHeight) in (dimension.minSection..dimension.maxSection).withIndex()) { for ((sectionIndex, sectionHeight) in (dimension.minSection..dimension.maxSection).withIndex()) {
if (!sectionBitMask[sectionIndex]) { if (!sectionBitMask[sectionIndex]) {
continue continue
@ -106,7 +105,7 @@ object ChunkUtil {
blocks[yzx] = buffer.connection.registries.blockState.getOrNull(blockId) ?: continue blocks[yzx] = buffer.connection.registries.blockState.getOrNull(blockId) ?: continue
} }
sections[sectionHeight] = BlockSectionDataProvider(data = blocks) sections[sectionHeight] = blocks
} }
chunkData.blocks = sections chunkData.blocks = sections
return chunkData return chunkData
@ -139,7 +138,7 @@ object ChunkUtil {
} }
var index = 0 var index = 0
val sections: Array<BlockSectionDataProvider?> = arrayOfNulls(dimension.sections) val sections: Array<Array<BlockState?>?> = arrayOfNulls(dimension.sections)
for ((sectionIndex, sectionHeight) in (dimension.minSection..dimension.maxSection).withIndex()) { // max sections per chunks in chunk column for ((sectionIndex, sectionHeight) in (dimension.minSection..dimension.maxSection).withIndex()) { // max sections per chunks in chunk column
if (!sectionBitMask[sectionIndex]) { if (!sectionBitMask[sectionIndex]) {
continue continue
@ -150,7 +149,7 @@ object ChunkUtil {
val block = buffer.connection.registries.blockState.getOrNull(blockId) ?: continue val block = buffer.connection.registries.blockState.getOrNull(blockId) ?: continue
blocks[yzx] = block blocks[yzx] = block
} }
sections[sectionHeight] = BlockSectionDataProvider(data = blocks) sections[sectionHeight] = blocks
} }
chunkData.blocks = sections chunkData.blocks = sections
return chunkData return chunkData
@ -158,7 +157,7 @@ object ChunkUtil {
fun readPaletteChunk(buffer: PlayInByteBuffer, dimension: DimensionProperties, sectionBitMask: BitSet?, complete: Boolean, skylight: Boolean = false): ChunkPrototype { fun readPaletteChunk(buffer: PlayInByteBuffer, dimension: DimensionProperties, sectionBitMask: BitSet?, complete: Boolean, skylight: Boolean = false): ChunkPrototype {
val chunkData = ChunkPrototype() val chunkData = ChunkPrototype()
val sectionBlocks: Array<BlockSectionDataProvider?> = arrayOfNulls(dimension.sections) val sectionBlocks: Array<Array<BlockState?>?> = arrayOfNulls(dimension.sections)
val light: Array<ByteArray?> = arrayOfNulls(dimension.sections) val light: Array<ByteArray?> = arrayOfNulls(dimension.sections)
var lightReceived = 0 var lightReceived = 0
val biomes: Array<Array<Biome?>?> = arrayOfNulls(dimension.sections) val biomes: Array<Array<Biome?>?> = arrayOfNulls(dimension.sections)
@ -176,8 +175,8 @@ object ChunkUtil {
val blockContainer: PalettedContainer<BlockState?> = PalettedContainerReader.read(buffer, buffer.connection.registries.blockState, paletteFactory = BlockStatePaletteFactory) val blockContainer: PalettedContainer<BlockState?> = PalettedContainerReader.read(buffer, buffer.connection.registries.blockState, paletteFactory = BlockStatePaletteFactory)
if (!blockContainer.isEmpty) { if (!blockContainer.isEmpty) {
val unpacked = BlockSectionDataProvider(data = blockContainer.unpack()) val unpacked = blockContainer.unpack<BlockState?>()
if (!unpacked.isEmpty) { if (!unpacked.isEmpty()) {
sectionBlocks[sectionHeight - dimension.minSection] = unpacked sectionBlocks[sectionHeight - dimension.minSection] = unpacked
} }
} }
@ -311,4 +310,12 @@ object ChunkUtil {
fun ChunkPosition.isInViewDistance(viewDistance: Int, cameraPosition: ChunkPosition): Boolean { fun ChunkPosition.isInViewDistance(viewDistance: Int, cameraPosition: ChunkPosition): Boolean {
return abs(this.x - cameraPosition.x) <= viewDistance && abs(this.y - cameraPosition.y) <= viewDistance return abs(this.x - cameraPosition.x) <= viewDistance && abs(this.y - cameraPosition.y) <= viewDistance
} }
private fun Array<BlockState?>.isEmpty(): Boolean {
for (entry in this) {
if (entry == null) continue
return false
}
return true
}
} }