• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Martomate / Hexacraft / 7351367576

28 Dec 2023 06:50PM UTC coverage: 51.185% (-0.1%) from 51.312%
7351367576

push

github

Martomate
Refactor: Made Chunk not know if it has been saved to file

9 of 12 new or added lines in 2 files covered. (75.0%)

110 existing lines in 32 files now uncovered.

2829 of 5527 relevant lines covered (51.19%)

0.51 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

75.68
/game/src/main/scala/hexacraft/world/World.scala
1
package hexacraft.world
2

3
import hexacraft.util.*
4
import hexacraft.world.block.{BlockRepository, BlockState}
5
import hexacraft.world.chunk.*
6
import hexacraft.world.coord.*
7
import hexacraft.world.entity.{Entity, EntityModelLoader, EntityRegistry, PlayerFactory, SheepFactory}
8

9
import scala.collection.mutable
10

11
object World:
12
  private val ticksBetweenBlockUpdates = 5
13
  private val ticksBetweenEntityRelocation = 120
14

15
  var shouldChillChunkLoader = false
16

1✔
17
  def apply(provider: WorldProvider, worldInfo: WorldInfo, modelLoader: EntityModelLoader): World =
1✔
18
    new World(provider, worldInfo, makeEntityRegistry(modelLoader))
19

1✔
UNCOV
20
  private def makeEntityRegistry(modelLoader: EntityModelLoader): EntityRegistry =
×
UNCOV
21
    val entityTypes = Map(
×
22
      "player" -> new PlayerFactory(() => modelLoader.load("player")),
1✔
23
      "sheep" -> new SheepFactory(() => modelLoader.load("sheep"))
24
    )
1✔
25
    EntityRegistry.from(entityTypes)
26

27
  enum Event:
28
    case ChunkAdded(chunk: Chunk)
29
    case ChunkRemoved(coords: ChunkRelWorld)
30
    case ChunkNeedsRenderUpdate(coords: ChunkRelWorld)
31
    case BlockReplaced(coords: BlockRelWorld, prev: BlockState, now: BlockState)
32

33
class World(worldProvider: WorldProvider, worldInfo: WorldInfo, val entityRegistry: EntityRegistry)
34
    extends BlockRepository
35
    with BlocksInWorld:
36
  given size: CylinderSize = worldInfo.worldSize
37

1✔
38
  private val worldGenerator = new WorldGenerator(worldInfo.gen)
1✔
39
  private val worldPlanner: WorldPlanner = WorldPlanner(this, entityRegistry, worldInfo.gen.seed)
1✔
40
  private val lightPropagator: LightPropagator = new LightPropagator(this)
41

42
  val renderDistance: Double = 8 * CylinderSize.y60
43

1✔
44
  val collisionDetector: CollisionDetector = new CollisionDetector(this)
45

1✔
46
  private val columns = mutable.LongMap.empty[ChunkColumn]
47

1✔
48
  private val chunkLoader: ChunkLoader = makeChunkLoader()
49

1✔
50
  private val blocksToUpdate: UniqueQueue[BlockRelWorld] = new UniqueQueue
51

1✔
52
  private val savedChunkModCounts = mutable.Map.empty[ChunkRelWorld, Long]
1✔
53
  private val chunkEventTrackerRevokeFns = mutable.Map.empty[ChunkRelWorld, RevokeTrackerFn]
54

1✔
55
  private val dispatcher = new EventDispatcher[World.Event]
1✔
56
  def trackEvents(tracker: Tracker[World.Event]): Unit = dispatcher.track(tracker)
57

1✔
58
  trackEvents(worldPlanner.onWorldEvent _)
1✔
59
  trackEvents(chunkLoader.onWorldEvent _)
60

1✔
61
  private def makeChunkLoader(): ChunkLoader =
62
    val chunkFactory = (coords: ChunkRelWorld) =>
1✔
63
      val loadedTag = worldProvider.loadChunkData(coords)
64
      val (chunk, isNew) =
1✔
65
        if loadedTag.vs.isEmpty
1✔
NEW
66
        then (Chunk.fromGenerator(coords, this, worldGenerator), true)
×
NEW
67
        else (Chunk.fromNbt(coords, loadedTag, entityRegistry), false)
×
68
      savedChunkModCounts(coords) = if isNew then -1L else chunk.modCount
69
      chunk
70

71
    val chunkUnloader = (coords: ChunkRelWorld) =>
×
72
      for chunk <- getChunk(coords) do
1✔
NEW
73
        if chunk.modCount != savedChunkModCounts.getOrElse(coords, -1L) then
×
74
          worldProvider.saveChunkData(chunk.toNbt, chunk.coords)
1✔
75
        savedChunkModCounts -= coords
76

1✔
77
    new ChunkLoader(chunkFactory, chunkUnloader, renderDistance)
UNCOV
78

×
79
  def getColumn(coords: ColumnRelWorld): Option[ChunkColumnTerrain] = columns.get(coords.value)
80

1✔
UNCOV
81
  def getChunk(coords: ChunkRelWorld): Option[Chunk] =
×
82
    columns.get(coords.getColumnRelWorld.value).flatMap(_.getChunk(coords.Y))
83

1✔
84
  def getBlock(coords: BlockRelWorld): BlockState =
1✔
85
    getChunk(coords.getChunkRelWorld) match
1✔
UNCOV
86
      case Some(chunk) => chunk.getBlock(coords.getBlockRelChunk)
×
87
      case None        => BlockState.Air
88

1✔
89
  def provideColumn(coords: ColumnRelWorld): ChunkColumnTerrain = ensureColumnExists(coords)
90

1✔
91
  def setBlock(coords: BlockRelWorld, block: BlockState): Unit =
1✔
92
    getChunk(coords.getChunkRelWorld) match
1✔
93
      case Some(chunk) => chunk.setBlock(coords.getBlockRelChunk, block)
×
94
      case None        =>
95

×
96
  def removeBlock(coords: BlockRelWorld): Unit =
×
UNCOV
97
    getChunk(coords.getChunkRelWorld) match
×
UNCOV
98
      case Some(chunk) => chunk.removeBlock(coords.getBlockRelChunk)
×
99
      case None        =>
100

1✔
101
  def addEntity(entity: Entity): Unit =
1✔
102
    chunkOfEntity(entity) match
1✔
UNCOV
103
      case Some(chunk) => chunk.addEntity(entity)
×
104
      case None        =>
105

1✔
106
  def removeEntity(entity: Entity): Unit =
1✔
107
    chunkOfEntity(entity) match
1✔
UNCOV
108
      case Some(chunk) => chunk.removeEntity(entity)
×
109
      case None        =>
110

1✔
111
  def removeAllEntities(): Unit =
1✔
112
    for
×
113
      col <- columns.values
1✔
UNCOV
114
      ch <- col.allChunks
×
115
      e <- ch.entities.toSeq
1✔
116
    do ch.removeEntity(e)
117

1✔
118
  private def chunkOfEntity(entity: Entity): Option[Chunk] =
1✔
119
    getChunk(CoordUtils.approximateChunkCoords(entity.position))
120

×
UNCOV
121
  def getHeight(x: Int, z: Int): Int =
×
UNCOV
122
    val coords = ColumnRelWorld(x >> 4, z >> 4)
×
123
    ensureColumnExists(coords).terrainHeight(x & 15, z & 15)
124

1✔
125
  def setChunk(ch: Chunk): Unit =
1✔
126
    ensureColumnExists(ch.coords.getColumnRelWorld).setChunk(ch)
127

1✔
128
    val revoke = ch.trackEvents(onChunkEvent _)
1✔
129
    chunkEventTrackerRevokeFns += ch.coords -> revoke
130

1✔
131
    dispatcher.notify(World.Event.ChunkAdded(ch))
132

1✔
133
    ch.requestRenderUpdate()
1✔
134
    requestRenderUpdateForNeighborChunks(ch.coords)
135

1✔
136
    worldPlanner.decorate(ch)
1✔
137
    if ch.modCount != savedChunkModCounts.getOrElse(ch.coords, -1L) then
1✔
138
      worldProvider.saveChunkData(ch.toNbt, ch.coords)
1✔
139
      savedChunkModCounts(ch.coords) = ch.modCount
140

1✔
141
    for block <- ch.blocks do
1✔
142
      requestBlockUpdate(BlockRelWorld.fromChunk(block.coords, ch.coords))
143

1✔
144
      for side <- 0 until 8 do
1✔
145
        if block.coords.isOnChunkEdge(side) then
1✔
UNCOV
146
          val neighCoords = block.coords.neighbor(side)
×
147
          for neighbor <- getChunk(ch.coords.offset(ChunkRelWorld.neighborOffsets(side))) do
1✔
148
            requestBlockUpdate(BlockRelWorld.fromChunk(neighCoords, neighbor.coords))
149

1✔
150
  def removeChunk(ch: ChunkRelWorld): Unit =
×
151
    for col <- columns.get(ch.getColumnRelWorld.value) do
1✔
152
      for removedChunk <- col.removeChunk(ch.Y) do
1✔
153
        chunkEventTrackerRevokeFns.remove(removedChunk.coords) match
1✔
UNCOV
154
          case Some(revoke) => revoke()
×
155
          case None         =>
156

1✔
157
        dispatcher.notify(World.Event.ChunkRemoved(removedChunk.coords))
158

1✔
159
        if removedChunk.modCount != savedChunkModCounts.getOrElse(removedChunk.coords, -1L) then
1✔
160
          worldProvider.saveChunkData(removedChunk.toNbt, removedChunk.coords)
1✔
161
          savedChunkModCounts -= removedChunk.coords
1✔
162
        requestRenderUpdateForNeighborChunks(ch)
163

1✔
164
      if col.isEmpty then
1✔
165
        columns.remove(col.coords.value)
1✔
166
        worldProvider.saveColumnData(col.toNBT, col.coords)
167

1✔
168
  def tick(camera: Camera): Unit =
1✔
169
    val (chunksToAdd, chunksToRemove) = chunkLoader.tick(
1✔
170
      PosAndDir.fromCameraView(camera.view),
171
      World.shouldChillChunkLoader
172
    )
173

1✔
174
    for ch <- chunksToAdd do setChunk(ch)
1✔
175
    for ch <- chunksToRemove do removeChunk(ch)
176

1✔
177
    if blockUpdateTimer.tick() then performBlockUpdates()
1✔
178
    if relocateEntitiesTimer.tick() then performEntityRelocation()
UNCOV
179

×
180
    for col <- columns.values do col.tick(this, collisionDetector)
181

1✔
182
  private val blockUpdateTimer: TickableTimer = TickableTimer(World.ticksBetweenBlockUpdates)
183

1✔
184
  private def performBlockUpdates(): Unit =
1✔
UNCOV
185
    val blocksToUpdateLen = blocksToUpdate.size
×
186
    for _ <- 0 until blocksToUpdateLen do
1✔
187
      val c = blocksToUpdate.dequeue()
1✔
188
      val block = getBlock(c).blockType
1✔
189
      block.behaviour.foreach(_.onUpdated(c, block, this))
190

1✔
191
  private val relocateEntitiesTimer: TickableTimer = TickableTimer(World.ticksBetweenEntityRelocation)
192

1✔
193
  private def performEntityRelocation(): Unit =
1✔
UNCOV
194
    val entList = for
×
UNCOV
195
      col <- columns.values
×
196
      ch <- col.allChunks
×
197
      ent <- ch.entities
×
198
    yield (ch, ent, chunkOfEntity(ent))
199

1✔
200
    for
×
201
      (ch, ent, newOpt) <- entList
×
202
      newChunk <- newOpt
203
      if newChunk != ch
UNCOV
204
    do
×
205
      ch.removeEntity(ent)
×
206
      newChunk.addEntity(ent)
207

1✔
208
  private def requestRenderUpdateForNeighborChunks(coords: ChunkRelWorld): Unit =
1✔
UNCOV
209
    for
×
UNCOV
210
      side <- 0 until 8
×
211
      ch <- getChunk(coords.offset(NeighborOffsets(side)))
1✔
212
    do ch.requestRenderUpdate()
213

1✔
214
  private def ensureColumnExists(here: ColumnRelWorld): ChunkColumn =
1✔
215
    columns.get(here.value) match
1✔
216
      case Some(col) => col
1✔
217
      case None =>
1✔
218
        val columnNBT = worldProvider.loadColumnData(here)
1✔
219
        val col = ChunkColumn.create(here, worldGenerator, columnNBT)
1✔
220
        columns(here.value) = col
221
        col
222

×
UNCOV
223
  def getBrightness(block: BlockRelWorld): Float =
×
UNCOV
224
    getChunk(block.getChunkRelWorld) match
×
UNCOV
225
      case Some(c) => c.lighting.getBrightness(block.getBlockRelChunk)
×
226
      case None    => 1.0f
UNCOV
227

×
228
  def onReloadedResources(): Unit = for col <- columns.values do col.onReloadedResources()
229

1✔
230
  def unload(): Unit =
231
    blockUpdateTimer.enabled = false
1✔
UNCOV
232
    for col <- columns.values do
×
233
      for ch <- col.allChunks if ch.modCount != savedChunkModCounts.getOrElse(ch.coords, -1L) do
1✔
234
        worldProvider.saveChunkData(ch.toNbt, ch.coords)
235

1✔
236
      worldProvider.saveColumnData(col.toNBT, col.coords)
1✔
237
    columns.clear()
1✔
238
    chunkLoader.unload()
239

1✔
240
  private def onChunkEvent(event: Chunk.Event): Unit =
241
    event match
1✔
242
      case Chunk.Event.ChunkNeedsRenderUpdate(coords) =>
1✔
243
        dispatcher.notify(World.Event.ChunkNeedsRenderUpdate(coords))
1✔
244
      case Chunk.Event.BlockReplaced(coords, prev, block) =>
1✔
245
        onSetBlock(coords, block)
1✔
246
        dispatcher.notify(World.Event.BlockReplaced(coords, prev, block))
247

1✔
248
  private def requestBlockUpdate(coords: BlockRelWorld): Unit = blocksToUpdate.enqueue(coords)
249

1✔
250
  private def onSetBlock(coords: BlockRelWorld, block: BlockState): Unit =
1✔
251
    def affectedChunkOffset(where: Byte): Int = where match
1✔
252
      case 0  => -1
1✔
253
      case 15 => 1
1✔
254
      case _  => 0
255

1✔
256
    def isInNeighborChunk(chunkOffset: Offset) =
1✔
257
      val xx = affectedChunkOffset(coords.cx)
1✔
258
      val yy = affectedChunkOffset(coords.cy)
1✔
259
      val zz = affectedChunkOffset(coords.cz)
260

261
      chunkOffset.dx * xx == 1 || chunkOffset.dy * yy == 1 || chunkOffset.dz * zz == 1
262

1✔
263
    for col <- columns.get(coords.getColumnRelWorld.value) do col.updateHeightmapAfterBlockUpdate(coords, block)
264

1✔
265
    val cCoords = coords.getChunkRelWorld
1✔
266
    val bCoords = coords.getBlockRelChunk
267

1✔
268
    for c <- getChunk(cCoords) do
1✔
269
      handleLightingOnSetBlock(c, bCoords, block)
270

1✔
271
      c.requestRenderUpdate()
1✔
272
      requestBlockUpdate(BlockRelWorld.fromChunk(bCoords, c.coords))
UNCOV
273

×
274
      for i <- 0 until 8 do
1✔
275
        val off = ChunkRelWorld.neighborOffsets(i)
1✔
276
        val c2 = bCoords.offset(off)
277

1✔
UNCOV
278
        if isInNeighborChunk(off) then
×
279
          for n <- getChunk(cCoords.offset(NeighborOffsets(i))) do
1✔
280
            n.requestRenderUpdate()
1✔
281
            requestBlockUpdate(BlockRelWorld.fromChunk(c2, n.coords))
1✔
282
        else requestBlockUpdate(BlockRelWorld.fromChunk(c2, c.coords))
283

1✔
284
  private def handleLightingOnSetBlock(chunk: Chunk, blockCoords: BlockRelChunk, block: BlockState): Unit =
1✔
285
    lightPropagator.removeTorchlight(chunk, blockCoords)
1✔
286
    lightPropagator.removeSunlight(chunk, blockCoords)
1✔
UNCOV
287
    if block.blockType.lightEmitted != 0 then
×
288
      lightPropagator.addTorchlight(chunk, blockCoords, block.blockType.lightEmitted)
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc