wip: improved chunk storing format

This commit is contained in:
Bixilon 2021-11-12 18:46:29 +01:00
parent db88f83f72
commit 86155c8f3f
No known key found for this signature in database
GPG Key ID: 5CAD791931B09AC4
25 changed files with 391 additions and 286 deletions

View File

@ -24,5 +24,5 @@ abstract class BlockEntity(
open fun updateNBT(nbt: Map<String, Any>) = Unit
open fun realTick(connection: PlayConnection, blockState: BlockState, blockPosition: Vec3i) = Unit
open fun tick(connection: PlayConnection, blockState: BlockState, blockPosition: Vec3i) = Unit
}

View File

@ -59,7 +59,7 @@ class CampfireBlockEntity(connection: PlayConnection) : BlockEntity(connection)
}
override fun realTick(connection: PlayConnection, blockState: BlockState, blockPosition: Vec3i) {
override fun tick(connection: PlayConnection, blockState: BlockState, blockPosition: Vec3i) {
if (blockState.properties[BlockProperties.LIT] != true) {
return
}

View File

@ -58,7 +58,7 @@ class MobSpawnerBlockEntity(connection: PlayConnection) : BlockEntity(connection
// ToDo: {MaxNearbyEntities: 6s, RequiredPlayerRange: 16s, SpawnCount: 4s, x: -80, y: 4, SpawnData: {id: "minecraft:zombie"}, z: 212, id: "minecraft:mob_spawner", MaxSpawnDelay: 800s, SpawnRange: 4s, Delay: 0s, MinSpawnDelay: 200s}
}
override fun realTick(connection: PlayConnection, blockState: BlockState, blockPosition: Vec3i) {
override fun tick(connection: PlayConnection, blockState: BlockState, blockPosition: Vec3i) {
spawnParticles(blockPosition)
}

View File

@ -50,8 +50,8 @@ class NoteBlockBlockEntity(connection: PlayConnection) : BlockEntity(connection)
// ToDo: Play sound?
}
override fun realTick(connection: PlayConnection, blockState: BlockState, blockPosition: Vec3i) {
super.realTick(connection, blockState, blockPosition)
override fun tick(connection: PlayConnection, blockState: BlockState, blockPosition: Vec3i) {
super.tick(connection, blockState, blockPosition)
if (!showParticleNextTick) {
return
}

View File

@ -37,6 +37,8 @@ data class DimensionProperties(
} else {
height / ProtocolDefinition.SECTION_HEIGHT_Y
}
val sections = highestSection - lowestSection
val lightLevels = FloatArray(16)
init {

View File

@ -18,12 +18,7 @@ import de.bixilon.minosoft.data.entities.entities.animal.horse.*
import de.bixilon.minosoft.data.entities.entities.monster.*
import de.bixilon.minosoft.data.entities.entities.vehicle.*
import de.bixilon.minosoft.data.entities.meta.EntityMetaData
import de.bixilon.minosoft.data.registries.blocks.BlockState
import de.bixilon.minosoft.data.world.ChunkSection
import de.bixilon.minosoft.data.world.ChunkSection.Companion.indexPosition
import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition
import de.bixilon.minosoft.protocol.protocol.ProtocolVersions
import glm_.vec3.Vec3i
object VersionTweaker {
// some data was packed in mata data in early versions (1.8). This function converts it to the real resource location
@ -87,40 +82,4 @@ object VersionTweaker {
}
return fakeClass
}
@JvmStatic
fun transformSections(sections: Map<Int, ChunkSection>, versionId: Int) {
// some blocks need to be tweaked. eg. Grass with a snow block on top becomes snowy grass block
if (versionId >= ProtocolDefinition.FLATTING_VERSION_ID) {
return
}
for ((sectionHeight, section) in sections) {
for ((index, blockState) in section.blocks.withIndex()) {
if (blockState == null) {
continue
}
val location = index.indexPosition
val newBlock = transformBlock(blockState, sections, location, sectionHeight)
if (newBlock === blockState) {
continue
}
if (newBlock == null) {
section.setBlockState(location, null)
continue
}
section.setBlockState(location, newBlock)
}
}
}
@JvmStatic
fun transformBlock(originalBlock: BlockState?, sections: Map<Int, ChunkSection>, inChunkSectionPositions: Vec3i, sectionHeight: Int): BlockState? {
// ToDo: Broken
return originalBlock
}
private fun getBlockAbove(sections: Map<Int, ChunkSection>, inChunkSectionPositions: Vec3i, sectionHeight: Int): BlockState? {
return sections[sectionHeight]?.getBlockState(inChunkSectionPositions)
}
}

View File

@ -16,102 +16,113 @@ import de.bixilon.minosoft.data.entities.block.BlockEntity
import de.bixilon.minosoft.data.registries.blocks.BlockState
import de.bixilon.minosoft.data.world.biome.source.BiomeSource
import de.bixilon.minosoft.data.world.light.LightAccessor
import de.bixilon.minosoft.gui.rendering.util.VecUtil.EMPTY
import de.bixilon.minosoft.gui.rendering.util.VecUtil.inChunkSectionPosition
import de.bixilon.minosoft.gui.rendering.util.VecUtil.of
import de.bixilon.minosoft.gui.rendering.util.VecUtil.sectionHeight
import de.bixilon.minosoft.protocol.network.connection.play.PlayConnection
import de.bixilon.minosoft.util.KUtil.toSynchronizedMap
import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition
import glm_.vec2.Vec2i
import glm_.vec3.Vec3i
/**
* Collection of chunks sections (allocated in y)
* Collection of chunks sections (from the lowest section to the highest section in y axis)
*/
class Chunk(
var sections: MutableMap<Int, ChunkSection>? = null,
private val connection: PlayConnection,
private var sections: Array<ChunkSection?>? = null,
var biomeSource: BiomeSource? = null,
var lightAccessor: LightAccessor? = null,
) {
private val lock = Object()
) : Iterable<ChunkSection?> {
val lowestSection = connection.world.dimension!!.lowestSection
val blocksInitialized: Boolean
get() = sections != null
val biomesInitialized
get() = biomeSource != null
val lightInitialized
get() = lightAccessor != null
val isFullyLoaded: Boolean
get() {
return sections != null && biomeSource != null && lightAccessor != null
}
get() = blocksInitialized && biomesInitialized && lightInitialized
operator fun get(inChunkPosition: Vec3i): BlockState? {
return sections?.get(inChunkPosition.sectionHeight)?.getBlockState(inChunkPosition.inChunkSectionPosition)
operator fun get(sectionHeight: Int): ChunkSection? = sections?.getOrNull(sectionHeight - lowestSection)
fun get(x: Int, y: Int, z: Int): BlockState? {
return this[y.sectionHeight]?.blocks?.get(x, y % ProtocolDefinition.SECTION_HEIGHT_Y, z)
}
operator fun get(x: Int, y: Int, z: Int): BlockState? {
return get(Vec3i(x, y, z))
operator fun get(position: Vec3i): BlockState? = get(position.x, position.y, position.z)
fun set(x: Int, y: Int, z: Int, blockState: BlockState?, blockEntity: BlockEntity? = null) {
val section = getOrPut(y.sectionHeight)
section.blocks[x, y % ProtocolDefinition.SECTION_HEIGHT_Y, z] = blockState
section.blockEntities[x, y % ProtocolDefinition.SECTION_HEIGHT_Y, z] = blockEntity // ToDo
}
operator fun set(position: Vec3i, blockState: BlockState?) = set(position.x, position.y, position.z, blockState)
fun setBlocks(blocks: Map<Vec3i, BlockState?>) {
for ((location, blockInfo) in blocks) {
set(location, blockInfo)
for ((location, blockState) in blocks) {
set(location, blockState)
}
}
fun getBlockEntity(x: Int, y: Int, z: Int): BlockEntity? {
return this[y.sectionHeight]?.blockEntities?.get(x, y % ProtocolDefinition.SECTION_HEIGHT_Y, z)
}
fun getBlockEntity(position: Vec3i): BlockEntity? = getBlockEntity(position.x, position.y, position.z)
fun setBlockEntity(x: Int, y: Int, z: Int, blockEntity: BlockEntity?) {
getOrPut(y.sectionHeight).blockEntities[x, y % ProtocolDefinition.SECTION_HEIGHT_Y, z] = blockEntity
}
fun setBlockEntity(position: Vec3i, blockEntity: BlockEntity?) = setBlockEntity(position.x, position.y, position.z, blockEntity)
fun setData(data: ChunkData, merge: Boolean = false) {
synchronized(lock) {
data.blocks?.let {
if (sections == null) {
sections = mutableMapOf()
}
if (!merge) {
sections?.clear()
}
// replace all chunk sections
for ((sectionHeight, chunkSection) in it) {
getOrPut(sectionHeight).setData(chunkSection)
}
data.blocks?.let {
var sections = this.sections
if (sections == null || !merge) {
sections = arrayOfNulls(connection.world.dimension!!.sections)
this.sections = sections
}
data.biomeSource?.let {
this.biomeSource = it
}
data.lightAccessor?.let {
this.lightAccessor = it
// replace all chunk sections
for ((index, section) in it.withIndex()) {
section ?: continue
sections[index] = section
}
}
}
operator fun set(inChunkPosition: Vec3i, blockState: BlockState?) {
getOrPut(inChunkPosition.sectionHeight).setBlockState(inChunkPosition.inChunkSectionPosition, blockState)
data.biomeSource?.let {
this.biomeSource = it
}
data.lightAccessor?.let {
this.lightAccessor = it
}
}
private fun getOrPut(sectionHeight: Int): ChunkSection {
if (sections == null) {
throw IllegalStateException("Chunk not received/initialized yet!")
}
return sections!![sectionHeight].let {
var section = it
if (section == null) {
section = ChunkSection()
sections!![sectionHeight] = section
}
section
val sections = sections ?: throw NullPointerException("Sections not initialized yet!")
val sectionIndex = sectionHeight - lowestSection
var section = sections[sectionIndex]
if (section == null) {
section = ChunkSection(connection.registries)
sections[sectionIndex] = section
}
return section
}
fun realTick(connection: PlayConnection, chunkPosition: Vec2i) {
fun tick(connection: PlayConnection, chunkPosition: Vec2i) {
if (!isFullyLoaded) {
return
}
val sections = sections
sections ?: return
for ((height, section) in sections.toSynchronizedMap()) {
section.realTick(connection, Vec3i.of(chunkPosition, height, Vec3i.EMPTY))
val sections = sections!!
for ((index, section) in sections.withIndex()) {
section ?: continue
section.tick(connection, chunkPosition, index - lowestSection)
}
}
fun getBlockEntity(inChunkPosition: Vec3i): BlockEntity? {
return sections?.get(inChunkPosition.sectionHeight)?.getBlockEntity(inChunkPosition.inChunkSectionPosition)
}
operator fun set(inChunkPosition: Vec3i, blockEntity: BlockEntity?) {
sections?.get(inChunkPosition.sectionHeight)?.setBlockEntity(inChunkPosition.inChunkSectionPosition, blockEntity)
override fun iterator(): Iterator<ChunkSection?> {
return sections!!.iterator()
}
}

View File

@ -17,20 +17,14 @@ import de.bixilon.minosoft.data.world.biome.source.BiomeSource
import de.bixilon.minosoft.data.world.light.LightAccessor
data class ChunkData(
var blocks: Map<Int, ChunkSection>? = null,
var blocks: Array<ChunkSection?>? = null,
var biomeSource: BiomeSource? = null,
var lightAccessor: LightAccessor? = null,
) {
fun replace(data: ChunkData) {
data.blocks?.let {
this.blocks = it
}
data.biomeSource?.let {
this.biomeSource = it
}
data.lightAccessor?.let {
this.lightAccessor = it
}
data.blocks?.let { this.blocks = it }
data.biomeSource?.let { this.biomeSource = it }
data.lightAccessor?.let { this.lightAccessor = it }
}
}

View File

@ -13,54 +13,69 @@
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.block.entities.ArrayBlockEntityProvider
import de.bixilon.minosoft.data.world.block.entities.BlockEntityProvider
import de.bixilon.minosoft.data.world.block.entities.MapBlockEntityProvider
import de.bixilon.minosoft.data.registries.registries.Registries
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
import de.bixilon.minosoft.protocol.network.connection.play.PlayConnection
import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition
import de.bixilon.minosoft.util.KUtil.unsafeCast
import glm_.vec2.Vec2i
import glm_.vec3.Vec3i
/**
* Collection of 16x16x16 blocks
*/
class ChunkSection(
var blocks: Array<BlockState?> = arrayOfNulls(ProtocolDefinition.BLOCKS_PER_SECTION),
private var blockEntities: BlockEntityProvider = MapBlockEntityProvider(),
val blocks: RegistrySectionDataProvider<BlockState?>,
@Deprecated("TODO")
val biomes: RegistrySectionDataProvider<Biome>,
val blockEntities: SectionDataProvider<BlockEntity?>,
@Deprecated("TODO")
val light: ByteArray, // packed (skyLight: 0xF0, blockLight: 0x0F)
) {
fun getBlockState(inChunkSectionPositions: Vec3i): BlockState? {
return blocks[inChunkSectionPositions.index]
}
fun setBlockState(inChunkSectionPositions: Vec3i, blockState: BlockState?) {
blocks[inChunkSectionPositions.index] = blockState
}
fun setData(chunkSection: ChunkSection) {
blocks = chunkSection.blocks.clone()
blockEntities = chunkSection.blockEntities.clone()
}
fun getBlockEntity(inChunkSectionPositions: Vec3i): BlockEntity? {
return blockEntities[inChunkSectionPositions]
}
fun setBlockEntity(inChunkSectionPositions: Vec3i, blockEntity: BlockEntity?) {
blockEntities[inChunkSectionPositions] = blockEntity
val blockEntities = blockEntities
if (blockEntities.size > BlockEntityProvider.BLOCK_ENTITY_MAP_LIMIT_UP && blockEntities is MapBlockEntityProvider) {
this.blockEntities = ArrayBlockEntityProvider(blockEntities)
} else if (blockEntities.size <= BlockEntityProvider.BLOCK_ENTITY_MAP_LIMIT_DOWN && blockEntities is ArrayBlockEntityProvider) {
this.blockEntities = MapBlockEntityProvider(blockEntities)
constructor(registries: Registries, blocks: Array<BlockState?>? = null) : this(RegistrySectionDataProvider<BlockState?>(registries.blockStateRegistry.unsafeCast()), RegistrySectionDataProvider(registries.biomeRegistry), SectionDataProvider(), ByteArray(ProtocolDefinition.BLOCKS_PER_SECTION)) {
if (blocks != null) {
this.blocks.setData(blocks)
}
}
fun realTick(connection: PlayConnection, chunkSectionPosition: Vec3i) {
blockEntities.forEach { entity, inChunkSectionPosition ->
val block = blocks[inChunkSectionPosition.index] ?: return@forEach // maybe block already got destroyed
entity.realTick(connection, block, chunkSectionPosition + inChunkSectionPosition)
fun tick(connection: PlayConnection, chunkPosition: Vec2i, sectionHeight: Int) {
acquire()
for ((index, blockEntity) in blockEntities.withIndex()) {
blockEntity ?: continue
val position = Vec3i.of(chunkPosition, sectionHeight, index.indexPosition)
val blockState = blocks[index] ?: continue
blockEntity.tick(connection, blockState, position)
}
release()
}
fun acquire() {
blocks.acquire()
biomes.acquire()
blockEntities.acquire()
}
fun release() {
blocks.release()
biomes.release()
blockEntities.release()
}
fun lock() {
blocks.lock()
biomes.lock()
blockEntities.lock()
}
fun unlock() {
blocks.unlock()
biomes.unlock()
blockEntities.unlock()
}
companion object {

View File

@ -21,7 +21,6 @@ import de.bixilon.minosoft.data.registries.blocks.BlockState
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.registries.tweaker.VersionTweaker
import de.bixilon.minosoft.data.world.biome.accessor.BiomeAccessor
import de.bixilon.minosoft.data.world.biome.accessor.NullBiomeAccessor
import de.bixilon.minosoft.data.world.light.WorldLightAccessor
@ -31,10 +30,8 @@ import de.bixilon.minosoft.gui.rendering.sound.AudioPlayer
import de.bixilon.minosoft.gui.rendering.util.VecUtil.blockPosition
import de.bixilon.minosoft.gui.rendering.util.VecUtil.chunkPosition
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.minus
import de.bixilon.minosoft.gui.rendering.util.VecUtil.plus
import de.bixilon.minosoft.gui.rendering.util.VecUtil.sectionHeight
import de.bixilon.minosoft.modding.event.EventInitiators
import de.bixilon.minosoft.modding.event.events.BlockSetEvent
import de.bixilon.minosoft.modding.event.events.ChunkUnloadEvent
@ -43,6 +40,7 @@ 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.collections.SynchronizedMap
import glm_.func.common.clamp
import glm_.vec2.Vec2i
import glm_.vec3.Vec3
@ -58,7 +56,7 @@ import kotlin.random.Random
class World(
val connection: PlayConnection,
) : BiomeAccessor {
val chunks: MutableMap<Vec2i, Chunk> = synchronizedMapOf()
val chunks: SynchronizedMap<Vec2i, Chunk> = synchronizedMapOf()
val entities = WorldEntities()
var hardcore = false
var dimension: DimensionProperties? = null
@ -81,14 +79,12 @@ class World(
return chunks[blockPosition.chunkPosition]?.get(blockPosition.inChunkPosition)
}
@Synchronized
operator fun get(chunkPosition: Vec2i): Chunk? {
return chunks[chunkPosition]
}
@Synchronized
fun getOrCreateChunk(chunkPosition: Vec2i): Chunk {
return chunks.getOrPut(chunkPosition) { Chunk() }
return chunks.getOrPut(chunkPosition) { Chunk(connection) }
}
fun setBlockState(blockPosition: Vec3i, blockState: BlockState?) {
@ -96,32 +92,24 @@ class World(
}
operator fun set(blockPosition: Vec3i, blockState: BlockState?) {
val chunkPosition = blockPosition.chunkPosition
chunks[chunkPosition]?.let {
val sections = it.sections ?: return
val transformedBlockState = if (connection.version.isFlattened()) {
blockState
} else {
VersionTweaker.transformBlock(blockState, sections, blockPosition.inChunkSectionPosition, blockPosition.sectionHeight)
}
val inChunkPosition = blockPosition.inChunkPosition
val previousBlock = it[inChunkPosition]
if (previousBlock == transformedBlockState) {
return
}
previousBlock?.block?.onBreak(connection, blockPosition, previousBlock, it.getBlockEntity(inChunkPosition))
blockState?.block?.onPlace(connection, blockPosition, blockState)
it[inChunkPosition] = transformedBlockState
connection.fireEvent(BlockSetEvent(
connection = connection,
blockPosition = blockPosition,
blockState = transformedBlockState,
))
val chunk = chunks[blockPosition.chunkPosition] ?: return
val inChunkPosition = blockPosition.inChunkPosition
val previousBlock = chunk[inChunkPosition]
if (previousBlock == blockState) {
return
}
previousBlock?.block?.onBreak(connection, blockPosition, previousBlock, chunk.getBlockEntity(inChunkPosition))
blockState?.block?.onPlace(connection, blockPosition, blockState)
chunk[inChunkPosition] = blockState
connection.fireEvent(BlockSetEvent(
connection = connection,
blockPosition = blockPosition,
blockState = blockState,
))
}
fun isPositionChangeable(blockPosition: Vec3i): Boolean {
// ToDo: World border
val dimension = connection.world.dimension!!
return (blockPosition.y >= dimension.minY || blockPosition.y < dimension.height)
}
@ -134,37 +122,22 @@ class World(
}
fun unloadChunk(chunkPosition: Vec2i) {
chunks.remove(chunkPosition)?.let {
connection.fireEvent(ChunkUnloadEvent(connection, EventInitiators.UNKNOWN, chunkPosition))
}
}
fun replaceChunk(position: Vec2i, chunk: Chunk) {
chunks[position] = chunk
}
fun replaceChunks(chunkMap: HashMap<Vec2i, Chunk>) {
for ((chunkLocation, chunk) in chunkMap) {
chunks[chunkLocation] = chunk
}
chunks.remove(chunkPosition) ?: return
connection.fireEvent(ChunkUnloadEvent(connection, EventInitiators.UNKNOWN, chunkPosition))
}
fun getBlockEntity(blockPosition: Vec3i): BlockEntity? {
return get(blockPosition.chunkPosition)?.getBlockEntity(blockPosition.inChunkPosition)
}
operator fun set(blockPosition: Vec3i, blockEntity: BlockEntity?) {
get(blockPosition.chunkPosition)?.set(blockPosition.inChunkPosition, blockEntity)
}
fun setBlockEntity(blockPosition: Vec3i, blockEntity: BlockEntity?) {
this[blockPosition] = blockEntity
get(blockPosition.chunkPosition)?.setBlockEntity(blockPosition.inChunkPosition, blockEntity)
}
fun setBlockEntities(blockEntities: Map<Vec3i, BlockEntity>) {
for ((blockPosition, entityMetaData) in blockEntities) {
set(blockPosition, entityMetaData)
for ((blockPosition, blockEntity) in blockEntities) {
setBlockEntity(blockPosition, blockEntity)
}
}
@ -172,9 +145,9 @@ class World(
return biomeAccessor.getBiome(blockPosition)
}
fun realTick() {
fun tick() {
for ((chunkPosition, chunk) in chunks.toSynchronizedMap()) {
chunk.realTick(connection, chunkPosition)
chunk.tick(connection, chunkPosition)
}
}
@ -201,24 +174,6 @@ class World(
return ret.toMap()
}
fun getBlocks(start: Vec3i, end: Vec3i): Map<Vec3i, BlockState> {
val blocks: MutableMap<Vec3i, BlockState> = mutableMapOf()
for (z in start.z..end.z) {
for (y in start.y..end.y) {
for (x in start.x..end.x) {
val blockPosition = Vec3i(x, y, z)
this[blockPosition]?.let {
blocks[blockPosition] = it
}
}
}
}
return blocks.toMap()
}
fun playSoundEvent(resourceLocation: ResourceLocation, position: Vec3i? = null, volume: Float = 1.0f, pitch: Float = 1.0f) {
audioPlayer?.playSoundEvent(resourceLocation, position, volume, pitch)
}

View File

@ -0,0 +1,43 @@
/*
* Minosoft
* Copyright (C) 2021 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
import de.bixilon.minosoft.data.registries.registries.registry.AbstractRegistry
import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition
class RegistrySectionDataProvider<T>(
val registry: AbstractRegistry<T>,
data: Array<Any?> = arrayOfNulls(ProtocolDefinition.BLOCKS_PER_SECTION),
) : SectionDataProvider<T>(data) {
@Suppress("UNCHECKED_CAST")
fun setIdData(ids: Array<Int>) {
val data: Array<Any?> = arrayOfNulls(ProtocolDefinition.BLOCKS_PER_SECTION)
for ((index, id) in ids.withIndex()) {
data[index] = registry[id]
}
setData(data as Array<T>)
}
override fun copy(): RegistrySectionDataProvider<T> {
acquire()
val clone = RegistrySectionDataProvider(registry, data.clone())
release()
return clone
}
}

View File

@ -0,0 +1,112 @@
/*
* Minosoft
* Copyright (C) 2021 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
import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition
open class SectionDataProvider<T>(
data: Array<Any?> = arrayOfNulls(ProtocolDefinition.BLOCKS_PER_SECTION),
) : Iterable<T> {
protected var data = data
private set
protected val lock = SemaphoreLock() // lock while reading (blocks writing)
var count: Int = 0
private set
init {
recalculateCount()
}
@Suppress("UNCHECKED_CAST")
operator fun get(index: Int): T {
lock.acquire()
val value = data[index] as T
lock.release()
return value
}
private fun recalculateCount() {
var count = 0
for (value in data) {
if (value == null) {
continue
}
count++
}
this.count = count
}
operator fun get(x: Int, y: Int, z: Int): T {
return get(y shl 8 or (z shl 4) or x)
}
operator fun set(x: Int, y: Int, z: Int, value: T) {
set(y shl 8 or (z shl 4) or x, value)
}
operator fun set(index: Int, value: T) {
lock()
val previous = data[index]
if (value == null) {
if (previous == null) {
return
}
count--
} else if (previous == null) {
count++
}
data[index] = value
unlock()
}
fun acquire() {
lock.acquire()
}
fun release() {
lock.release()
}
fun lock() {
lock.lock()
}
fun unlock() {
lock.unlock()
}
@Suppress("UNCHECKED_CAST")
@Synchronized
fun setData(data: Array<T>) {
lock()
check(data.size == ProtocolDefinition.BLOCKS_PER_SECTION) { "Size does not match!" }
this.data = data as Array<Any?>
recalculateCount()
unlock()
}
open fun copy(): SectionDataProvider<T> {
acquire()
val clone = SectionDataProvider<T>(data.clone())
release()
return clone
}
@Suppress("UNCHECKED_CAST")
override fun iterator(): Iterator<T> {
return data.iterator() as Iterator<T>
}
}

View File

@ -0,0 +1,36 @@
/*
* Minosoft
* Copyright (C) 2021 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
import java.util.concurrent.Semaphore
class SemaphoreLock {
private val semaphore = Semaphore(Int.MAX_VALUE)
fun acquire() {
semaphore.acquire()
}
fun release() {
semaphore.release()
}
fun lock() {
semaphore.acquire(Int.MAX_VALUE)
}
fun unlock() {
semaphore.release(Int.MAX_VALUE)
}
}

View File

@ -43,8 +43,6 @@ object RenderConstants {
const val TEXT_LINE_PADDING = 2
val WORD_SEPARATORS = arrayOf(' ', '.', ',', '!', '-', '?')
const val CHUNK_SECTIONS_PER_MESH = 1
const val FRUSTUM_CULLING_ENABLED = true
const val SHOW_FPS_IN_WINDOW_TITLE = true

View File

@ -29,7 +29,10 @@ import de.bixilon.minosoft.data.world.Chunk
import de.bixilon.minosoft.data.world.ChunkSection
import de.bixilon.minosoft.data.world.ChunkSection.Companion.indexPosition
import de.bixilon.minosoft.data.world.World
import de.bixilon.minosoft.gui.rendering.*
import de.bixilon.minosoft.gui.rendering.RenderWindow
import de.bixilon.minosoft.gui.rendering.Renderer
import de.bixilon.minosoft.gui.rendering.RendererBuilder
import de.bixilon.minosoft.gui.rendering.RenderingStates
import de.bixilon.minosoft.gui.rendering.block.mesh.ChunkSectionMeshCollection
import de.bixilon.minosoft.gui.rendering.block.renderable.BlockLikeRenderContext
import de.bixilon.minosoft.gui.rendering.input.camera.Frustum
@ -171,7 +174,7 @@ class WorldRenderer(
connection.registerEvent(CallbackEventInvoker.of<ChunkUnloadEvent> { unloadChunk(it.chunkPosition) })
connection.registerEvent(CallbackEventInvoker.of<ChunkDataChangeEvent> { prepareChunk(it.chunkPosition) })
connection.registerEvent(CallbackEventInvoker.of<ChunkDataChangeEvent> { prepareChunk(it.chunkPosition, it.chunk) })
connection.registerEvent(CallbackEventInvoker.of<BlockSetEvent> { prepareChunkSection(it.blockPosition.chunkPosition, it.blockPosition.sectionHeight) })
@ -297,7 +300,9 @@ class WorldRenderer(
var currentChunks: MutableMap<Int, ChunkSection> = synchronizedMapOf()
var currentIndex = 0
for ((sectionHeight, section) in chunk.sections!!) {
for ((index, section) in chunk.withIndex()) {
section ?: continue
val sectionHeight = index - chunk.lowestSection
if (sectionHeight.sectionIndex != currentIndex) {
prepareChunkSections(chunkPosition, currentChunks)
currentChunks = synchronizedMapOf()
@ -419,9 +424,8 @@ class WorldRenderer(
private fun prepareChunkSection(chunkPosition: Vec2i, sectionHeight: Int) {
val sections: MutableMap<Int, ChunkSection> = synchronizedMapOf()
val chunk = world[chunkPosition]!!
val lowestSectionHeight = sectionHeight.sectionIndex * RenderConstants.CHUNK_SECTIONS_PER_MESH
for (i in lowestSectionHeight until lowestSectionHeight + RenderConstants.CHUNK_SECTIONS_PER_MESH) {
sections[i] = chunk.sections?.get(i) ?: continue
for ((index, section) in chunk.withIndex()) {
sections[index - chunk.lowestSection] = section ?: continue
}
prepareChunkSections(chunkPosition, sections)
}
@ -508,13 +512,7 @@ class WorldRenderer(
}
val Int.sectionIndex: Int
get() {
val divided = this / RenderConstants.CHUNK_SECTIONS_PER_MESH
if (this < 0) {
return divided - 1
}
return divided
}
get() = this
private operator fun Int.plus(upOrDown: Directions): Int {
return this + upOrDown.vector.y

View File

@ -241,15 +241,16 @@ object VecUtil {
return Vec3i(inVec2i.x, y, inVec2i.z)
}
val Vec3i.sectionHeight: Int
get() {
return if (y < 0) {
(y + 1) / ProtocolDefinition.SECTION_HEIGHT_Y - 1
} else {
y / ProtocolDefinition.SECTION_HEIGHT_Y
}
val Int.sectionHeight: Int
get() = if (this < 0) {
(this + 1) / ProtocolDefinition.SECTION_HEIGHT_Y - 1
} else {
this / ProtocolDefinition.SECTION_HEIGHT_Y
}
val Vec3i.sectionHeight: Int
get() = y.sectionHeight
val Vec3i.entityPosition: Vec3d
get() = Vec3d(x + 0.5f, y, z + 0.5f) // ToDo: Confirm

View File

@ -158,7 +158,7 @@ class PlayConnection(
})
worldTickTask = TimeWorker.addTask(TimeWorkerTask(ProtocolDefinition.TICK_TIME, maxDelayTime = ProtocolDefinition.TICK_TIME / 2) {
world.realTick()
world.tick()
})
randomTickTask = TimeWorker.addTask(TimeWorkerTask(ProtocolDefinition.TICK_TIME, maxDelayTime = ProtocolDefinition.TICK_TIME / 2) {

View File

@ -45,7 +45,7 @@ class BlockActionS2CP(buffer: PlayInByteBuffer) : PlayS2CPacket() {
return
}
val blockEntity = factory.build(connection)
connection.world[position] = blockEntity
connection.world.setBlockEntity(position, blockEntity)
blockEntity
}

View File

@ -37,7 +37,7 @@ class BlockEntityMetaDataS2CP(buffer: PlayInByteBuffer) : PlayS2CPacket() {
connection.world.getBlockEntity(position)?.updateNBT(nbt) ?: let {
val blockEntity = DefaultBlockEntityMetaDataFactory.buildBlockEntity(DefaultBlockEntityMetaDataFactory[type]!!, connection)
blockEntity.updateNBT(nbt)
connection.world[position] = blockEntity
connection.world.setBlockEntity(position, blockEntity)
}
connection.fireEvent(BlockEntityMetaDataChangeEvent(connection, this))
}

View File

@ -15,10 +15,11 @@ package de.bixilon.minosoft.protocol.packets.s2c.play
import de.bixilon.minosoft.Minosoft
import de.bixilon.minosoft.data.entities.block.BlockEntity
import de.bixilon.minosoft.data.registries.ResourceLocation
import de.bixilon.minosoft.data.registries.tweaker.VersionTweaker
import de.bixilon.minosoft.data.world.ChunkData
import de.bixilon.minosoft.data.world.biome.source.SpatialBiomeArray
import de.bixilon.minosoft.datafixer.BlockEntityFixer.fix
import de.bixilon.minosoft.gui.rendering.util.VecUtil.EMPTY
import de.bixilon.minosoft.gui.rendering.util.VecUtil.of
import de.bixilon.minosoft.modding.event.EventInitiators
import de.bixilon.minosoft.modding.event.events.ChunkDataChangeEvent
import de.bixilon.minosoft.modding.event.events.ChunkUnloadEvent
@ -106,10 +107,11 @@ class ChunkDataS2CP(buffer: PlayInByteBuffer) : PlayS2CPacket() {
buffer.pointer = size + lastPos
}
if (buffer.versionId >= ProtocolVersions.V_1_9_4) {
val positionOffset = Vec3i.of(chunkPosition, dimension.lowestSection, Vec3i.EMPTY)
val blockEntitiesCount = buffer.readVarInt()
for (i in 0 until blockEntitiesCount) {
val nbt = buffer.readNBT().asCompound()
val position = Vec3i(nbt["x"]!!.toInt(), nbt["y"]!!.toInt(), nbt["z"]!!.toInt())
val position = Vec3i(nbt["x"]!!.toInt(), nbt["y"]!!.toInt(), nbt["z"]!!.toInt()) - positionOffset
val resourceLocation = ResourceLocation(nbt["id"].unsafeCast()).fix()
val type = buffer.connection.registries.blockEntityTypeRegistry[resourceLocation] ?: let {
Log.log(LogMessageType.NETWORK_PACKETS_IN, level = LogLevels.WARN) { "Unknown block entity: $resourceLocation" }
@ -124,13 +126,12 @@ class ChunkDataS2CP(buffer: PlayInByteBuffer) : PlayS2CPacket() {
}
override fun handle(connection: PlayConnection) {
chunkData?.blocks?.let {
VersionTweaker.transformSections(it, connection.version.versionId)
}
chunkData?.let {
val chunk = connection.world.getOrCreateChunk(chunkPosition)
chunk.setData(chunkData!!)
connection.world.setBlockEntities(blockEntities)
for ((position, blockEntity) in blockEntities) {
chunk.setBlockEntity(position, blockEntity)
}
connection.fireEvent(ChunkDataChangeEvent(connection, this))
} ?: let {
connection.world.unloadChunk(chunkPosition)

View File

@ -14,9 +14,6 @@ package de.bixilon.minosoft.protocol.packets.s2c.play
import de.bixilon.minosoft.data.registries.blocks.BlockState
import de.bixilon.minosoft.data.registries.tweaker.VersionTweaker
import de.bixilon.minosoft.gui.rendering.util.VecUtil.inChunkSectionPosition
import de.bixilon.minosoft.gui.rendering.util.VecUtil.sectionHeight
import de.bixilon.minosoft.modding.event.events.MassBlockSetEvent
import de.bixilon.minosoft.protocol.network.connection.play.PlayConnection
import de.bixilon.minosoft.protocol.packets.s2c.PlayS2CPacket
@ -81,21 +78,11 @@ class MassBlockSetS2CP(buffer: PlayInByteBuffer) : PlayS2CPacket() {
override fun handle(connection: PlayConnection) {
val chunk = connection.world[chunkPosition] ?: return // thanks mojang
if (chunk.sections == null) {
if (!chunk.blocksInitialized) {
return
}
chunk.setBlocks(blocks)
// tweak
if (!connection.version.isFlattened()) {
for ((key, value) in blocks) {
val block = VersionTweaker.transformBlock(value, chunk.sections!!, key.inChunkSectionPosition, key.sectionHeight)
if (block === value) {
continue
}
chunk[key] = block
}
}
connection.fireEvent(MassBlockSetEvent(connection, this))
}

View File

@ -13,10 +13,8 @@
package de.bixilon.minosoft.protocol.packets.s2c.play
import de.bixilon.minosoft.Minosoft
import de.bixilon.minosoft.data.registries.tweaker.VersionTweaker
import de.bixilon.minosoft.data.world.ChunkData
import de.bixilon.minosoft.modding.event.EventInitiators
import de.bixilon.minosoft.modding.event.events.ChunkDataChangeEvent
import de.bixilon.minosoft.modding.event.events.ChunkUnloadEvent
import de.bixilon.minosoft.protocol.network.connection.play.PlayConnection
@ -73,10 +71,6 @@ class MassChunkDataS2CP() : PlayS2CPacket() {
override fun handle(connection: PlayConnection) {
// transform data
for ((chunkPosition, data) in data) {
data?.blocks?.let {
VersionTweaker.transformSections(it, connection.version.versionId)
}
data?.let {
val chunk = connection.world.getOrCreateChunk(chunkPosition)
chunk.setData(data)

View File

@ -26,7 +26,7 @@ import de.bixilon.minosoft.util.logging.LogMessageType
import glm_.vec3.Vec3i
class SignTextSetS2CP(buffer: PlayInByteBuffer) : PlayS2CPacket() {
val signPosition: Vec3i = if (buffer.versionId < ProtocolVersions.V_14W04A) {
val position: Vec3i = if (buffer.versionId < ProtocolVersions.V_14W04A) {
buffer.readShortBlockPosition()
} else {
buffer.readBlockPosition()
@ -43,16 +43,14 @@ class SignTextSetS2CP(buffer: PlayInByteBuffer) : PlayS2CPacket() {
}
override fun handle(connection: PlayConnection) {
val signBlockEntity = connection.world.getBlockEntity(signPosition)?.unsafeCast<SignBlockEntity>() ?: let {
val blockEntity = SignBlockEntity(connection)
connection.world[signPosition] = blockEntity
blockEntity
val signBlockEntity = connection.world.getBlockEntity(position)?.unsafeCast<SignBlockEntity>() ?: SignBlockEntity(connection).apply {
connection.world.setBlockEntity(position, this)
}
signBlockEntity.lines = lines
}
override fun log() {
Log.log(LogMessageType.NETWORK_PACKETS_IN, level = LogLevels.VERBOSE) { "Sign text set (position=$signPosition, lines=$lines" }
Log.log(LogMessageType.NETWORK_PACKETS_IN, level = LogLevels.VERBOSE) { "Sign text set (position=$position, lines=$lines" }
}
}

View File

@ -52,6 +52,7 @@ import de.bixilon.minosoft.protocol.packets.s2c.status.StatusPongS2CP
class PacketTypes {
@Suppress("UNUSED")
enum class C2S(val clazz: Class<out C2SPacket>? = null) {
HANDSHAKING_HANDSHAKE(HandshakeC2SP::class.java),
STATUS_PING(StatusPingC2SP::class.java),
@ -145,6 +146,7 @@ class PacketTypes {
}
@Suppress("UNUSED")
enum class S2C(
val playFactory: ((buffer: PlayInByteBuffer) -> PlayS2CPacket)? = null,
val statusFactory: ((buffer: InByteBuffer) -> StatusS2CPacket)? = null,

View File

@ -24,7 +24,6 @@ import de.bixilon.minosoft.data.world.palette.Palette.Companion.choosePalette
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.synchronizedMapOf
import java.util.*
@ -59,7 +58,7 @@ object ChunkUtil {
// parse data
var arrayPosition = 0
val sectionMap: MutableMap<Int, ChunkSection> = synchronizedMapOf()
val sections: Array<ChunkSection?> = arrayOfNulls(dimension.sections)
for ((sectionIndex, sectionHeight) in (dimension.lowestSection until dimension.highestSection).withIndex()) {
if (!sectionBitMask[sectionIndex]) {
continue
@ -91,9 +90,9 @@ object ChunkUtil {
blocks[blockNumber] = buffer.connection.registries.blockStateRegistry[blockId] ?: continue
}
sectionMap[sectionHeight] = ChunkSection(blocks)
sections[sectionHeight - dimension.lowestSection] = ChunkSection(buffer.connection.registries, blocks)
}
chunkData.blocks = sectionMap
chunkData.blocks = sections
return chunkData
}
@ -125,7 +124,7 @@ object ChunkUtil {
}
var arrayPos = 0
val sectionMap: MutableMap<Int, ChunkSection> = synchronizedMapOf()
val sections: Array<ChunkSection?> = arrayOfNulls(dimension.sections)
for ((sectionIndex, sectionHeight) in (dimension.lowestSection until dimension.highestSection).withIndex()) { // max sections per chunks in chunk column
if (!sectionBitMask[sectionIndex]) {
continue
@ -136,15 +135,15 @@ object ChunkUtil {
val block = buffer.connection.registries.blockStateRegistry[blockId] ?: continue
blocks[blockNumber] = block
}
sectionMap[sectionHeight] = ChunkSection(blocks)
sections[sectionHeight] = ChunkSection(buffer.connection.registries, blocks)
}
chunkData.blocks = sectionMap
chunkData.blocks = sections
return chunkData
}
fun readPaletteChunk(buffer: PlayInByteBuffer, dimension: DimensionProperties, sectionBitMask: BitSet, isFullChunk: Boolean, containsSkyLight: Boolean = false): ChunkData {
val chunkData = ChunkData()
val sectionMap: MutableMap<Int, ChunkSection> = synchronizedMapOf()
val sections: Array<ChunkSection?> = arrayOfNulls(dimension.sections)
for ((sectionIndex, sectionHeight) in (dimension.lowestSection until sectionBitMask.length()).withIndex()) { // max sections per chunks in chunk column
if (!sectionBitMask[sectionIndex]) {
@ -193,10 +192,10 @@ object ChunkUtil {
// ToDo
chunkData.lightAccessor = DummyLightAccessor
}
sectionMap[sectionHeight] = ChunkSection(blocks)
sections[sectionHeight - dimension.lowestSection] = ChunkSection(buffer.connection.registries, blocks)
}
chunkData.blocks = sectionMap
chunkData.blocks = sections
if (buffer.versionId < V_19W36A && isFullChunk) {
chunkData.biomeSource = readLegacyBiomeArray(buffer)
}