From 7f32520f6e43fe521e87ebe2ff5cf22b8a14c80f Mon Sep 17 00:00:00 2001 From: Moritz Zwerger Date: Thu, 7 Dec 2023 23:49:12 +0100 Subject: [PATCH] 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). --- .../data/world/biome/WorldBiomesTest.kt | 6 ++- .../world/chunk/manager/ChunkManagerTest.kt | 18 ++++---- .../block/BlockSectionDataProviderTest.kt | 18 +++++++- .../packets/s2c/play/chunk/ChunkS2CPTest.kt | 6 +++ .../minosoft/data/world/chunk/ChunkSection.kt | 34 +++------------ .../minosoft/data/world/chunk/chunk/Chunk.kt | 4 +- .../data/world/chunk/chunk/ChunkPrototype.kt | 42 +++++++------------ .../data/world/chunk/manager/ChunkManager.kt | 2 +- .../world/chunk/neighbours/ChunkNeighbours.kt | 3 -- .../world/container/SectionDataProvider.kt | 28 +++---------- .../biome/BiomeSectionDataProvider.kt | 41 ++++++++++++++++++ .../block/BlockSectionDataProvider.kt | 16 +------ .../chunk/mesher/SolidSectionMesher.kt | 2 +- .../gui/rendering/tint/TintManager.kt | 8 +--- .../packets/s2c/play/block/chunk/ChunkUtil.kt | 23 ++++++---- 15 files changed, 127 insertions(+), 124 deletions(-) create mode 100644 src/main/java/de/bixilon/minosoft/data/world/container/biome/BiomeSectionDataProvider.kt diff --git a/src/integration-test/kotlin/de/bixilon/minosoft/data/world/biome/WorldBiomesTest.kt b/src/integration-test/kotlin/de/bixilon/minosoft/data/world/biome/WorldBiomesTest.kt index 2bcca08ba..ab65ace86 100644 --- a/src/integration-test/kotlin/de/bixilon/minosoft/data/world/biome/WorldBiomesTest.kt +++ b/src/integration-test/kotlin/de/bixilon/minosoft/data/world/biome/WorldBiomesTest.kt @@ -90,9 +90,11 @@ class WorldBiomesTest { val chunk = world.chunks[0, 0]!! chunk.getOrPut(0) - assertEquals(source.counter, 4096) + assertEquals(source.counter, 0) // biomes ore on demand 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) assertEquals(chunk[0]!!.biomes[1, 2, 3], b1) } diff --git a/src/integration-test/kotlin/de/bixilon/minosoft/data/world/chunk/manager/ChunkManagerTest.kt b/src/integration-test/kotlin/de/bixilon/minosoft/data/world/chunk/manager/ChunkManagerTest.kt index 7ae2ae47a..e96847b88 100644 --- a/src/integration-test/kotlin/de/bixilon/minosoft/data/world/chunk/manager/ChunkManagerTest.kt +++ b/src/integration-test/kotlin/de/bixilon/minosoft/data/world/chunk/manager/ChunkManagerTest.kt @@ -15,12 +15,14 @@ package de.bixilon.minosoft.data.world.chunk.manager import de.bixilon.kotlinglm.vec2.Vec2i 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.identified.Namespaces.minosoft 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.DummyBiomeSource 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.ChunkPrototype 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.NeighbourChangeUpdate 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.ChunkPosition import de.bixilon.minosoft.data.world.positions.InChunkPosition 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.protocol.network.connection.play.ConnectionTestUtil.createConnection +import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition import org.testng.Assert.* import org.testng.annotations.Test @@ -191,9 +193,9 @@ class ChunkManagerTest { manager[ChunkPosition(0, 1)]!![3, 16, 3] = StoneTest0.state manager[ChunkPosition(0, 0)] = ChunkPrototype(blocks = arrayOf( - BlockSectionDataProvider(null).apply { this[3, 3, 3] = StoneTest0.state }, - BlockSectionDataProvider(null).apply { this[3, 3, 3] = StoneTest0.state }, - BlockSectionDataProvider(null).apply { this[3, 3, 3] = StoneTest0.state }, + arrayOfNulls(ProtocolDefinition.BLOCKS_PER_SECTION).apply { this[getIndex(3, 3, 3)] = StoneTest0.state }, + arrayOfNulls(ProtocolDefinition.BLOCKS_PER_SECTION).apply { this[getIndex(3, 3, 3)] = StoneTest0.state }, + arrayOfNulls(ProtocolDefinition.BLOCKS_PER_SECTION).apply { this[getIndex(3, 3, 3)] = StoneTest0.state }, null, null, null, ), blockEntities = emptyMap(), @@ -399,7 +401,7 @@ class ChunkManagerTest { 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)]) @@ -443,8 +445,10 @@ class ChunkManagerTest { val matrix = manager.createMatrix(source) val chunk = matrix[1][1] - assertEquals(chunk.getOrPut(0)!!.biomes.count, 4096) - assertEquals(chunk.getOrPut(0)!!.biomes[0], biome) + val section = chunk.getOrPut(0)!!.biomes + 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(chunk.getBiome(BlockPosition(5, 5, 5)), biome) diff --git a/src/integration-test/kotlin/de/bixilon/minosoft/data/world/container/block/BlockSectionDataProviderTest.kt b/src/integration-test/kotlin/de/bixilon/minosoft/data/world/container/block/BlockSectionDataProviderTest.kt index abe47609c..7cc3f29d2 100644 --- a/src/integration-test/kotlin/de/bixilon/minosoft/data/world/container/block/BlockSectionDataProviderTest.kt +++ b/src/integration-test/kotlin/de/bixilon/minosoft/data/world/container/block/BlockSectionDataProviderTest.kt @@ -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 . + * + * This software is not affiliated with Mojang AB, the original developer of Minecraft. + */ + package de.bixilon.minosoft.data.world.container.block import de.bixilon.kotlinglm.vec3.Vec3i 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.annotations.Test @@ -9,7 +24,8 @@ import org.testng.annotations.Test class BlockSectionDataProviderTest { private fun create(): BlockSectionDataProvider { - return BlockSectionDataProvider(null) + val section = ChunkSection::class.java.allocate() + return BlockSectionDataProvider(null, section) } fun `initial empty`() { diff --git a/src/integration-test/kotlin/de/bixilon/minosoft/protocol/packets/s2c/play/chunk/ChunkS2CPTest.kt b/src/integration-test/kotlin/de/bixilon/minosoft/protocol/packets/s2c/play/chunk/ChunkS2CPTest.kt index 2e1781833..f1ab2b717 100644 --- a/src/integration-test/kotlin/de/bixilon/minosoft/protocol/packets/s2c/play/chunk/ChunkS2CPTest.kt +++ b/src/integration-test/kotlin/de/bixilon/minosoft/protocol/packets/s2c/play/chunk/ChunkS2CPTest.kt @@ -19,8 +19,10 @@ import de.bixilon.kutil.observer.DataObserver import de.bixilon.kutil.reflection.ReflectionUtil.forceSet import de.bixilon.mbf.MBFBinaryReader 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.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.PlayConnection import de.bixilon.minosoft.protocol.packets.s2c.play.block.chunk.ChunkS2CP @@ -37,6 +39,10 @@ class ChunkS2CPTest { registries.update(version, mbf.data.unsafeCast()) } + private operator fun Array.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 { val data = ChunkS2CPTest::class.java.getResourceAsStream("/packets/chunk/$name.bin")!!.readAllBytes() connection.world::dimension.forceSet(DataObserver(dimension)) diff --git a/src/main/java/de/bixilon/minosoft/data/world/chunk/ChunkSection.kt b/src/main/java/de/bixilon/minosoft/data/world/chunk/ChunkSection.kt index bbe27ab64..dc343ba3e 100644 --- a/src/main/java/de/bixilon/minosoft/data/world/chunk/ChunkSection.kt +++ b/src/main/java/de/bixilon/minosoft/data/world/chunk/ChunkSection.kt @@ -14,19 +14,14 @@ package de.bixilon.minosoft.data.world.chunk import de.bixilon.kotlinglm.vec2.Vec2i 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.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.light.SectionLight 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.gui.rendering.util.VecUtil.of import de.bixilon.minosoft.protocol.network.connection.play.PlayConnection -import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition import java.util.* /** @@ -34,12 +29,12 @@ import java.util.* */ class ChunkSection( val sectionHeight: Int, - chunk: Chunk? = null, - var blocks: BlockSectionDataProvider = BlockSectionDataProvider(chunk?.lock), - var biomes: SectionDataProvider = SectionDataProvider(chunk?.lock, checkSize = false), - var blockEntities: SectionDataProvider = SectionDataProvider(chunk?.lock, checkSize = true), + val chunk: Chunk, ) { - val chunk: Chunk = chunk ?: unsafeNull() + var blocks = BlockSectionDataProvider(chunk.lock, this) + var biomes = BiomeSectionDataProvider(chunk.lock, this) + var blockEntities: SectionDataProvider = SectionDataProvider(chunk.lock, checkSize = true) + var light = SectionLight(this) var neighbours: Array? = null @@ -66,14 +61,6 @@ class ChunkSection( } } - fun buildBiomeCache(noise: NoiseBiomeAccessor) { - val biomes: Array = 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() { blocks.clear() @@ -81,16 +68,7 @@ class ChunkSection( blockEntities.clear() } - fun updateChunk(chunk: Chunk) { - CHUNK[this] = chunk - blocks.lock = chunk.lock - // biomes? - blockEntities.lock = chunk.lock - } - companion object { - private val CHUNK = ChunkSection::chunk.jvmField - inline val Vec3i.index: Int get() = getIndex(x, y, z) diff --git a/src/main/java/de/bixilon/minosoft/data/world/chunk/chunk/Chunk.kt b/src/main/java/de/bixilon/minosoft/data/world/chunk/chunk/Chunk.kt index 1c9f9eebd..b492fcc8a 100644 --- a/src/main/java/de/bixilon/minosoft/data/world/chunk/chunk/Chunk.kt +++ b/src/main/java/de/bixilon/minosoft/data/world/chunk/chunk/Chunk.kt @@ -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.update.block.ChunkLocalBlockUpdate 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.InChunkPosition import de.bixilon.minosoft.data.world.positions.SectionHeight @@ -44,7 +43,6 @@ import java.util.* class Chunk( val connection: PlayConnection, val chunkPosition: ChunkPosition, - var sections: Array, var biomeSource: BiomeSource, ) : Iterable, BiomeAccessor { val lock = ThreadLock() @@ -53,6 +51,7 @@ class Chunk( val minSection = world.dimension.minSection val maxSection = world.dimension.maxSection val cacheBiomes = world.biomes.noise != null + var sections: Array = arrayOfNulls(world.dimension.sections) val neighbours = ChunkNeighbours(this) @@ -184,7 +183,6 @@ class Chunk( var section = sections[index] // get another time, it might have changed already if (section == null) { section = ChunkSection(sectionHeight, chunk = this) - section.blocks.unsafeSetSection(section) val neighbours = this.neighbours.get() if (neighbours != null) { this.neighbours.completeSection(neighbours, section, sectionHeight, world.biomes.noise) diff --git a/src/main/java/de/bixilon/minosoft/data/world/chunk/chunk/ChunkPrototype.kt b/src/main/java/de/bixilon/minosoft/data/world/chunk/chunk/ChunkPrototype.kt index d429b15d4..56880344a 100644 --- a/src/main/java/de/bixilon/minosoft/data/world/chunk/chunk/ChunkPrototype.kt +++ b/src/main/java/de/bixilon/minosoft/data/world/chunk/chunk/ChunkPrototype.kt @@ -15,20 +15,18 @@ package de.bixilon.minosoft.data.world.chunk.chunk import de.bixilon.kotlinglm.vec3.Vec3i 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.data.registries.blocks.state.BlockState 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.chunk.ChunkSection -import de.bixilon.minosoft.data.world.container.block.BlockSectionDataProvider import de.bixilon.minosoft.data.world.positions.ChunkPosition import de.bixilon.minosoft.protocol.network.connection.play.PlayConnection import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition import it.unimi.dsi.fastutil.ints.IntOpenHashSet class ChunkPrototype( - var blocks: Array? = null, + var blocks: Array?>? = null, var blockEntities: Map? = null, var biomeSource: BiomeSource? = null, var light: Array? = null, @@ -53,28 +51,23 @@ class ChunkPrototype( val dimension = connection.world.dimension - val sections: Array = arrayOfNulls(dimension.sections) val light = this.light - for ((index, provider) in blocks.withIndex()) { - if (provider == null) continue - val section = ChunkSection(index + dimension.minSection, null, provider) - SECTION[provider] = section + val chunk = Chunk(connection, position, biomeSource) + + for ((index, blockData) in blocks.withIndex()) { + if (blockData == null) continue + val section = ChunkSection(index + dimension.minSection, chunk) + section.blocks.setData(blockData) if (!StaticConfiguration.IGNORE_SERVER_LIGHT) { 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) { this.topLight?.let { chunk.light.top.update(it) } this.bottomLight?.let { chunk.light.bottom.update(it) } @@ -84,7 +77,7 @@ class ChunkPrototype( return chunk } - private fun Array.update(chunk: Chunk, replace: Boolean, affected: IntOpenHashSet) { + private fun Array?>.update(chunk: Chunk, replace: Boolean, affected: IntOpenHashSet) { for ((index, provider) in this.withIndex()) { var section = chunk.sections[index] val sectionHeight = index - chunk.minSection @@ -101,16 +94,15 @@ class ChunkPrototype( } else { section.blockEntities.clear() } - SECTION.forceSet(provider, section) - section.blocks = provider + section.blocks.setData(provider) affected += sectionHeight } } - private fun Map?.update(minSection: Int, sections: Array, affected: IntOpenHashSet?, connection: PlayConnection) { + private fun Map?.update(minSection: Int, chunk: Chunk, affected: IntOpenHashSet?, connection: PlayConnection) { val position = Vec3i() - for ((index, section) in sections.withIndex()) { + for ((index, section) in chunk.sections.withIndex()) { if (section == null || section.blocks.isEmpty) continue val blocks = section.blocks val sectionHeight = (index + minSection) @@ -142,7 +134,7 @@ class ChunkPrototype( fun updateChunk(chunk: Chunk, replace: Boolean): IntOpenHashSet? { val affected = IntOpenHashSet() 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 @@ -155,8 +147,4 @@ class ChunkPrototype( if (affected.isEmpty()) return null return affected } - - private companion object { - private val SECTION = BlockSectionDataProvider::section.jvmField - } } diff --git a/src/main/java/de/bixilon/minosoft/data/world/chunk/manager/ChunkManager.kt b/src/main/java/de/bixilon/minosoft/data/world/chunk/manager/ChunkManager.kt index 86f7f85bc..eec270837 100644 --- a/src/main/java/de/bixilon/minosoft/data/world/chunk/manager/ChunkManager.kt +++ b/src/main/java/de/bixilon/minosoft/data/world/chunk/manager/ChunkManager.kt @@ -161,7 +161,7 @@ class ChunkManager(val world: World, chunkCapacity: Int = 0, prototypeCapacity: fun create(position: ChunkPosition, biome: BiomeSource = DummyBiomeSource(null)): Chunk { if (!world.isValidPosition(position)) throw IllegalArgumentException("Chunk position $position is not valid!") 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) chunks.lock.unlock() diff --git a/src/main/java/de/bixilon/minosoft/data/world/chunk/neighbours/ChunkNeighbours.kt b/src/main/java/de/bixilon/minosoft/data/world/chunk/neighbours/ChunkNeighbours.kt index d579b5262..ceafb4e90 100644 --- a/src/main/java/de/bixilon/minosoft/data/world/chunk/neighbours/ChunkNeighbours.kt +++ b/src/main/java/de/bixilon/minosoft/data/world/chunk/neighbours/ChunkNeighbours.kt @@ -72,9 +72,6 @@ class ChunkNeighbours(val chunk: Chunk) : Iterable { fun completeSection(neighbours: Array, section: ChunkSection, sectionHeight: SectionHeight, noise: NoiseBiomeAccessor?) { section.neighbours = ChunkUtil.getDirectNeighbours(neighbours, chunk, sectionHeight) - if (noise != null) { - section.buildBiomeCache(noise) - } } private fun complete(neighbours: Array) { diff --git a/src/main/java/de/bixilon/minosoft/data/world/container/SectionDataProvider.kt b/src/main/java/de/bixilon/minosoft/data/world/container/SectionDataProvider.kt index d68c1c238..292badb09 100644 --- a/src/main/java/de/bixilon/minosoft/data/world/container/SectionDataProvider.kt +++ b/src/main/java/de/bixilon/minosoft/data/world/container/SectionDataProvider.kt @@ -23,36 +23,25 @@ import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition open class SectionDataProvider( var lock: Lock?, - data: Array? = null, val checkSize: Boolean = false, - calculateInitial: Boolean = true, ) : Iterable { - protected var data: Array? = data?.unsafeCast() + protected var data: Array? = null private set var count: Int = 0 private set val isEmpty: Boolean get() = count == 0 - lateinit var minPosition: Vec3i + var minPosition = Vec3i(ProtocolDefinition.CHUNK_SECTION_SIZE) private set - lateinit var maxPosition: Vec3i + var maxPosition = Vec3i.EMPTY private set - init { - if (data != null && calculateInitial) { - recalculate() - } else { - minPosition = Vec3i(ProtocolDefinition.CHUNK_SECTION_SIZE) - maxPosition = Vec3i.EMPTY - } - } - @Suppress("UNCHECKED_CAST") - operator fun get(index: Int): T { + open operator fun get(index: Int): 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)) } @@ -193,13 +182,6 @@ open class SectionDataProvider( lock?.unlock() } - fun copy(): SectionDataProvider { - lock?.acquire() - val clone = SectionDataProvider(null, data?.clone()?.unsafeCast()) - lock?.release() - - return clone - } fun clear() { lock?.lock() diff --git a/src/main/java/de/bixilon/minosoft/data/world/container/biome/BiomeSectionDataProvider.kt b/src/main/java/de/bixilon/minosoft/data/world/container/biome/BiomeSectionDataProvider.kt new file mode 100644 index 000000000..4f730c24e --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/data/world/container/biome/BiomeSectionDataProvider.kt @@ -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 . + * + * 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(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 + } +} diff --git a/src/main/java/de/bixilon/minosoft/data/world/container/block/BlockSectionDataProvider.kt b/src/main/java/de/bixilon/minosoft/data/world/container/block/BlockSectionDataProvider.kt index 08a8608ee..27c324e41 100644 --- a/src/main/java/de/bixilon/minosoft/data/world/container/block/BlockSectionDataProvider.kt +++ b/src/main/java/de/bixilon/minosoft/data/world/container/block/BlockSectionDataProvider.kt @@ -13,9 +13,7 @@ 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.reflection.ReflectionUtil.jvmField 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.fluid.fluids.WaterFluid.Companion.isWaterlogged @@ -25,9 +23,8 @@ import de.bixilon.minosoft.data.world.container.SectionDataProvider class BlockSectionDataProvider( lock: Lock? = null, - data: Array? = null, -) : SectionDataProvider(lock, data, true, false) { - val section: ChunkSection = unsafeNull() + val section: ChunkSection, +) : SectionDataProvider(lock, true) { val occlusion = SectionOcclusion(this) var fluidCount = 0 private set @@ -89,13 +86,4 @@ class BlockSectionDataProvider( } 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 - } - } } diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/mesher/SolidSectionMesher.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/mesher/SolidSectionMesher.kt index 77956e7e5..6697ab57a 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/mesher/SolidSectionMesher.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/mesher/SolidSectionMesher.kt @@ -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 model?.render(position, floatOffset, mesh, random, state, neighbourBlocks, light, tints, blockEntity.unsafeCast())?.let { if (it) rendered = true } diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/tint/TintManager.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/tint/TintManager.kt index 8d756cf39..6efc4d921 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/tint/TintManager.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/tint/TintManager.kt @@ -17,7 +17,6 @@ import de.bixilon.kotlinglm.vec3.Vec3i import de.bixilon.kutil.primitive.IntUtil.toInt import de.bixilon.minosoft.assets.AssetsManager 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.fluid.Fluid import de.bixilon.minosoft.data.text.formatting.color.RGBColor @@ -47,10 +46,11 @@ class TintManager(val connection: PlayConnection) { 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 val tintProvider = state.block.tintProvider ?: return null val tints = IntArray(if (tintProvider is MultiTintProvider) tintProvider.tints else 1) + val biome = chunk?.getBiome(x, y, z) for (tintIndex in tints.indices) { 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) } - 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? { val biome = chunk.getBiome(x and 0x0F, y, z and 0x0F) return fluid.model?.tint?.getFluidTint(fluid, biome, height, x, y, z) diff --git a/src/main/java/de/bixilon/minosoft/protocol/packets/s2c/play/block/chunk/ChunkUtil.kt b/src/main/java/de/bixilon/minosoft/protocol/packets/s2c/play/block/chunk/ChunkUtil.kt index 758a3d98e..5c74f92f3 100644 --- a/src/main/java/de/bixilon/minosoft/protocol/packets/s2c/play/block/chunk/ChunkUtil.kt +++ b/src/main/java/de/bixilon/minosoft/protocol/packets/s2c/play/block/chunk/ChunkUtil.kt @@ -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.ChunkPrototype 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.PalettedContainerReader import de.bixilon.minosoft.data.world.container.palette.palettes.BiomePaletteFactory @@ -74,7 +73,7 @@ object ChunkUtil { // parse data var index = 0 - val sections: Array = arrayOfNulls(dimension.sections) + val sections: Array?> = arrayOfNulls(dimension.sections) for ((sectionIndex, sectionHeight) in (dimension.minSection..dimension.maxSection).withIndex()) { if (!sectionBitMask[sectionIndex]) { continue @@ -106,7 +105,7 @@ object ChunkUtil { blocks[yzx] = buffer.connection.registries.blockState.getOrNull(blockId) ?: continue } - sections[sectionHeight] = BlockSectionDataProvider(data = blocks) + sections[sectionHeight] = blocks } chunkData.blocks = sections return chunkData @@ -139,7 +138,7 @@ object ChunkUtil { } var index = 0 - val sections: Array = arrayOfNulls(dimension.sections) + val sections: Array?> = arrayOfNulls(dimension.sections) for ((sectionIndex, sectionHeight) in (dimension.minSection..dimension.maxSection).withIndex()) { // max sections per chunks in chunk column if (!sectionBitMask[sectionIndex]) { continue @@ -150,7 +149,7 @@ object ChunkUtil { val block = buffer.connection.registries.blockState.getOrNull(blockId) ?: continue blocks[yzx] = block } - sections[sectionHeight] = BlockSectionDataProvider(data = blocks) + sections[sectionHeight] = blocks } chunkData.blocks = sections return chunkData @@ -158,7 +157,7 @@ object ChunkUtil { fun readPaletteChunk(buffer: PlayInByteBuffer, dimension: DimensionProperties, sectionBitMask: BitSet?, complete: Boolean, skylight: Boolean = false): ChunkPrototype { val chunkData = ChunkPrototype() - val sectionBlocks: Array = arrayOfNulls(dimension.sections) + val sectionBlocks: Array?> = arrayOfNulls(dimension.sections) val light: Array = arrayOfNulls(dimension.sections) var lightReceived = 0 val biomes: Array?> = arrayOfNulls(dimension.sections) @@ -176,8 +175,8 @@ object ChunkUtil { val blockContainer: PalettedContainer = PalettedContainerReader.read(buffer, buffer.connection.registries.blockState, paletteFactory = BlockStatePaletteFactory) if (!blockContainer.isEmpty) { - val unpacked = BlockSectionDataProvider(data = blockContainer.unpack()) - if (!unpacked.isEmpty) { + val unpacked = blockContainer.unpack() + if (!unpacked.isEmpty()) { sectionBlocks[sectionHeight - dimension.minSection] = unpacked } } @@ -311,4 +310,12 @@ object ChunkUtil { fun ChunkPosition.isInViewDistance(viewDistance: Int, cameraPosition: ChunkPosition): Boolean { return abs(this.x - cameraPosition.x) <= viewDistance && abs(this.y - cameraPosition.y) <= viewDistance } + + private fun Array.isEmpty(): Boolean { + for (entry in this) { + if (entry == null) continue + return false + } + return true + } }