Skip to content

Commit

Permalink
Refactor: Simplified the chunk code
Browse files Browse the repository at this point in the history
  • Loading branch information
Martomate committed Dec 30, 2023
1 parent 6aef36e commit 74237f6
Show file tree
Hide file tree
Showing 5 changed files with 41 additions and 65 deletions.
16 changes: 14 additions & 2 deletions game/src/main/scala/hexacraft/world/World.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import hexacraft.world.entity.Entity

import com.martomate.nbt.Nbt

import scala.annotation.tailrec
import scala.collection.mutable

object World:
Expand Down Expand Up @@ -85,7 +86,7 @@ class World(worldProvider: WorldProvider, worldInfo: WorldInfo) extends BlockRep
def removeBlock(coords: BlockRelWorld): Unit =
getChunk(coords.getChunkRelWorld) match
case Some(chunk) =>
chunk.removeBlock(coords.getBlockRelChunk)
chunk.setBlock(coords.getBlockRelChunk, BlockState.Air)
onSetBlock(coords, BlockState.Air)
case None =>

Expand Down Expand Up @@ -163,7 +164,18 @@ class World(worldProvider: WorldProvider, worldInfo: WorldInfo) extends BlockRep
if blockUpdateTimer.tick() then performBlockUpdates()
if relocateEntitiesTimer.tick() then performEntityRelocation()

for col <- columns.values do col.tick(this, collisionDetector)
for
col <- columns.values
ch <- col.allChunks
do
ch.optimizeStorage()
tickEntities(ch.entities)

@tailrec // this is done for performance
def tickEntities(ents: Iterable[Entity]): Unit =
if ents.nonEmpty then
ents.head.tick(this, collisionDetector)
tickEntities(ents.tail)

private val blockUpdateTimer: TickableTimer = TickableTimer(World.ticksBetweenBlockUpdates)

Expand Down
3 changes: 1 addition & 2 deletions game/src/main/scala/hexacraft/world/WorldGenerator.scala
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,7 @@ class WorldGenerator(worldGenSettings: WorldGenSettings)(using CylinderSize) {
storage.setBlock(BlockRelChunk(i, j, k), new BlockState(getBlockAtDepth(yToGo)))
}

val data = ChunkData.fromStorage(storage)
data
ChunkData.fromStorage(storage)

private def getBlockAtDepth(yToGo: Int) = {
if (yToGo < -5) Block.Stone
Expand Down
77 changes: 24 additions & 53 deletions game/src/main/scala/hexacraft/world/chunk/chunk.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import hexacraft.world.entity.{Entity, EntityFactory}

import com.martomate.nbt.Nbt

import scala.annotation.tailrec
import scala.collection.mutable

object Chunk:
Expand All @@ -30,51 +29,41 @@ class Chunk private (val coords: ChunkRelWorld, chunkData: ChunkData)(using Cyli
lighting.setInitialized()
lightPropagator.initBrightnesses(this)

def entities: collection.Seq[Entity] = chunkData.allEntities
def entities: collection.Seq[Entity] = chunkData.entities

def addEntity(entity: Entity): Unit =
chunkData.addEntity(entity)
chunkData.entities += entity
_modCount += 1

def removeEntity(entity: Entity): Unit =
chunkData.removeEntity(entity)
chunkData.entities -= entity
_modCount += 1

def blocks: Array[LocalBlockState] = chunkData.allBlocks
def blocks: Array[LocalBlockState] = chunkData.storage.allBlocks

def getBlock(coords: BlockRelChunk): BlockState = chunkData.getBlock(coords)
def getBlock(coords: BlockRelChunk): BlockState = chunkData.storage.getBlock(coords)

def setBlock(blockCoords: BlockRelChunk, block: BlockState): Unit =
val before = getBlock(blockCoords)
if before != block then
chunkData.setBlock(blockCoords, block)
chunkData.storage.setBlock(blockCoords, block)
_modCount += 1

def removeBlock(coords: BlockRelChunk): Unit = setBlock(coords, BlockState.Air)

def tick(world: BlocksInWorld, collisionDetector: CollisionDetector): Unit =
chunkData.optimizeStorage()

tickEntities(entities)

@tailrec
def tickEntities(ents: Iterable[Entity]): Unit =
if ents.nonEmpty then
ents.head.tick(world, collisionDetector)
tickEntities(ents.tail)
def optimizeStorage(): Unit = chunkData.optimizeStorage()

def toNbt: Nbt.MapTag = chunkData.toNBT

def isDecorated: Boolean = chunkData.isDecorated
def setDecorated(): Unit =
if !chunkData.isDecorated then
chunkData.setDecorated()
chunkData.isDecorated = true
_modCount += 1

class ChunkData(private var storage: ChunkStorage, entities: mutable.ArrayBuffer[Entity])(using CylinderSize):
private var _isDecorated: Boolean = false
def isDecorated: Boolean = _isDecorated

class ChunkData(
private[chunk] var storage: ChunkStorage,
private[chunk] val entities: mutable.ArrayBuffer[Entity],
private[chunk] var isDecorated: Boolean
):
def optimizeStorage(): Unit =
if storage.isDense then {
if storage.numBlocks < 32
Expand All @@ -84,29 +73,19 @@ class ChunkData(private var storage: ChunkStorage, entities: mutable.ArrayBuffer
then storage = DenseChunkStorage.fromStorage(storage)
}

def getBlock(coords: BlockRelChunk): BlockState = storage.getBlock(coords)
def setBlock(coords: BlockRelChunk, block: BlockState): Unit = storage.setBlock(coords, block)
def allBlocks: Array[LocalBlockState] = storage.allBlocks

def addEntity(entity: Entity): Unit = entities += entity
def removeEntity(entity: Entity): Unit = entities -= entity
def allEntities: collection.Seq[Entity] = entities

def setDecorated(): Unit = _isDecorated = true

def toNBT: Nbt.MapTag =
val storageNbt = storage.toNBT

Nbt.makeMap(
"blocks" -> Nbt.ByteArrayTag(storageNbt.blocks),
"metadata" -> Nbt.ByteArrayTag(storageNbt.metadata),
"entities" -> Nbt.ListTag(allEntities.map(e => e.toNBT).toSeq),
"isDecorated" -> Nbt.ByteTag(_isDecorated)
"entities" -> Nbt.ListTag(entities.map(e => e.toNBT).toSeq),
"isDecorated" -> Nbt.ByteTag(isDecorated)
)

object ChunkData:
def fromStorage(storage: ChunkStorage)(using CylinderSize): ChunkData =
new ChunkData(storage, mutable.ArrayBuffer.empty)
def fromStorage(storage: ChunkStorage): ChunkData =
new ChunkData(storage, mutable.ArrayBuffer.empty, false)

def fromNBT(nbt: Nbt.MapTag)(using CylinderSize): ChunkData =
val storage = nbt.getByteArray("blocks") match
Expand All @@ -116,26 +95,18 @@ object ChunkData:
case None =>
SparseChunkStorage.empty

val entities =
nbt.getList("entities") match
case Some(tags) => entitiesFromNbt(tags.map(_.asInstanceOf[Nbt.MapTag]))
case None => Nil

val data = new ChunkData(storage, mutable.ArrayBuffer.from(entities))
data._isDecorated = nbt.getBoolean("isDecorated", default = false)
data

private def entitiesFromNbt(list: Seq[Nbt.MapTag])(using
CylinderSize
): Iterable[Entity] =
val entities = mutable.ArrayBuffer.empty[Entity]

for tag <- list do
for
tags <- nbt.getList("entities")
tag <- tags.map(_.asInstanceOf[Nbt.MapTag])
do
EntityFactory.fromNbt(tag) match
case Ok(entity) => entities += entity
case Err(message) => println(message)

entities
val isDecorated = nbt.getBoolean("isDecorated", default = false)

new ChunkData(storage, entities, isDecorated)

class ChunkLighting:
private val brightness: SmartArray[Byte] = SmartArray.withByteArray(16 * 16 * 16, 0)
Expand Down
3 changes: 0 additions & 3 deletions game/src/main/scala/hexacraft/world/chunk/column.scala
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,4 @@ class ChunkColumn private (
case Some(h) => heightMap(x)(z) = h.toShort
case None =>

def tick(world: BlocksInWorld, collisionDetector: CollisionDetector): Unit =
chunks.foreachValue(_.tick(world, collisionDetector))

def toNBT: Nbt.MapTag = ChunkColumnData(Some(heightMap)).toNBT
7 changes: 2 additions & 5 deletions game/src/main/scala/hexacraft/world/chunk/storage.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package hexacraft.world.chunk

import hexacraft.util.SmartArray
import hexacraft.world.CylinderSize
import hexacraft.world.block.{Block, BlockState}
import hexacraft.world.coord.BlockRelChunk

Expand Down Expand Up @@ -72,9 +71,7 @@ object DenseChunkStorage:
for LocalBlockState(i, b) <- storage.allBlocks do result.setBlock(i, b)
result

def fromNBT(blocks: Array[Byte], metadata: Option[Array[Byte]])(using
CylinderSize
): DenseChunkStorage =
def fromNBT(blocks: Array[Byte], metadata: Option[Array[Byte]]): DenseChunkStorage =
val storage = new DenseChunkStorage

val meta = metadata.map(_.apply).getOrElse(_ => 0)
Expand Down Expand Up @@ -114,7 +111,7 @@ class SparseChunkStorage extends ChunkStorage:
ChunkStorage.NbtData(blocks = ids, metadata = meta)

object SparseChunkStorage:
def empty(using CylinderSize): ChunkStorage = new SparseChunkStorage
def empty: ChunkStorage = new SparseChunkStorage

def fromStorage(storage: ChunkStorage): SparseChunkStorage =
val result = new SparseChunkStorage
Expand Down

0 comments on commit 74237f6

Please sign in to comment.