Massively improve memory usage of world tiles

This commit is contained in:
DBotThePony 2023-10-22 10:12:39 +07:00
parent 949ed802ad
commit 064689fc25
Signed by: DBot
GPG Key ID: DCC23B5715498507
7 changed files with 21 additions and 15 deletions

View File

@ -116,7 +116,7 @@ fun main() {
val rand = Random() val rand = Random()
for (i in 0 .. 128) { for (i in 0 until 0) {
val item = ItemEntity(client.world!!, Registries.items.keys.values.random().value) val item = ItemEntity(client.world!!, Registries.items.keys.values.random().value)
item.position = Vector2d(225.0 - i, 785.0) item.position = Vector2d(225.0 - i, 785.0)

View File

@ -613,7 +613,7 @@ class StarboundClient : Closeable {
} }
val tileRenderers = TileRenderers(this) val tileRenderers = TileRenderers(this)
var world: ClientWorld? = ClientWorld(this, 0L, Vector2i(3000, 2000), true) var world: ClientWorld? = ClientWorld(this, 0L, Vector2i(16000, 8000), true)
init { init {
clearColor = RGBAColor.SLATE_GRAY clearColor = RGBAColor.SLATE_GRAY

View File

@ -9,11 +9,13 @@ import java.lang.ref.WeakReference
import java.util.concurrent.locks.LockSupport import java.util.concurrent.locks.LockSupport
import java.util.concurrent.locks.ReentrantLock import java.util.concurrent.locks.ReentrantLock
import kotlin.concurrent.withLock import kotlin.concurrent.withLock
import kotlin.math.log
import kotlin.math.roundToInt
// hand-rolled interner, which has similar performance to ConcurrentHashMap // hand-rolled interner, which has similar performance to ConcurrentHashMap
// (given there is no strong congestion, otherwise it performs somewhere above Caffeine interner), // (given there is no strong congestion, otherwise it performs somewhere above Caffeine interner),
// while yielding significantly better memory utilization than both // while yielding significantly better memory utilization than both
class HashTableInterner<T : Any>(private val segmentBits: Int) : Interner<T> { class HashTableInterner<T : Any>(private val segmentBits: Int = log(Runtime.getRuntime().availableProcessors().toDouble(), 2.0).roundToInt().coerceAtLeast(4)) : Interner<T> {
companion object { companion object {
private val interners = ArrayList<WeakReference<HashTableInterner<*>>>() private val interners = ArrayList<WeakReference<HashTableInterner<*>>>()

View File

@ -1,5 +1,7 @@
package ru.dbotthepony.kstarbound.world.api package ru.dbotthepony.kstarbound.world.api
import com.github.benmanes.caffeine.cache.Interner
import ru.dbotthepony.kstarbound.util.HashTableInterner
import java.io.DataInputStream import java.io.DataInputStream
sealed class AbstractCell { sealed class AbstractCell {
@ -23,7 +25,10 @@ sealed class AbstractCell {
stream.skipNBytes(1 + 2 + 1 + 1 + 1 + 1) stream.skipNBytes(1 + 2 + 1 + 1 + 1 + 1)
} }
val EMPTY = ImmutableCell(AbstractTileState.EMPTY, AbstractTileState.EMPTY, AbstractLiquidState.EMPTY, 0, 0, 0, false) @JvmStatic
val NULL = ImmutableCell(AbstractTileState.NULL, AbstractTileState.NULL, AbstractLiquidState.EMPTY, 0, 0, 0, false) protected val POOL: Interner<ImmutableCell> = HashTableInterner()
val EMPTY: ImmutableCell = POOL.intern(ImmutableCell(AbstractTileState.EMPTY, AbstractTileState.EMPTY, AbstractLiquidState.EMPTY, 0, 0, 0, false))
val NULL: ImmutableCell = POOL.intern(ImmutableCell(AbstractTileState.NULL, AbstractTileState.NULL, AbstractLiquidState.EMPTY, 0, 0, 0, false))
} }
} }

View File

@ -1,9 +1,11 @@
package ru.dbotthepony.kstarbound.world.api package ru.dbotthepony.kstarbound.world.api
import com.github.benmanes.caffeine.cache.Interner
import ru.dbotthepony.kstarbound.Registry import ru.dbotthepony.kstarbound.Registry
import ru.dbotthepony.kstarbound.defs.tile.BuiltinMetaMaterials import ru.dbotthepony.kstarbound.defs.tile.BuiltinMetaMaterials
import ru.dbotthepony.kstarbound.defs.tile.MaterialModifier import ru.dbotthepony.kstarbound.defs.tile.MaterialModifier
import ru.dbotthepony.kstarbound.defs.tile.TileDefinition import ru.dbotthepony.kstarbound.defs.tile.TileDefinition
import ru.dbotthepony.kstarbound.util.HashTableInterner
import java.io.DataInputStream import java.io.DataInputStream
sealed class AbstractTileState { sealed class AbstractTileState {
@ -21,7 +23,10 @@ sealed class AbstractTileState {
stream.skipNBytes(2 + 1 + 1 + 2 + 1) stream.skipNBytes(2 + 1 + 1 + 2 + 1)
} }
val EMPTY = ImmutableTileState(BuiltinMetaMaterials.EMPTY) @JvmStatic
val NULL = ImmutableTileState(BuiltinMetaMaterials.NULL) protected val POOL: Interner<ImmutableTileState> = HashTableInterner()
val EMPTY: ImmutableTileState = POOL.intern(ImmutableTileState(BuiltinMetaMaterials.EMPTY))
val NULL: ImmutableTileState = POOL.intern(ImmutableTileState(BuiltinMetaMaterials.NULL))
} }
} }

View File

@ -29,10 +29,7 @@ data class MutableCell(
} }
override fun immutable(): ImmutableCell { override fun immutable(): ImmutableCell {
val result = ImmutableCell(foreground.immutable(), background.immutable(), liquid.immutable(), dungeonId, biome, envBiome, isIndestructible) return POOL.intern(ImmutableCell(foreground.immutable(), background.immutable(), liquid.immutable(), dungeonId, biome, envBiome, isIndestructible))
if (result == NULL) return NULL
if (result == EMPTY) return EMPTY
return result
} }
override fun mutable(): MutableCell { override fun mutable(): MutableCell {

View File

@ -15,10 +15,7 @@ data class MutableTileState(
override var modifierHueShift: Float = 0f, override var modifierHueShift: Float = 0f,
) : AbstractTileState() { ) : AbstractTileState() {
override fun immutable(): ImmutableTileState { override fun immutable(): ImmutableTileState {
val result = ImmutableTileState(material, modifier, color, hueShift, modifierHueShift) return POOL.intern(ImmutableTileState(material, modifier, color, hueShift, modifierHueShift))
if (result == NULL) return NULL
if (result == EMPTY) return EMPTY
return result
} }
override fun mutable(): MutableTileState { override fun mutable(): MutableTileState {