more chunk pool codes

This commit is contained in:
minjaesong
2024-10-07 18:06:18 +09:00
parent 253ca85790
commit 07b4f3bff1
4 changed files with 175 additions and 27 deletions

View File

@@ -64,7 +64,7 @@ open class BlockLayerI16(override val width: Int, override val height: Int) : Bl
val lsb = ptr[offset]
val msb = ptr[offset + 1]
return lsb.toUint() + msb.toUint().shl(8)
return lsb.toUint() or msb.toUint().shl(8)
}
override fun unsafeToBytes(x: Int, y: Int): ByteArray {

View File

@@ -62,10 +62,8 @@ class BlockLayerI16F16(override val width: Int, override val height: Int) : Bloc
val offset = getOffset(x, y)
val lsb = ptr[offset]
val msb = ptr[offset + 1]
val hbits = (ptr[offset + 2].toUint() or ptr[offset + 3].toUint().shl(8)).toShort()
val fill = Float16.toFloat(hbits)
return lsb.toUint() + msb.toUint().shl(8)
return lsb.toUint() or msb.toUint().shl(8)
}
internal fun unsafeGetTile1(x: Int, y: Int): Pair<Int, Float> {
@@ -75,7 +73,7 @@ class BlockLayerI16F16(override val width: Int, override val height: Int) : Bloc
val hbits = (ptr[offset + 2].toUint() or ptr[offset + 3].toUint().shl(8)).toShort()
val fill = Float16.toFloat(hbits)
return lsb.toUint() + msb.toUint().shl(8) to fill
return lsb.toUint() or msb.toUint().shl(8) to fill
}
override fun unsafeToBytes(x: Int, y: Int): ByteArray {

View File

@@ -69,7 +69,7 @@ class BlockLayerOresI16I8 (override val width: Int, override val height: Int) :
val msb = ptr[offset + 1]
val placement = ptr[offset + 2]
return lsb.toUint() + msb.toUint().shl(8) to placement.toUint()
return lsb.toUint() or msb.toUint().shl(8) to placement.toUint()
}
override fun unsafeToBytes(x: Int, y: Int): ByteArray {

View File

@@ -1,15 +1,36 @@
package net.torvald.terrarum.gameworld
import net.torvald.terrarum.INGAME
import net.torvald.terrarum.Point2i
import net.torvald.terrarum.TerrarumAppConfiguration.TILE_SIZE
import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.archivers.ClusteredFormatDOM
import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.archivers.Clustfile
import net.torvald.terrarum.realestate.LandUtil
import net.torvald.terrarum.realestate.LandUtil.CHUNK_H
import net.torvald.terrarum.realestate.LandUtil.CHUNK_W
import net.torvald.terrarum.serialise.Common
import net.torvald.terrarum.serialise.toUint
import net.torvald.unsafe.UnsafeHelper
import net.torvald.unsafe.UnsafePtr
import net.torvald.util.Float16
import java.util.TreeMap
/**
* Created by minjaesong on 2024-10-07.
*/
data class ChunkAllocation(
val chunkNumber: Int,
var classifier: ChunkAllocClass,
var lastAccessTime: Long = System.nanoTime()
)
/**
* Created by minjaesong on 2024-10-07.
*/
enum class ChunkAllocClass {
PERSISTENT, TEMPORARY
}
/**
* Single layer gets single Chunk Pool.
*
@@ -17,20 +38,21 @@ import java.util.TreeMap
*/
open class ChunkPool(
val DOM: ClusteredFormatDOM,
val wordSizeInBytes: Int,
val wordSizeInBytes: Long,
val world: GameWorld,
val chunkNumToFileNum: (Int) -> String,
val renumberFun: (Int) -> Int,
) {
private val pointers = TreeMap<Int, Long>()
private var allocCap = 32
private var allocMap = ArrayList<Int>(allocCap)
private var allocMap = Array<ChunkAllocation?>(allocCap) { null }
private var allocCounter = 0
private val chunkSize = (wordSizeInBytes.toLong() * CHUNK_W * CHUNK_H)
private val chunkSize = (wordSizeInBytes * CHUNK_W * CHUNK_H)
private val pool = UnsafeHelper.allocate(chunkSize * allocCap)
init {
allocMap.fill(-1)
allocMap.fill(null)
}
private fun createPointerViewOfChunk(chunkNumber: Int): UnsafePtr {
@@ -40,42 +62,103 @@ open class ChunkPool(
private fun createPointerViewOfChunk(chunkNumber: Int, offsetX: Int, offsetY: Int): Pair<UnsafePtr, Long> {
val baseAddr = pointers[chunkNumber]!!
return UnsafePtr(baseAddr, chunkSize) to wordSizeInBytes.toLong() * (offsetY * CHUNK_W + offsetX)
return UnsafePtr(baseAddr, chunkSize) to wordSizeInBytes * (offsetY * CHUNK_W + offsetX)
}
private fun allocate(chunkNumber: Int): UnsafePtr {
// expand the pool if needed
if (allocCounter >= allocCap) {
/**
* If the chunk number == playerChunkNum, the result will be `false`
*/
private fun Int.isChunkNearPlayer(playerChunkNum: Int): Boolean {
if (this == playerChunkNum) return false
val pxy = LandUtil.chunkNumToChunkXY(world, playerChunkNum)
val validChunknums = chunkOffsetsNearPlayer.map { (it + pxy).also {
it.x = it.x fmod (world.width / CHUNK_W) // wrap around X as per ROUNDWORLD
} }.filter { it.y in 0 until (world.height / CHUNK_H) }.map { // filter values that is outside of world's Y-range
LandUtil.chunkXYtoChunkNum(world, it.x, it.y) // convert back to chunk numbers
}
return validChunknums.contains(this)
}
/**
* Tries to make a spot for one (1) new chunk.
*
* Procedure:
* 1. Check if there are at least one out-of-reach and `TEMPORARY` chunk.
* 2. If Check #1 passes, that chunk will be unloaded — end.
* 3. Otherwise, the pool will be extended.
*/
private fun updateAllocMapUsingIngamePlayer() {
val playerChunkNum = INGAME.actorNowPlaying?.intTilewiseHitbox?.let {
val cx = (it.canonicalX.toInt() fmod world.width) / CHUNK_W
val cy = (it.canonicalY.toInt() / CHUNK_H).coerceIn(0 until world.height / CHUNK_H)
LandUtil.chunkXYtoChunkNum(world, cx, cy)
}
if (playerChunkNum != null) {
// get list of chunks that passes Check #1
val remCandidate: List<ChunkAllocation> = allocMap.filterNotNull().filter {
it.chunkNumber.isChunkNearPlayer(playerChunkNum) &&
it.classifier == ChunkAllocClass.TEMPORARY
}
if (remCandidate.isNotEmpty()) {
// try to deallocate the oldest allocation (having the smallest lastAccessTime)
if (deallocate(remCandidate.minByOrNull { it.lastAccessTime }!!))
return // exit the function ONLY IF the deallocation was successful
}
// if there is no candidate, proceed to the next line
}
// expand the pool and allocMap if needed
while (allocCounter >= allocCap) {
allocCap *= 2
allocMap.ensureCapacity(allocCap)
val newAllocMap = Array<ChunkAllocation?>(allocCap) { null }
System.arraycopy(allocMap, 0, newAllocMap, 0, allocMap.size)
allocMap = newAllocMap
pool.realloc(chunkSize * allocCap)
}
}
// find the empty spot
val idx = allocMap.indexOfFirst { it == -1 }
private fun allocate(chunkNumber: Int, allocClass: ChunkAllocClass = ChunkAllocClass.TEMPORARY): UnsafePtr {
updateAllocMapUsingIngamePlayer()
// find the empty spot within the pool
val idx = allocMap.indexOfFirst { it == null }
val ptr = pool.ptr + idx * chunkSize
allocMap[idx] = ChunkAllocation(chunkNumber, allocClass)
allocCounter += 1
pointers[chunkNumber] = ptr
return UnsafePtr(ptr, chunkSize)
}
private fun deallocate(chunkNumber: Int) {
val ptr = pointers[chunkNumber] ?: return
private fun deallocate(allocation: ChunkAllocation) = deallocate(allocation.chunkNumber)
private fun deallocate(chunkNumber: Int): Boolean {
val ptr = pointers[chunkNumber] ?: return false
storeToDisk(chunkNumber)
pointers.remove(chunkNumber)
allocMap[chunkNumber] = -1
UnsafeHelper.unsafe.freeMemory(ptr)
allocMap.indexOfFirst { it?.chunkNumber == chunkNumber }.let {
allocMap[it] = null
allocCounter -= 1
}
return true
}
private fun renumber(ptr: UnsafePtr) {
for (i in 0 until ptr.size step wordSizeInBytes.toLong()) {
val numIn = (0 until wordSizeInBytes).fold(0) { acc, off ->
for (i in 0 until ptr.size step wordSizeInBytes) {
val numIn = (0 until wordSizeInBytes.toInt()).fold(0) { acc, off ->
acc or (ptr[i + off].toUint().shl(8 * off))
}
val numOut = renumberFun(numIn)
(0 until wordSizeInBytes).forEach { off ->
(0 until wordSizeInBytes.toInt()).forEach { off ->
ptr[i + off] = numOut.ushr(8 * off).toByte()
}
}
@@ -102,16 +185,75 @@ open class ChunkPool(
}
}
/**
* Given the word-aligned byte sequence of `[B0, B1, B2, B3, ...]`,
* Return format:
* - word size is 4: Int `B3_B2_B1_B0`
* - word size is 3: Int `00_B2_B1_B0`
* - word size is 2: Int `00_00_B1_B0`
*/
fun getTileRaw(chunkNumber: Int, offX: Int, offY: Int): Int {
checkForChunk(chunkNumber)
allocMap.find { it?.chunkNumber == chunkNumber }!!.let { it.lastAccessTime = System.nanoTime() }
val (ptr, ptrOff) = createPointerViewOfChunk(chunkNumber, offX, offY)
val numIn = (0 until wordSizeInBytes).fold(0) { acc, off ->
val numIn = (0 until wordSizeInBytes.toInt()).fold(0) { acc, off ->
acc or (ptr[ptrOff].toUint().shl(8 * off))
}
return numIn
}
/**
* Given the word-aligned byte sequence of `[B0, B1, B2, B3, ...]`,
* Return format:
* - First element: Int `00_00_B1_B0`
* - Second element: Float16(`B3_B2`).toFloat32
*/
fun getTileI16F16(chunkNumber: Int, offX: Int, offY: Int): Pair<Int, Float> {
val raw = getTileRaw(chunkNumber, offX, offY)
val ibits = raw.get1SS()
val fbits = raw.get2SS().toShort()
return ibits to Float16.toFloat(fbits)
}
/**
* Given the word-aligned byte sequence of `[B0, B1, B2, B3, ...]`,
* Return format:
* - Int `00_00_B1_B0`
*/
fun getTileI16(chunkNumber: Int, offX: Int, offY: Int): Int {
val raw = getTileRaw(chunkNumber, offX, offY)
val ibits = raw.get1SS()
return ibits
}
/**
* Given the word-aligned byte sequence of `[B0, B1, B2, B3, ...]`,
* Return format:
* - First element: Int `00_00_B1_B0`
* - Second element: Int `00_00_00_B2`
*/
fun getTileI16I8(chunkNumber: Int, offX: Int, offY: Int): Pair<Int, Int> {
val raw = getTileRaw(chunkNumber, offX, offY)
val ibits = raw.get1SS()
val jbits = raw.get2SS() and 255
return ibits to jbits
}
companion object {
private fun Int.get1SS() = this and 65535
private fun Int.get2SS() = (this ushr 16) and 65535
private fun Int.getMSB() = (this ushr 24) and 255
private fun Int.get2MSB() = (this ushr 16) and 255
private fun Int.get3MSB() = (this ushr 8) and 255
private fun Int.get4MSB() = this and 255
private fun Int.get4LSB() = this.getMSB()
private fun Int.get3LSB() = this.get2MSB()
private fun Int.get2LSB() = this.get3MSB()
private fun Int.getLSB() = this.get4MSB()
fun getRenameFunTerrain(world: GameWorld): (Int) -> Int {
// word size: 2
return { oldTileNum ->
@@ -142,5 +284,13 @@ open class ChunkPool(
world.tileNameToNumberMap[oldFluidName]!! or oldFluidFill
}
}
private val chunkOffsetsNearPlayer = listOf(
Point2i(-1,-2), Point2i(0,-2),Point2i(1,-2),
Point2i(-2,-1),Point2i(-1,-1),Point2i(0,-1),Point2i(1,-1),Point2i(2,-1),
Point2i(-2,0),Point2i(-1,0),Point2i(1,0),Point2i(2,0),
Point2i(-2,1),Point2i(-1,1),Point2i(0,1),Point2i(1,1),Point2i(2,1),
Point2i(-1,2),Point2i(0,2),Point2i(1,2)
)
}
}