• 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

94.06
/game/src/main/scala/hexacraft/world/loader.scala
1
package hexacraft.world
2

3
import hexacraft.util.{EventDispatcher, TickableTimer, Tracker}
4
import hexacraft.world.chunk.Chunk
5
import hexacraft.world.coord.{BlockCoords, BlockRelWorld, ChunkRelWorld, CoordUtils, CylCoords}
6

7
import java.util.concurrent.TimeUnit
8
import scala.collection.mutable
9
import scala.concurrent.{Await, Future}
10
import scala.concurrent.duration.Duration
11

12
class ChunkLoader(
13
    chunkFactory: ChunkRelWorld => Chunk,
14
    chunkUnloader: ChunkRelWorld => Unit,
15
    maxDist: Double
16
)(using CylinderSize) {
17
  import scala.concurrent.ExecutionContext.Implicits.global
18

19
  private val LoadsPerTick = 1
20
  private val UnloadsPerTick = 2
21
  private val MaxChunksToLoad = 4
22
  private val MaxChunksToUnload = 4
23

1✔
24
  private val prioritizer = new ChunkLoadingPrioritizer(maxDist)
25

×
26
  private def distSqFunc(p: PosAndDir, c: ChunkRelWorld): Double =
×
27
    p.pos.distanceSq(BlockCoords(BlockRelWorld(8, 8, 8, c)).toCylCoords)
28

1✔
29
  private val chunksLoading: mutable.Map[ChunkRelWorld, Future[Chunk]] = mutable.Map.empty
1✔
30
  private val chunksUnloading: mutable.Map[ChunkRelWorld, Future[ChunkRelWorld]] = mutable.Map.empty
31

1✔
32
  def tick(origin: PosAndDir, shouldLoadSlowly: Boolean): (Seq[Chunk], Seq[ChunkRelWorld]) =
1✔
33
    prioritizer.tick(origin)
34

1✔
35
    val (maxLoad, maxUnload) = if shouldLoadSlowly then (1, 1) else (MaxChunksToLoad, MaxChunksToUnload)
36

1✔
37
    for _ <- 1 to LoadsPerTick do
1✔
38
      if chunksLoading.size < maxLoad then
1✔
39
        prioritizer.popChunkToLoad.foreach: coords =>
1✔
40
          chunksLoading(coords) = Future(chunkFactory(coords))
41

1✔
42
    for _ <- 1 to UnloadsPerTick do
1✔
UNCOV
43
      if chunksUnloading.size < maxUnload then
×
44
        prioritizer.popChunkToRemove.foreach: coords =>
1✔
45
          chunksUnloading(coords) = Future:
1✔
46
            chunkUnloader(coords)
47
            coords
48

1✔
49
    val chunksToAdd = chunksLoading.values.flatMap(_.value).flatMap(_.toOption).toSeq
1✔
50
    val chunksToRemove = chunksUnloading.values.flatMap(_.value).flatMap(_.toOption).toSeq
51

52
    (chunksToAdd, chunksToRemove)
53

1✔
54
  def onWorldEvent(event: World.Event): Unit =
55
    event match
1✔
56
      case World.Event.ChunkAdded(chunk)    => chunksLoading -= chunk.coords
1✔
57
      case World.Event.ChunkRemoved(coords) => chunksUnloading -= coords
1✔
58
      case _                                =>
59

1✔
60
  def unload(): Unit =
1✔
61
    for f <- chunksLoading.values do Await.result(f, Duration(10, TimeUnit.SECONDS))
×
62
    for f <- chunksUnloading.values do Await.result(f, Duration(10, TimeUnit.SECONDS))
63
}
64

65
object ChunkLoadingPrioritizer {
1✔
66
  def distSq(p: PosAndDir, c: ChunkRelWorld)(using CylinderSize): Double =
1✔
67
    p.pos.distanceSq(BlockCoords(BlockRelWorld(8, 8, 8, c)).toCylCoords)
68
}
69

70
class ChunkLoadingPrioritizer(maxDist: Double)(using CylinderSize) {
1✔
71
  private var origin: PosAndDir = PosAndDir(CylCoords(0, 0, 0))
1✔
72
  private val edge = new ChunkLoadingEdge
1✔
73
  edge.trackEvents(this.onChunkEdgeEvent _)
74

1✔
75
  private val furthestFirst: Ordering[ChunkRelWorld] = Ordering.by(c => distSq(origin, c))
1✔
76
  private val closestFirst: Ordering[ChunkRelWorld] = Ordering.by(c => -distSq(origin, c))
77

1✔
78
  private val addableChunks: mutable.PriorityQueue[ChunkRelWorld] = mutable.PriorityQueue.empty(closestFirst)
1✔
79
  private val removableChunks: mutable.PriorityQueue[ChunkRelWorld] = mutable.PriorityQueue.empty(furthestFirst)
80

81
  private val maxDistSqInBlocks: Double = (maxDist * 16) * (maxDist * 16)
82

1✔
83
  private val reorderingTimer = TickableTimer(60)
84

1✔
85
  private def distSq(p: PosAndDir, c: ChunkRelWorld): Double = ChunkLoadingPrioritizer.distSq(p, c)
86

1✔
87
  def +=(chunk: ChunkRelWorld): Unit = edge.loadChunk(chunk)
88

1✔
89
  def -=(chunk: ChunkRelWorld): Unit = edge.unloadChunk(chunk)
90

1✔
91
  def tick(origin: PosAndDir): Unit =
92
    this.origin = origin
1✔
93
    if reorderingTimer.tick() then reorderPQs()
94

1✔
95
  def reorderPQs(): Unit =
1✔
96
    val addSeq = addableChunks.toSeq
1✔
97
    addableChunks.clear()
1✔
98
    addableChunks.enqueue(addSeq*)
1✔
99
    val remSeq = removableChunks.toSeq
1✔
100
    removableChunks.clear()
1✔
101
    removableChunks.enqueue(remSeq*)
102

1✔
103
  def nextAddableChunk: Option[ChunkRelWorld] =
1✔
104
    while addableChunks.nonEmpty && !edge.canLoad(addableChunks.head)
1✔
105
    do addableChunks.dequeue()
106

1✔
107
    if addableChunks.nonEmpty
1✔
108
    then Some(addableChunks.head).filter(coords => distSq(origin, coords) <= maxDistSqInBlocks)
1✔
109
    else Some(CoordUtils.approximateChunkCoords(origin.pos)).filter(coords => !edge.isLoaded(coords))
110

1✔
111
  def nextRemovableChunk: Option[ChunkRelWorld] =
1✔
112
    while removableChunks.nonEmpty && !edge.onEdge(removableChunks.head)
1✔
113
    do removableChunks.dequeue()
114

1✔
115
    if removableChunks.nonEmpty
1✔
116
    then Some(removableChunks.head).filter(coords => distSq(origin, coords) > maxDistSqInBlocks)
1✔
117
    else None
118

1✔
119
  def popChunkToLoad: Option[ChunkRelWorld] =
×
120
    nextAddableChunk.map: coords =>
1✔
121
      this += coords
122
      coords
123

1✔
124
  def popChunkToRemove: Option[ChunkRelWorld] =
1✔
125
    nextRemovableChunk.map: coords =>
1✔
126
      this -= coords
127
      coords
128

1✔
129
  private def onChunkEdgeEvent(event: ChunkLoadingEdge.Event): Unit =
130
    import ChunkLoadingEdge.Event.*
131
    event match
1✔
132
      case ChunkOnEdge(chunk, onEdge)     => if onEdge then removableChunks += chunk
1✔
133
      case ChunkLoadable(chunk, loadable) => if loadable then addableChunks += chunk
134
}
135

136
object ChunkLoadingEdge {
137
  enum Event:
138
    case ChunkOnEdge(chunk: ChunkRelWorld, onEdge: Boolean)
139
    case ChunkLoadable(chunk: ChunkRelWorld, loadable: Boolean)
140
}
141

142
class ChunkLoadingEdge(using CylinderSize) {
1✔
143
  private val chunksLoaded: mutable.Set[ChunkRelWorld] = mutable.HashSet.empty
1✔
144
  private val chunksEdge: mutable.Set[ChunkRelWorld] = mutable.HashSet.empty
1✔
145
  private val chunksLoadable: mutable.Set[ChunkRelWorld] = mutable.HashSet.empty
146

1✔
147
  private val dispatcher = new EventDispatcher[ChunkLoadingEdge.Event]
1✔
148
  def trackEvents(tracker: Tracker[ChunkLoadingEdge.Event]): Unit = dispatcher.track(tracker)
149

1✔
150
  def isLoaded(chunk: ChunkRelWorld): Boolean = chunksLoaded.contains(chunk)
151

1✔
152
  def onEdge(chunk: ChunkRelWorld): Boolean = chunksEdge.contains(chunk)
153

1✔
154
  def canLoad(chunk: ChunkRelWorld): Boolean = chunksLoadable.contains(chunk)
155

1✔
156
  def loadChunk(chunk: ChunkRelWorld): Unit =
1✔
157
    setLoaded(chunk, true)
1✔
158
    setOnEdge(chunk, true)
1✔
159
    setLoadable(chunk, false)
160

1✔
161
    for n <- chunk.neighbors
162
    do
1✔
163
      if n.neighbors.forall(isLoaded) then setOnEdge(n, false)
1✔
164
      if !isLoaded(n) then setLoadable(n, true)
165

1✔
166
  def unloadChunk(chunk: ChunkRelWorld): Unit =
1✔
167
    setLoaded(chunk, false)
1✔
168
    setOnEdge(chunk, false)
1✔
169
    setLoadable(chunk, chunk.neighbors.exists(isLoaded))
170

×
171
    for n <- chunk.neighbors
172
    do
1✔
173
      if !n.neighbors.exists(isLoaded) then setLoadable(n, false)
1✔
174
      if isLoaded(n) then setOnEdge(n, onEdge = true)
175

1✔
176
  private def setLoaded(chunk: ChunkRelWorld, loaded: Boolean): Unit =
1✔
177
    if chunksLoaded.contains(chunk) != loaded then chunksLoaded(chunk) = loaded
178

1✔
179
  private def setOnEdge(chunk: ChunkRelWorld, onEdge: Boolean): Unit =
1✔
180
    if chunksEdge.contains(chunk) != onEdge then
1✔
181
      chunksEdge(chunk) = onEdge
1✔
182
      dispatcher.notify(ChunkLoadingEdge.Event.ChunkOnEdge(chunk, onEdge))
183

1✔
184
  private def setLoadable(chunk: ChunkRelWorld, loadable: Boolean): Unit =
1✔
185
    if chunksLoadable.contains(chunk) != loadable then
1✔
186
      chunksLoadable(chunk) = loadable
1✔
187
      dispatcher.notify(ChunkLoadingEdge.Event.ChunkLoadable(chunk, loadable))
188
}
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