80 fps with unsafe access

This commit is contained in:
minjaesong
2019-06-08 03:00:47 +09:00
parent 5f11bb8cf9
commit 15cb42e26b
19 changed files with 260 additions and 248 deletions

View 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)

View File

@@ -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
}
}

View File

@@ -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)

View File

@@ -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, ...
}
}
}*/