mirror of
https://github.com/curioustorvald/Terrarum.git
synced 2026-06-12 11:34:05 +09:00
80 fps with unsafe access
This commit is contained in:
147
src/net/torvald/terrarum/gameworld/BlockLayer.kt
Normal file
147
src/net/torvald/terrarum/gameworld/BlockLayer.kt
Normal file
@@ -0,0 +1,147 @@
|
||||
package net.torvald.terrarum.gameworld
|
||||
|
||||
import com.badlogic.gdx.utils.Disposable
|
||||
import net.torvald.terrarum.AppLoader.printdbg
|
||||
import sun.misc.Unsafe
|
||||
|
||||
/**
|
||||
* Created by minjaesong on 2016-01-17.
|
||||
*/
|
||||
open class BlockLayer(val width: Int, val height: Int) : Disposable {
|
||||
|
||||
private val unsafe: Unsafe
|
||||
init {
|
||||
val unsafeConstructor = Unsafe::class.java.getDeclaredConstructor()
|
||||
unsafeConstructor.isAccessible = true
|
||||
unsafe = unsafeConstructor.newInstance()
|
||||
}
|
||||
private var unsafeArrayInitialised = false
|
||||
|
||||
private var layerPtr = unsafe.allocateMemory(width * height * BYTES_PER_BLOCK.toLong())
|
||||
|
||||
/**
|
||||
* @param data Byte array representation of the layer, where:
|
||||
* - every 2n-th byte is lowermost 8 bits of the tile number
|
||||
* - every (2n+1)th byte is uppermost 4 (4096 blocks) or 8 (65536 blocks) bits of the tile number.
|
||||
*
|
||||
* When 4096-block mode is being used, every (2n+1)th byte is filled in this format:
|
||||
* ```
|
||||
* (MSB) 0 0 0 0 a b c d (LSB)
|
||||
* ```
|
||||
*
|
||||
* In other words, the valid range for the every (2n+1)th byte is 0..15.
|
||||
*
|
||||
* TL;DR: LITTLE ENDIAN PLEASE
|
||||
*/
|
||||
constructor(width: Int, height: Int, data: ByteArray) : this(width, height) {
|
||||
unsafe.allocateMemory(width * height * BYTES_PER_BLOCK.toLong())
|
||||
data.forEachIndexed { index, byte -> unsafe.putByte(layerPtr + index, byte) }
|
||||
unsafeArrayInitialised = true
|
||||
}
|
||||
|
||||
init {
|
||||
if (!unsafeArrayInitialised) {
|
||||
unsafe.setMemory(layerPtr, width * height * BYTES_PER_BLOCK.toLong(), 0)
|
||||
unsafeArrayInitialised = true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an iterator over blocks of type `Int`.
|
||||
*
|
||||
* @return an Iterator.
|
||||
*/
|
||||
fun blocksIterator(): Iterator<Int> {
|
||||
return object : Iterator<Int> {
|
||||
|
||||
private var iteratorCount = 0
|
||||
|
||||
override fun hasNext(): Boolean {
|
||||
return iteratorCount < width * height
|
||||
}
|
||||
|
||||
override fun next(): Int {
|
||||
val y = iteratorCount / width
|
||||
val x = iteratorCount % width
|
||||
// advance counter
|
||||
iteratorCount += 2
|
||||
|
||||
val offset = 2 * (y * width + x)
|
||||
val lsb = unsafe.getByte(layerPtr + offset)
|
||||
val msb = unsafe.getByte(layerPtr + offset + 1)
|
||||
|
||||
//return data[y * width + x]
|
||||
return lsb.toUint() + msb.toUint().shl(8)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an iterator over stored bytes.
|
||||
*
|
||||
* @return an Iterator.
|
||||
*/
|
||||
fun bytesIterator(): Iterator<Byte> {
|
||||
return object : Iterator<Byte> {
|
||||
|
||||
private var iteratorCount = 0
|
||||
|
||||
override fun hasNext(): Boolean {
|
||||
return iteratorCount < width * height
|
||||
}
|
||||
|
||||
override fun next(): Byte {
|
||||
val y = iteratorCount / width
|
||||
val x = iteratorCount % width
|
||||
// advance counter
|
||||
iteratorCount += 1
|
||||
|
||||
return unsafe.getByte(layerPtr + 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun unsafeGetTile(x: Int, y: Int): Int {
|
||||
val offset = BYTES_PER_BLOCK * (y * width + x)
|
||||
val lsb = unsafe.getByte(layerPtr + offset)
|
||||
val msb = unsafe.getByte(layerPtr + offset + 1)
|
||||
|
||||
return lsb.toUint() + msb.toUint().shl(8)
|
||||
}
|
||||
|
||||
internal fun unsafeSetTile(x: Int, y: Int, tile: Int) {
|
||||
val offset = BYTES_PER_BLOCK * (y * width + x)
|
||||
|
||||
val lsb = tile.and(0xff).toByte()
|
||||
val msb = tile.ushr(8).and(0xff).toByte()
|
||||
|
||||
unsafe.putByte(layerPtr + offset, lsb)
|
||||
unsafe.putByte(layerPtr + offset + 1, msb)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param blockOffset Offset in blocks. BlockOffset of 0x100 is equal to ```layerPtr + 0x200```
|
||||
*/
|
||||
internal fun unsafeSetTile(blockOffset: Long, tile: Int) {
|
||||
val offset = 2 * blockOffset
|
||||
|
||||
val lsb = tile.and(0xff).toByte()
|
||||
val msb = tile.ushr(8).and(0xff).toByte()
|
||||
|
||||
unsafe.putByte(layerPtr + offset, lsb)
|
||||
unsafe.putByte(layerPtr + offset + 1, msb)
|
||||
}
|
||||
|
||||
fun isInBound(x: Int, y: Int) = (x >= 0 && y >= 0 && x < width && y < height)
|
||||
|
||||
override fun dispose() {
|
||||
unsafe.freeMemory(layerPtr)
|
||||
printdbg(this, "BlockLayer successfully freed")
|
||||
}
|
||||
|
||||
companion object {
|
||||
@Transient val BYTES_PER_BLOCK = 2
|
||||
}
|
||||
}
|
||||
|
||||
fun Byte.toUint() = java.lang.Byte.toUnsignedInt(this)
|
||||
@@ -1,6 +1,7 @@
|
||||
|
||||
package net.torvald.terrarum.gameworld
|
||||
|
||||
import com.badlogic.gdx.utils.Disposable
|
||||
import net.torvald.gdx.graphics.Cvec
|
||||
import net.torvald.terrarum.AppLoader.printdbg
|
||||
import net.torvald.terrarum.Terrarum
|
||||
@@ -17,14 +18,14 @@ import kotlin.math.sign
|
||||
|
||||
typealias BlockAddress = Long
|
||||
|
||||
open class GameWorld {
|
||||
open class GameWorld : Disposable {
|
||||
|
||||
var worldName: String = "New World"
|
||||
/** Index start at 1 */
|
||||
var worldIndex: Int
|
||||
set(value) {
|
||||
if (value <= 0)
|
||||
throw Error("World index start at 1; you entered $value")
|
||||
throw Error("World index start at 1; you've entered $value")
|
||||
|
||||
printdbg(this, "Creation of new world with index $value, called by:")
|
||||
Thread.currentThread().stackTrace.forEach {
|
||||
@@ -46,17 +47,12 @@ open class GameWorld {
|
||||
val loadTime: Long = System.currentTimeMillis() / 1000L
|
||||
|
||||
//layers
|
||||
@TEMzPayload("WALL", TEMzPayload.EIGHT_MSB)
|
||||
val layerWall: MapLayer
|
||||
@TEMzPayload("WALL", TEMzPayload.TWELVE_BITS_LITTLE)
|
||||
val layerWall: BlockLayer
|
||||
@TEMzPayload("TERR", TEMzPayload.EIGHT_MSB)
|
||||
val layerTerrain: MapLayer
|
||||
val layerTerrain: BlockLayer
|
||||
//val layerWire: MapLayer
|
||||
|
||||
@TEMzPayload("WALL", TEMzPayload.FOUR_LSB)
|
||||
val layerWallLowBits: PairedMapLayer
|
||||
@TEMzPayload("TERR", TEMzPayload.FOUR_LSB)
|
||||
val layerTerrainLowBits: PairedMapLayer
|
||||
|
||||
//val layerThermal: MapLayerHalfFloat // in Kelvins
|
||||
//val layerFluidPressure: MapLayerHalfFloat // (milibar - 1000)
|
||||
|
||||
@@ -108,11 +104,9 @@ open class GameWorld {
|
||||
this.spawnX = width / 2
|
||||
this.spawnY = 200
|
||||
|
||||
layerTerrain = MapLayer(width, height)
|
||||
layerWall = MapLayer(width, height)
|
||||
layerTerrain = BlockLayer(width, height)
|
||||
layerWall = BlockLayer(width, height)
|
||||
//layerWire = MapLayer(width, height)
|
||||
layerTerrainLowBits = PairedMapLayer(width, height)
|
||||
layerWallLowBits = PairedMapLayer(width, height)
|
||||
|
||||
wallDamages = HashMap()
|
||||
terrainDamages = HashMap()
|
||||
@@ -140,8 +134,6 @@ open class GameWorld {
|
||||
layerTerrain = layerData.layerTerrain
|
||||
layerWall = layerData.layerWall
|
||||
//layerWire = layerData.layerWire
|
||||
layerTerrainLowBits = layerData.layerTerrainLowBits
|
||||
layerWallLowBits = layerData.layerWallLowBits
|
||||
|
||||
wallDamages = layerData.wallDamages
|
||||
terrainDamages = layerData.terrainDamages
|
||||
@@ -163,23 +155,6 @@ open class GameWorld {
|
||||
this.totalPlayTime = totalPlayTime
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get 2d array data of terrain
|
||||
|
||||
* @return byte[][] terrain layer
|
||||
*/
|
||||
val terrainArray: ByteArray
|
||||
get() = layerTerrain.data
|
||||
|
||||
/**
|
||||
* Get 2d array data of wall
|
||||
|
||||
* @return byte[][] wall layer
|
||||
*/
|
||||
val wallArray: ByteArray
|
||||
get() = layerWall.data
|
||||
|
||||
/**
|
||||
* Get 2d array data of wire
|
||||
|
||||
@@ -190,34 +165,18 @@ open class GameWorld {
|
||||
|
||||
private fun coerceXY(x: Int, y: Int) = (x fmod width) to (y.coerceIn(0, height - 1))
|
||||
|
||||
fun getTileFromWall(x: Int, y: Int): Int? {
|
||||
fun getTileFromWall(x: Int, y: Int): Int {
|
||||
val (x, y) = coerceXY(x, y)
|
||||
val wall: Int? = layerWall.getTile(x, y)
|
||||
val wallDamage: Int? = getWallLowBits(x, y)
|
||||
return if (wall == null || wallDamage == null)
|
||||
null
|
||||
else
|
||||
wall * PairedMapLayer.RANGE + wallDamage
|
||||
if (y !in 0 until height) throw Error("Y coord out of world boundary: $y")
|
||||
|
||||
return layerWall.unsafeGetTile(x, y)
|
||||
}
|
||||
|
||||
fun getTileFromTerrain(x: Int, y: Int): Int? {
|
||||
fun getTileFromTerrain(x: Int, y: Int): Int {
|
||||
val (x, y) = coerceXY(x, y)
|
||||
val terrain: Int? = layerTerrain.getTile(x, y)
|
||||
val terrainDamage: Int? = getTerrainLowBits(x, y)
|
||||
return if (terrain == null || terrainDamage == null)
|
||||
null
|
||||
else
|
||||
terrain * PairedMapLayer.RANGE + terrainDamage
|
||||
}
|
||||
if (y !in 0 until height) throw Error("Y coord out of world boundary: $y")
|
||||
|
||||
private fun getWallLowBits(x: Int, y: Int): Int? {
|
||||
val (x, y) = coerceXY(x, y)
|
||||
return layerWallLowBits.getData(x, y)
|
||||
}
|
||||
|
||||
private fun getTerrainLowBits(x: Int, y: Int): Int? {
|
||||
val (x, y) = coerceXY(x, y)
|
||||
return layerTerrainLowBits.getData(x, y)
|
||||
return layerTerrain.unsafeGetTile(x, y)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -233,12 +192,10 @@ open class GameWorld {
|
||||
val tilenum = tilenum % TILES_SUPPORTED // does work without this, but to be safe...
|
||||
|
||||
val oldWall = getTileFromWall(x, y)
|
||||
layerWall.setTile(x, y, (tilenum / PairedMapLayer.RANGE).toByte())
|
||||
layerWallLowBits.setData(x, y, tilenum % PairedMapLayer.RANGE)
|
||||
layerWall.unsafeSetTile(x, y, tilenum)
|
||||
wallDamages.remove(LandUtil.getBlockAddr(this, x, y))
|
||||
|
||||
if (oldWall != null)
|
||||
Terrarum.ingame?.queueWallChangedEvent(oldWall, tilenum, LandUtil.getBlockAddr(this, x, y))
|
||||
Terrarum.ingame?.queueWallChangedEvent(oldWall, tilenum, LandUtil.getBlockAddr(this, x, y))
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -256,8 +213,7 @@ open class GameWorld {
|
||||
val (x, y) = coerceXY(x, y)
|
||||
|
||||
val oldTerrain = getTileFromTerrain(x, y)
|
||||
layerTerrain.setTile(x, y, (tilenum / PairedMapLayer.RANGE).toByte())
|
||||
layerTerrainLowBits.setData(x, y, tilenum % PairedMapLayer.RANGE)
|
||||
layerTerrain.unsafeSetTile(x, y, tilenum)
|
||||
val blockAddr = LandUtil.getBlockAddr(this, x, y)
|
||||
terrainDamages.remove(blockAddr)
|
||||
|
||||
@@ -267,8 +223,7 @@ open class GameWorld {
|
||||
}
|
||||
// fluid tiles-item should be modified so that they will also place fluid onto their respective map
|
||||
|
||||
if (oldTerrain != null)
|
||||
Terrarum.ingame?.queueTerrainChangedEvent(oldTerrain, tilenum, LandUtil.getBlockAddr(this, x, y))
|
||||
Terrarum.ingame?.queueTerrainChangedEvent(oldTerrain, tilenum, LandUtil.getBlockAddr(this, x, y))
|
||||
}
|
||||
|
||||
/*fun setTileWire(x: Int, y: Int, tile: Byte) {
|
||||
@@ -506,16 +461,19 @@ open class GameWorld {
|
||||
return null
|
||||
}
|
||||
|
||||
override fun dispose() {
|
||||
layerWall.dispose()
|
||||
layerTerrain.dispose()
|
||||
}
|
||||
|
||||
companion object {
|
||||
@Transient val WALL = 0
|
||||
@Transient val TERRAIN = 1
|
||||
@Transient val WIRE = 2
|
||||
@Transient const val WALL = 0
|
||||
@Transient const val TERRAIN = 1
|
||||
@Transient const val WIRE = 2
|
||||
|
||||
/** 4096 */
|
||||
@Transient val TILES_SUPPORTED = MapLayer.RANGE * PairedMapLayer.RANGE
|
||||
@Transient val SIZEOF: Byte = MapLayer.SIZEOF
|
||||
@Transient val LAYERS: Byte = 4 // terrain, wall (layerTerrainLowBits + layerWallLowBits), wire
|
||||
@Transient const val TILES_SUPPORTED = 4096
|
||||
//@Transient val SIZEOF: Byte = 2
|
||||
@Transient const val LAYERS: Byte = 4 // terrain, wall (layerTerrainLowBits + layerWallLowBits), wire
|
||||
|
||||
fun makeNullWorld() = GameWorld(1, 1, 1, 0, 0, 0)
|
||||
}
|
||||
@@ -547,5 +505,6 @@ annotation class TEMzPayload(val payloadName: String, val arg: Int) {
|
||||
const val INT48_FLOAT_PAIR = 2
|
||||
const val INT48_SHORT_PAIR = 3
|
||||
const val INT48_INT_PAIR = 4
|
||||
const val TWELVE_BITS_LITTLE = 5
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
package net.torvald.terrarum.gameworld
|
||||
|
||||
/**
|
||||
* Created by minjaesong on 2016-01-17.
|
||||
*/
|
||||
open class MapLayer : Iterable<Byte> {
|
||||
|
||||
val width: Int; val height: Int
|
||||
internal @Volatile var data: ByteArray // in parallel programming: do not trust your register; always read freshly from RAM!
|
||||
|
||||
constructor(width: Int, height: Int) {
|
||||
this.width = width
|
||||
this.height = height
|
||||
data = ByteArray(width * height)
|
||||
}
|
||||
|
||||
constructor(width: Int, height: Int, data: ByteArray) {
|
||||
this.data = data
|
||||
this.width = width
|
||||
this.height = height
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an iterator over elements of type `T`.
|
||||
|
||||
* @return an Iterator.
|
||||
*/
|
||||
override fun iterator(): Iterator<Byte> {
|
||||
return object : Iterator<Byte> {
|
||||
|
||||
private var iteratorCount = 0
|
||||
|
||||
override fun hasNext(): Boolean {
|
||||
return iteratorCount < width * height
|
||||
}
|
||||
|
||||
override fun next(): Byte {
|
||||
val y = iteratorCount / width
|
||||
val x = iteratorCount % width
|
||||
// advance counter
|
||||
iteratorCount += 1
|
||||
|
||||
return data[y * width + x]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun getTile(x: Int, y: Int): Int? {
|
||||
return if (x !in 0..width - 1 || y !in 0..height - 1)
|
||||
null
|
||||
else
|
||||
data[y * width + x].toUint()
|
||||
}
|
||||
|
||||
internal fun setTile(x: Int, y: Int, tile: Byte) {
|
||||
data[y * width + x] = tile
|
||||
}
|
||||
|
||||
fun isInBound(x: Int, y: Int) = (x >= 0 && y >= 0 && x < width && y < height)
|
||||
|
||||
companion object {
|
||||
@Transient const val RANGE = 256
|
||||
@Transient const val SIZEOF: Byte = 1 // 1 for 8-bit, 2 for 16-bit, ...
|
||||
}
|
||||
}
|
||||
|
||||
fun Byte.toUint() = java.lang.Byte.toUnsignedInt(this)
|
||||
@@ -3,7 +3,7 @@ package net.torvald.terrarum.gameworld
|
||||
/**
|
||||
* Created by minjaesong on 2016-02-15.
|
||||
*/
|
||||
open class PairedMapLayer : Iterable<Byte> {
|
||||
/*open class PairedMapLayer : Iterable<Byte> {
|
||||
|
||||
val width: Int; val height: Int
|
||||
|
||||
@@ -87,4 +87,4 @@ open class PairedMapLayer : Iterable<Byte> {
|
||||
@Transient const val RANGE = 16
|
||||
@Transient const val SIZEOF: Byte = 1 // 1 for 8-bit, 2 for 16-bit, ...
|
||||
}
|
||||
}
|
||||
}*/
|
||||
Reference in New Issue
Block a user