Immutable chunk cells

This commit is contained in:
DBotThePony 2023-10-19 00:03:38 +07:00
parent ba0d792e83
commit 93deb62d4b
Signed by: DBot
GPG Key ID: DCC23B5715498507
25 changed files with 423 additions and 461 deletions

View File

@ -1,8 +1,6 @@
package ru.dbotthepony.kstarbound
import com.google.common.collect.ImmutableList
import com.google.gson.reflect.TypeToken
import org.apache.logging.log4j.LogManager
import org.lwjgl.Version
import org.lwjgl.glfw.GLFW.glfwSetWindowShouldClose
@ -13,14 +11,13 @@ import ru.dbotthepony.kstarbound.player.Avatar
import ru.dbotthepony.kstarbound.player.QuestDescriptor
import ru.dbotthepony.kstarbound.player.QuestInstance
import ru.dbotthepony.kstarbound.util.JVMTimeSource
import ru.dbotthepony.kstarbound.world.api.IChunkCell
import ru.dbotthepony.kstarbound.world.api.AbstractCell
import ru.dbotthepony.kstarbound.world.entities.ItemEntity
import ru.dbotthepony.kstarbound.io.json.VersionedJson
import ru.dbotthepony.kstarbound.io.readVarInt
import ru.dbotthepony.kstarbound.util.AssetPathStack
import ru.dbotthepony.kstarbound.world.api.MutableCell
import ru.dbotthepony.kstarbound.world.entities.WorldObject
import ru.dbotthepony.kstarbound.world.physics.Poly
import ru.dbotthepony.kvector.util2d.AABB
import ru.dbotthepony.kvector.vector.Vector2d
import java.io.BufferedInputStream
import java.io.ByteArrayInputStream
@ -30,7 +27,6 @@ import java.util.*
import java.util.concurrent.TimeUnit
import java.util.zip.Inflater
import java.util.zip.InflaterInputStream
import kotlin.collections.ArrayList
private val LOGGER = LogManager.getLogger()
@ -82,13 +78,12 @@ fun main() {
var reader = DataInputStream(BufferedInputStream(InflaterInputStream(ByteArrayInputStream(data), Inflater())))
reader.skipBytes(3)
for (y in 0 .. 31) {
for (x in 0 .. 31) {
val cell = client.world!!.getCellDirect(chunkX * 32 + x, chunkY * 32 + y)
if (cell == null) {
IChunkCell.skip(reader)
} else {
cell.read(reader)
val chunk = client.world!!.chunkMap.compute(chunkX - 2, chunkY)
if (chunk != null) {
for (y in 0 .. 31) {
for (x in 0 .. 31) {
check(chunk.setCell(x, y, MutableCell().read(reader)))
}
}
}

View File

@ -54,7 +54,7 @@ import ru.dbotthepony.kstarbound.util.forEachValid
import ru.dbotthepony.kstarbound.util.formatBytesShort
import ru.dbotthepony.kstarbound.world.LightCalculator
import ru.dbotthepony.kstarbound.world.api.ICellAccess
import ru.dbotthepony.kstarbound.world.api.IChunkCell
import ru.dbotthepony.kstarbound.world.api.AbstractCell
import ru.dbotthepony.kvector.api.IStruct4f
import ru.dbotthepony.kvector.arrays.Matrix3f
import ru.dbotthepony.kvector.arrays.Matrix3fStack
@ -662,12 +662,16 @@ class StarboundClient : Closeable {
val settings = ClientSettings()
val viewportCells: ICellAccess = object : ICellAccess {
override fun getCell(x: Int, y: Int): IChunkCell? {
return world?.getCell(x + viewportCellX, y + viewportCellY)
override fun getCell(x: Int, y: Int): AbstractCell {
return world!!.getCell(x + viewportCellX, y + viewportCellY)
}
override fun getCellDirect(x: Int, y: Int): IChunkCell? {
return world?.getCellDirect(x + viewportCellX, y + viewportCellY)
override fun getCellDirect(x: Int, y: Int): AbstractCell {
return world!!.getCellDirect(x + viewportCellX, y + viewportCellY)
}
override fun setCell(x: Int, y: Int, cell: AbstractCell): Boolean {
return world!!.setCell(x + viewportCellX, y + viewportCellY, cell)
}
}

View File

@ -2,7 +2,7 @@ package ru.dbotthepony.kstarbound.client.render
import com.google.common.collect.ImmutableMap
import org.apache.logging.log4j.LogManager
import ru.dbotthepony.kstarbound.world.api.ITileState
import ru.dbotthepony.kstarbound.world.api.AbstractTileState
import ru.dbotthepony.kstarbound.world.api.TileColor
enum class RenderLayer {
@ -69,7 +69,7 @@ enum class RenderLayer {
}
}
fun tileLayer(isBackground: Boolean, isModifier: Boolean, tile: ITileState): Point {
fun tileLayer(isBackground: Boolean, isModifier: Boolean, tile: AbstractTileState): Point {
if (isModifier) {
return tileLayer(isBackground, true, tile.modifier?.renderParameters?.zLevel ?: 0L, tile.modifier?.modId?.toLong() ?: 0L, tile.modifierHueShift)
} else {

View File

@ -13,7 +13,7 @@ import ru.dbotthepony.kstarbound.client.gl.shader.UberShader
import ru.dbotthepony.kstarbound.client.gl.vertex.*
import ru.dbotthepony.kstarbound.defs.tile.*
import ru.dbotthepony.kstarbound.world.api.ITileAccess
import ru.dbotthepony.kstarbound.world.api.ITileState
import ru.dbotthepony.kstarbound.world.api.AbstractTileState
import ru.dbotthepony.kstarbound.world.api.TileColor
import ru.dbotthepony.kvector.vector.RGBAColor
import ru.dbotthepony.kvector.vector.Vector2i
@ -94,13 +94,13 @@ class TileRenderers(val client: StarboundClient) {
}
private class TileEqualityTester(val definition: TileDefinition) : EqualityRuleTester {
override fun test(thisTile: ITileState?, otherTile: ITileState?): Boolean {
override fun test(thisTile: AbstractTileState?, otherTile: AbstractTileState?): Boolean {
return otherTile?.material == definition && thisTile?.hueShift == otherTile.hueShift
}
}
private class ModifierEqualityTester(val definition: MaterialModifier) : EqualityRuleTester {
override fun test(thisTile: ITileState?, otherTile: ITileState?): Boolean {
override fun test(thisTile: AbstractTileState?, otherTile: AbstractTileState?): Boolean {
return otherTile?.modifier == definition
}
}
@ -125,7 +125,7 @@ class TileRenderer(val renderers: TileRenderers, val def: IRenderableTile) {
val bakedBackgroundProgramState = texture?.let { renderers.background(it) }
// private var notifiedDepth = false
private fun tesselateAt(self: ITileState, piece: RenderPiece, getter: ITileAccess, builder: VertexBuilder, pos: Vector2i, offset: Vector2i = Vector2i.ZERO, isModifier: Boolean) {
private fun tesselateAt(self: AbstractTileState, piece: RenderPiece, getter: ITileAccess, builder: VertexBuilder, pos: Vector2i, offset: Vector2i = Vector2i.ZERO, isModifier: Boolean) {
val fx = pos.x.toFloat()
val fy = pos.y.toFloat()
@ -173,7 +173,7 @@ class TileRenderer(val renderers: TileRenderers, val def: IRenderableTile) {
}
private fun tesselatePiece(
self: ITileState,
self: AbstractTileState,
matchPiece: RenderMatch,
getter: ITileAccess,
meshBuilder: LayeredRenderer,
@ -227,7 +227,7 @@ class TileRenderer(val renderers: TileRenderers, val def: IRenderableTile) {
*
* Тесселирует тайлы в нужный VertexBuilder с масштабом согласно константе [PIXELS_IN_STARBOUND_UNITf]
*/
fun tesselate(self: ITileState, getter: ITileAccess, meshBuilder: LayeredRenderer, pos: Vector2i, isBackground: Boolean = false, isModifier: Boolean = false) {
fun tesselate(self: AbstractTileState, getter: ITileAccess, meshBuilder: LayeredRenderer, pos: Vector2i, isBackground: Boolean = false, isModifier: Boolean = false) {
if (texture == null) return
// если у нас нет renderTemplate

View File

@ -2,28 +2,29 @@ package ru.dbotthepony.kstarbound.client.world
import ru.dbotthepony.kstarbound.world.Chunk
import ru.dbotthepony.kstarbound.world.ChunkPos
import ru.dbotthepony.kstarbound.world.api.ImmutableCell
class ClientChunk(world: ClientWorld, pos: ChunkPos) : Chunk<ClientWorld, ClientChunk>(world, pos){
override fun foregroundChanges(cell: Cell) {
super.foregroundChanges(cell)
override fun foregroundChanges(x: Int, y: Int, cell: ImmutableCell) {
super.foregroundChanges(x, y, cell)
world.forEachRenderRegion(cell) {
world.forEachRenderRegion(pos.tile(x, y)) {
it.foreground.isDirty = true
}
}
override fun backgroundChanges(cell: Cell) {
super.backgroundChanges(cell)
override fun backgroundChanges(x: Int, y: Int, cell: ImmutableCell) {
super.backgroundChanges(x, y, cell)
world.forEachRenderRegion(cell) {
world.forEachRenderRegion(pos.tile(x, y)) {
it.background.isDirty = true
}
}
override fun liquidChanges(cell: Cell) {
super.liquidChanges(cell)
override fun liquidChanges(x: Int, y: Int, cell: ImmutableCell) {
super.liquidChanges(x, y, cell)
world.forEachRenderRegion(cell) {
world.forEachRenderRegion(pos.tile(x, y)) {
it.liquidIsDirty = true
}
}

View File

@ -8,7 +8,7 @@ import ru.dbotthepony.kstarbound.defs.image.Image
import ru.dbotthepony.kstarbound.io.json.builder.JsonFactory
import ru.dbotthepony.kstarbound.util.WriteOnce
import ru.dbotthepony.kstarbound.world.api.ITileAccess
import ru.dbotthepony.kstarbound.world.api.ITileState
import ru.dbotthepony.kstarbound.world.api.AbstractTileState
import ru.dbotthepony.kvector.vector.Vector2i
@JsonFactory
@ -21,7 +21,7 @@ data class RenderPiece(
)
fun interface EqualityRuleTester {
fun test(thisTile: ITileState?, otherTile: ITileState?): Boolean
fun test(thisTile: AbstractTileState?, otherTile: AbstractTileState?): Boolean
}
@JsonFactory

View File

@ -1,16 +1,10 @@
package ru.dbotthepony.kstarbound.world
import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet
import ru.dbotthepony.kstarbound.defs.tile.BuiltinMetaMaterials
import ru.dbotthepony.kstarbound.defs.tile.LiquidDefinition
import ru.dbotthepony.kstarbound.defs.tile.MaterialModifier
import ru.dbotthepony.kstarbound.defs.tile.TileDefinition
import ru.dbotthepony.kstarbound.world.api.AbstractCell
import ru.dbotthepony.kstarbound.world.api.ICellAccess
import ru.dbotthepony.kstarbound.world.api.IChunkCell
import ru.dbotthepony.kstarbound.world.api.ILiquidState
import ru.dbotthepony.kstarbound.world.api.ITileState
import ru.dbotthepony.kstarbound.world.api.ImmutableCell
import ru.dbotthepony.kstarbound.world.api.OffsetCellAccess
import ru.dbotthepony.kstarbound.world.api.TileColor
import ru.dbotthepony.kstarbound.world.api.TileView
import ru.dbotthepony.kstarbound.world.entities.Entity
import ru.dbotthepony.kvector.arrays.Object2DArray
@ -33,10 +27,7 @@ abstract class Chunk<WorldType : World<WorldType, This>, This : Chunk<WorldType,
val pos: ChunkPos,
) : ICellAccess {
var changeset = 0
private set(value) {
field = value
promote()
}
private set
var tileChangeset = 0
private set
@ -50,32 +41,49 @@ abstract class Chunk<WorldType : World<WorldType, This>, This : Chunk<WorldType,
var backgroundChangeset = 0
private set
private var isEmpty = true
fun promote() {
if (isEmpty) {
isEmpty = false
world.chunkMap.promote(this as This)
}
}
protected val cells = lazy {
Object2DArray.nulls<Cell>(CHUNK_SIZE, CHUNK_SIZE)
Object2DArray(CHUNK_SIZE, CHUNK_SIZE, AbstractCell.NULL)
}
override fun getCell(x: Int, y: Int): IChunkCell {
var get = cells.value[x, y]
override fun getCell(x: Int, y: Int): AbstractCell {
if (!cells.isInitialized())
return AbstractCell.NULL
if (get == null) {
get = Cell(x, y)
cells.value[x, y] = get
return cells.value[x, y]
}
override fun getCellDirect(x: Int, y: Int): AbstractCell {
return getCell(x, y)
}
override fun setCell(x: Int, y: Int, cell: AbstractCell): Boolean {
val ix = x and CHUNK_SIZE_FF
val iy = y and CHUNK_SIZE_FF
if (ix != x || iy != y) return false
val old = if (cells.isInitialized()) cells.value[ix, iy] else AbstractCell.NULL
val new = cell.immutable()
if (old != new) {
cells.value[ix, iy] = new
if (old.foreground != new.foreground) {
foregroundChanges(ix, iy, new)
}
if (old.background != new.background) {
backgroundChanges(ix, iy, new)
}
if (old.liquid != new.liquid) {
liquidChanges(ix, iy, new)
}
cellChanges(ix, iy, new)
}
return get
}
override fun getCellDirect(x: Int, y: Int): IChunkCell {
return getCell(x, y)
return true
}
// local cells' tile access
@ -89,24 +97,24 @@ abstract class Chunk<WorldType : World<WorldType, This>, This : Chunk<WorldType,
val aabb = aabbBase + Vector2d(pos.x * CHUNK_SIZE.toDouble(), pos.y * CHUNK_SIZE.toDouble())
protected open fun foregroundChanges(cell: Cell) {
cellChanges(cell)
protected open fun foregroundChanges(x: Int, y: Int, cell: ImmutableCell) {
cellChanges(x, y, cell)
tileChangeset++
foregroundChangeset++
}
protected open fun backgroundChanges(cell: Cell) {
cellChanges(cell)
protected open fun backgroundChanges(x: Int, y: Int, cell: ImmutableCell) {
cellChanges(x, y, cell)
tileChangeset++
backgroundChangeset++
}
protected open fun liquidChanges(cell: Cell) {
cellChanges(cell)
protected open fun liquidChanges(x: Int, y: Int, cell: ImmutableCell) {
cellChanges(x, y, cell)
liquidChangeset++
}
protected open fun cellChanges(cell: Cell) {
protected open fun cellChanges(x: Int, y: Int, cell: ImmutableCell) {
changeset++
cellChangeset++
}
@ -122,130 +130,6 @@ abstract class Chunk<WorldType : World<WorldType, This>, This : Chunk<WorldType,
world.chunkMap[pos.bottomRight]?.let(block)
}
inner class Cell(x: Int, y: Int) : IChunkCell {
override val x: Int = x + pos.x * CHUNK_SIZE
override val y: Int = y + pos.y * CHUNK_SIZE
inner class Tile(private val foreground: Boolean) : ITileState {
private fun change() {
if (foreground) {
foregroundChanges(this@Cell)
} else {
backgroundChanges(this@Cell)
}
}
override var material: TileDefinition = BuiltinMetaMaterials.NULL
set(value) {
if (field !== value) {
field = value
change()
}
}
override var modifier: MaterialModifier? = null
set(value) {
if (field !== value) {
field = value
change()
}
}
override var color: TileColor = TileColor.DEFAULT
set(value) {
if (field != value) {
field = value
change()
}
}
override var hueShift: Float = 0f
set(value) {
if (field != value) {
field = value.coerceIn(0f, 360f)
change()
}
}
override var modifierHueShift: Float = 0f
set(value) {
if (field != value) {
field = value.coerceIn(0f, 360f)
change()
}
}
}
inner class Liquid : ILiquidState {
override var def: LiquidDefinition? = null
set(value) {
if (field !== value) {
field = value
liquidChanges(this@Cell)
}
}
override var level: Float = 0f
set(value) {
if (field != value) {
field = value
liquidChanges(this@Cell)
}
}
override var pressure: Float = 0f
set(value) {
if (field != value) {
field = value
liquidChanges(this@Cell)
}
}
override var isInfinite: Boolean = false
set(value) {
if (field != value) {
field = value
liquidChanges(this@Cell)
}
}
}
override val foreground = Tile(true)
override val background = Tile(false)
override val liquid = Liquid()
override var dungeonId: Int = 0
set(value) {
if (field != value) {
field = value
cellChanges(this)
}
}
override var biome: Int = 0
set(value) {
if (field != value) {
field = value
cellChanges(this)
}
}
override var envBiome: Int = 0
set(value) {
if (field != value) {
field = value
cellChanges(this)
}
}
override var isIndestructible: Boolean = false
set(value) {
if (field != value) {
field = value
cellChanges(this)
}
}
}
override fun randomLongFor(x: Int, y: Int): Long {
return world.randomLongFor(x or pos.x shl CHUNK_SIZE_BITS, y or pos.y shl CHUNK_SIZE_BITS)
}

View File

@ -33,6 +33,10 @@ data class ChunkPos(val x: Int, val y: Int) : IStruct2i, Comparable<ChunkPos> {
val tileY = y shl CHUNK_SIZE_BITS
val tile = Vector2i(tileX, tileY)
fun tile(x: Int, y: Int): Vector2i {
return Vector2i(tileX + x, tileY + y)
}
val top: ChunkPos get() {
return ChunkPos(x, y + 1)
}

View File

@ -1,9 +1,8 @@
package ru.dbotthepony.kstarbound.world
import ru.dbotthepony.kstarbound.world.METRES_IN_STARBOUND_UNIT
import ru.dbotthepony.kstarbound.math.roundTowardsNegativeInfinity
import ru.dbotthepony.kstarbound.world.api.ICellAccess
import ru.dbotthepony.kstarbound.world.api.IChunkCell
import ru.dbotthepony.kstarbound.world.api.AbstractCell
import ru.dbotthepony.kvector.vector.Vector2d
import ru.dbotthepony.kvector.vector.Vector2i
import kotlin.collections.ArrayList
@ -20,7 +19,7 @@ data class RayCastResult(
) {
constructor(startPos: Vector2d, direction: Vector2d) : this(listOf(), null, 0.0, startPos, startPos, direction)
data class HitCell(val pos: Vector2i, val normal: Direction, val borderCross: Vector2d, val cell: IChunkCell)
data class HitCell(val pos: Vector2i, val normal: Direction, val borderCross: Vector2d, val cell: AbstractCell)
}
enum class RayFilterResult(val hit: Boolean, val write: Boolean) {
@ -44,7 +43,7 @@ fun interface TileRayFilter {
/**
* [x] and [y] are wrapped around positions
*/
fun test(cell: IChunkCell, fraction: Double, x: Int, y: Int, normal: Direction, borderX: Double, borderY: Double): RayFilterResult
fun test(cell: AbstractCell, fraction: Double, x: Int, y: Int, normal: Direction, borderX: Double, borderY: Double): RayFilterResult
}
val NeverFilter = TileRayFilter { state, fraction, x, y, normal, borderX, borderY -> RayFilterResult.CONTINUE }

View File

@ -10,12 +10,11 @@ import ru.dbotthepony.kstarbound.math.*
import ru.dbotthepony.kstarbound.util.MailboxExecutorService
import ru.dbotthepony.kstarbound.util.ParallelPerform
import ru.dbotthepony.kstarbound.world.api.ICellAccess
import ru.dbotthepony.kstarbound.world.api.IChunkCell
import ru.dbotthepony.kstarbound.world.api.AbstractCell
import ru.dbotthepony.kstarbound.world.api.TileView
import ru.dbotthepony.kstarbound.world.entities.Entity
import ru.dbotthepony.kstarbound.world.entities.WorldObject
import ru.dbotthepony.kstarbound.world.physics.CollisionPoly
import ru.dbotthepony.kstarbound.world.physics.CollisionType
import ru.dbotthepony.kstarbound.world.physics.Poly
import ru.dbotthepony.kvector.api.IStruct2d
import ru.dbotthepony.kvector.api.IStruct2i
@ -23,8 +22,6 @@ import ru.dbotthepony.kvector.arrays.Object2DArray
import ru.dbotthepony.kvector.util2d.AABB
import ru.dbotthepony.kvector.vector.Vector2d
import ru.dbotthepony.kvector.vector.Vector2i
import java.lang.ref.ReferenceQueue
import java.lang.ref.WeakReference
import java.util.concurrent.ForkJoinPool
import java.util.concurrent.locks.ReentrantLock
import java.util.random.RandomGenerator
@ -54,33 +51,31 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
final override fun randomLongFor(x: Int, y: Int) = super.randomLongFor(x, y) xor seed
override fun getCellDirect(x: Int, y: Int): IChunkCell? {
if (!this.x.inBoundsCell(x) || !this.y.inBoundsCell(y)) return null
override fun getCellDirect(x: Int, y: Int): AbstractCell {
if (!this.x.inBoundsCell(x) || !this.y.inBoundsCell(y)) return AbstractCell.NULL
return getCell(x, y)
}
override fun getCell(x: Int, y: Int): IChunkCell? {
override fun getCell(x: Int, y: Int): AbstractCell {
return chunkMap.getCell(x, y)
}
override fun setCell(x: Int, y: Int, cell: AbstractCell): Boolean {
return chunkMap.setCell(x, y, cell)
}
abstract inner class ChunkMap {
abstract operator fun get(x: Int, y: Int): ChunkType?
abstract fun compute(x: Int, y: Int): ChunkType?
abstract fun promote(self: ChunkType)
abstract fun purge()
abstract fun remove(x: Int, y: Int)
abstract fun getCell(x: Int, y: Int): IChunkCell?
abstract fun getCell(x: Int, y: Int): AbstractCell
abstract fun setCell(x: Int, y: Int, cell: AbstractCell): Boolean
protected val queue = ReferenceQueue<ChunkType>()
operator fun get(pos: ChunkPos) = get(pos.x, pos.y)
protected inner class Ref(chunk: ChunkType) : WeakReference<ChunkType>(chunk, queue) {
val pos = chunk.pos
}
protected fun create(x: Int, y: Int): ChunkType {
purge()
val pos = ChunkPos(x, y)
val chunk = chunkFactory(pos)
val orphanedInThisChunk = ArrayList<Entity>()
@ -103,68 +98,41 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
// hash chunk map is around 30% slower than rectangular one
inner class HashChunkMap : ChunkMap() {
private val map = Long2ObjectOpenHashMap<Any>()
private val map = Long2ObjectOpenHashMap<ChunkType>()
override fun getCell(x: Int, y: Int): IChunkCell? {
if (!this@World.x.isValidCellIndex(x) || !this@World.y.isValidCellIndex(y)) return null
override fun getCell(x: Int, y: Int): AbstractCell {
if (!this@World.x.isValidCellIndex(x) || !this@World.y.isValidCellIndex(y)) return AbstractCell.NULL
val ix = this@World.x.cell(x)
val iy = this@World.y.cell(y)
return this[this@World.x.chunkFromCell(ix), this@World.y.chunkFromCell(iy)]?.getCell(ix and CHUNK_SIZE_MASK, iy and CHUNK_SIZE_MASK)
return this[this@World.x.chunkFromCell(ix), this@World.y.chunkFromCell(iy)]?.getCell(ix and CHUNK_SIZE_MASK, iy and CHUNK_SIZE_MASK) ?: AbstractCell.NULL
}
@Suppress("NAME_SHADOWING")
override fun get(x: Int, y: Int): ChunkType? {
if (!this@World.x.isValidChunkIndex(x) || !this@World.y.isValidChunkIndex(y)) return null
val x = this@World.x.chunk(x)
val y = this@World.y.chunk(y)
return map[ChunkPos.toLong(x, y)]?.let {
if (it is World<*, *>.ChunkMap.Ref) {
it.get() as ChunkType?
} else {
it as ChunkType?
}
} ?: create(x, y).also { map[ChunkPos.toLong(x, y)] = Ref(it) }
return map[ChunkPos.toLong(x, y)]
}
override fun purge() {
var next = queue.poll() as World<*, *>.ChunkMap.Ref?
while (next != null) {
val k = ChunkPos.toLong(next.pos.x, next.pos.y)
val get = map[k]
if (get === next)
map.remove(k)
next = queue.poll() as World<*, *>.ChunkMap.Ref?
}
override fun compute(x: Int, y: Int): ChunkType? {
if (!this@World.x.inBoundsChunk(x) || !this@World.y.inBoundsChunk(y)) return null
return map[ChunkPos.toLong(x, y)] ?: create(x, y).also { map[ChunkPos.toLong(x, y)] = it }
}
override fun promote(self: ChunkType) {
val (x, y) = self.pos
val ref = map[ChunkPos.toLong(x, y)]
if (ref !is World<*, *>.ChunkMap.Ref) {
throw IllegalStateException("Tried to promote chunk from weak to strong storage at $x, $y; but there is $ref")
}
if (!(ref as World<*, ChunkType>.ChunkMap.Ref).refersTo(self)) {
throw IllegalStateException("Tried to promote chunk from weak to strong storage at $x, $y; but it doesn't refer to valid object (self: $self, referent: ${ref.get()})")
}
map[ChunkPos.toLong(x, y)] = self
override fun setCell(x: Int, y: Int, cell: AbstractCell): Boolean {
if (!this@World.x.isValidCellIndex(x) || !this@World.y.isValidCellIndex(y)) return false
val ix = this@World.x.cell(x)
val iy = this@World.y.cell(y)
val cx = this@World.x.chunkFromCell(ix)
val cy = this@World.y.chunkFromCell(iy)
return (map[ChunkPos.toLong(cx, cy)] ?: create(cx, cy).also { map[ChunkPos.toLong(cx, cy)] = it }).setCell(ix and CHUNK_SIZE_MASK, iy and CHUNK_SIZE_MASK, cell)
}
override fun remove(x: Int, y: Int) {
val x = this@World.x.chunk(x)
val y = this@World.y.chunk(y)
val ref = map.remove(ChunkPos.toLong(x, y))
if (ref is World<*, *>.ChunkMap.Ref) {
ref.clear()
}
map.remove(ChunkPos.toLong(this@World.x.chunk(x), this@World.y.chunk(y)))
}
}
@ -172,64 +140,38 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
val width = size!!.x
val height = size!!.y
private val map = Object2DArray.nulls<Any>(divideUp(width, CHUNK_SIZE), divideUp(height, CHUNK_SIZE))
private val map = Object2DArray.nulls<ChunkType>(divideUp(width, CHUNK_SIZE), divideUp(height, CHUNK_SIZE))
private fun getRaw(x: Int, y: Int): ChunkType {
return map[x, y]?.let {
if (it is World<*, *>.ChunkMap.Ref) {
it.get() as ChunkType?
} else {
it as ChunkType?
}
} ?: create(x, y).also {
map[x, y] = Ref(it)
}
return map[x, y] ?: create(x, y).also { map[x, y] = it }
}
override fun getCell(x: Int, y: Int): IChunkCell? {
if (!this@World.x.isValidCellIndex(x) || !this@World.y.isValidCellIndex(y)) return null
override fun compute(x: Int, y: Int): ChunkType? {
if (!this@World.x.inBoundsChunk(x) || !this@World.y.inBoundsChunk(y)) return null
return getRaw(x, y)
}
override fun getCell(x: Int, y: Int): AbstractCell {
if (!this@World.x.isValidCellIndex(x) || !this@World.y.isValidCellIndex(y)) return AbstractCell.NULL
val ix = this@World.x.cell(x)
val iy = this@World.y.cell(y)
return getRaw(ix ushr CHUNK_SIZE_BITS, iy ushr CHUNK_SIZE_BITS).getCell(ix and CHUNK_SIZE_MASK, iy and CHUNK_SIZE_MASK)
}
override fun setCell(x: Int, y: Int, cell: AbstractCell): Boolean {
if (!this@World.x.isValidCellIndex(x) || !this@World.y.isValidCellIndex(y)) return false
val ix = this@World.x.cell(x)
val iy = this@World.y.cell(y)
return getRaw(ix ushr CHUNK_SIZE_BITS, iy ushr CHUNK_SIZE_BITS).setCell(ix and CHUNK_SIZE_MASK, iy and CHUNK_SIZE_MASK, cell)
}
override fun get(x: Int, y: Int): ChunkType? {
if (!this@World.x.isValidChunkIndex(x) || !this@World.y.isValidChunkIndex(y)) return null
return getRaw(this@World.x.chunk(x), this@World.y.chunk(y))
}
override fun purge() {
while (queue.poll() != null) {}
}
override fun promote(self: ChunkType) {
val (x, y) = self.pos
val ref = map[x, y]
if (ref !is World<*, *>.ChunkMap.Ref) {
throw IllegalStateException("Tried to promote chunk from weak to strong storage at $x, $y; but there is $ref")
}
if (!(ref as World<*, ChunkType>.ChunkMap.Ref).refersTo(self)) {
throw IllegalStateException("Tried to promote chunk from weak to strong storage at $x, $y; but it doesn't refer to valid object (self: $self, referent: ${ref.get()})")
}
map[x, y] = self
}
override fun remove(x: Int, y: Int) {
val x = this@World.x.chunk(x)
val y = this@World.y.chunk(y)
val old = map[x, y]
if (old is World<*, *>.ChunkMap.Ref) {
old.clear()
}
map[x, y] = null
map[this@World.x.chunk(x), this@World.y.chunk(y)] = null
}
}

View File

@ -0,0 +1,29 @@
package ru.dbotthepony.kstarbound.world.api
import java.io.DataInputStream
sealed class AbstractCell {
abstract val foreground: AbstractTileState
abstract val background: AbstractTileState
abstract val liquid: AbstractLiquidState
abstract val dungeonId: Int
abstract val biome: Int
abstract val envBiome: Int
abstract val isIndestructible: Boolean
abstract fun immutable(): ImmutableCell
abstract fun mutable(): MutableCell
companion object {
fun skip(stream: DataInputStream) {
AbstractTileState.skip(stream)
AbstractTileState.skip(stream)
AbstractLiquidState.skip(stream)
stream.skipNBytes(1 + 2 + 1 + 1 + 1 + 1)
}
val EMPTY = ImmutableCell(AbstractTileState.EMPTY, AbstractTileState.EMPTY, AbstractLiquidState.EMPTY, 0, 0, 0, false)
val NULL = ImmutableCell(AbstractTileState.NULL, AbstractTileState.NULL, AbstractLiquidState.EMPTY, 0, 0, 0, false)
}
}

View File

@ -0,0 +1,23 @@
package ru.dbotthepony.kstarbound.world.api
import ru.dbotthepony.kstarbound.Registries
import ru.dbotthepony.kstarbound.defs.tile.LiquidDefinition
import java.io.DataInputStream
sealed class AbstractLiquidState {
abstract val def: LiquidDefinition?
abstract val level: Float
abstract val pressure: Float
abstract val isInfinite: Boolean
abstract fun mutable(): MutableLiquidState
abstract fun immutable(): ImmutableLiquidState
companion object {
fun skip(stream: DataInputStream) {
stream.skipNBytes(1 + 4 + 4 + 1)
}
val EMPTY = ImmutableLiquidState()
}
}

View File

@ -0,0 +1,26 @@
package ru.dbotthepony.kstarbound.world.api
import ru.dbotthepony.kstarbound.defs.tile.BuiltinMetaMaterials
import ru.dbotthepony.kstarbound.defs.tile.MaterialModifier
import ru.dbotthepony.kstarbound.defs.tile.TileDefinition
import java.io.DataInputStream
sealed class AbstractTileState {
abstract val material: TileDefinition
abstract val modifier: MaterialModifier?
abstract val color: TileColor
abstract val hueShift: Float
abstract val modifierHueShift: Float
abstract fun immutable(): ImmutableTileState
abstract fun mutable(): MutableTileState
companion object {
fun skip(stream: DataInputStream) {
stream.skipNBytes(2 + 1 + 1 + 2 + 1)
}
val EMPTY = ImmutableTileState(BuiltinMetaMaterials.EMPTY)
val NULL = ImmutableTileState(BuiltinMetaMaterials.NULL)
}
}

View File

@ -5,19 +5,22 @@ import ru.dbotthepony.kvector.vector.Vector2i
interface ICellAccess {
/**
* non-null - valid cell (maybe wrapped around)
* null - invalid cell (outside world bounds)
* with wrap-around
*/
fun getCell(x: Int, y: Int): IChunkCell?
fun getCell(x: Int, y: Int): AbstractCell
fun getCell(pos: IStruct2i) = getCell(pos.component1(), pos.component2())
/**
* non-null - valid cell and not wrapped around
* null - invalid cell (outside world bounds)
* without wrap-around
*/
fun getCellDirect(x: Int, y: Int): IChunkCell?
fun getCellDirect(x: Int, y: Int): AbstractCell
fun getCellDirect(pos: IStruct2i) = getCellDirect(pos.component1(), pos.component2())
/**
* whenever cell was set
*/
fun setCell(x: Int, y: Int, cell: AbstractCell): Boolean
/**
* Возвращает псевдослучайное Long для заданной позиции
*
@ -45,19 +48,3 @@ interface ICellAccess {
fun randomDoubleFor(pos: Vector2i) = randomDoubleFor(pos.x, pos.y)
}
class OffsetCellAccess(private val parent: ICellAccess, var x: Int, var y: Int) : ICellAccess {
constructor(parent: ICellAccess, offset: IStruct2i) : this(parent, offset.component1(), offset.component2())
override fun getCell(x: Int, y: Int): IChunkCell? {
return parent.getCell(x + this.x, y + this.y)
}
override fun getCellDirect(x: Int, y: Int): IChunkCell? {
return parent.getCellDirect(x + this.x, y + this.y)
}
override fun randomLongFor(x: Int, y: Int) = parent.randomLongFor(x + this.x, y + this.y)
override fun randomDoubleFor(x: Int, y: Int) = parent.randomDoubleFor(x + this.x, y + this.y)
override fun randomLongFor(pos: Vector2i) = parent.randomLongFor(pos.x + this.x, pos.y + this.y)
override fun randomDoubleFor(pos: Vector2i) = parent.randomDoubleFor(pos.x + this.x, pos.y + this.y)
}

View File

@ -1,66 +0,0 @@
package ru.dbotthepony.kstarbound.world.api
import ru.dbotthepony.kstarbound.RegistryObject
import ru.dbotthepony.kstarbound.defs.tile.LiquidDefinition
import ru.dbotthepony.kstarbound.defs.tile.MaterialModifier
import ru.dbotthepony.kstarbound.defs.tile.TileDefinition
import ru.dbotthepony.kstarbound.world.physics.Poly
import ru.dbotthepony.kvector.api.IStruct2i
import ru.dbotthepony.kvector.util2d.AABB
import ru.dbotthepony.kvector.vector.Vector2d
import java.io.DataInputStream
private val rect = Poly(listOf(Vector2d.ZERO, Vector2d(0.0, 1.0), Vector2d(1.0, 1.0), Vector2d(1.0, 0.0)))
interface IChunkCell : IStruct2i {
/**
* absolute (in world)
*/
val x: Int
/**
* absolute (in world)
*/
val y: Int
override fun component1(): Int {
return x
}
override fun component2(): Int {
return y
}
val foreground: ITileState
val background: ITileState
val liquid: ILiquidState
var dungeonId: Int
var biome: Int
var envBiome: Int
var isIndestructible: Boolean
fun read(stream: DataInputStream) {
foreground.read(stream)
background.read(stream)
liquid.read(stream)
stream.skipNBytes(1) // collisionMap
dungeonId = stream.readUnsignedShort()
biome = stream.readUnsignedByte()
envBiome = stream.readUnsignedByte()
isIndestructible = stream.readBoolean()
stream.skipNBytes(1) // unknown
}
companion object {
fun skip(stream: DataInputStream) {
ITileState.skip(stream)
ITileState.skip(stream)
ILiquidState.skip(stream)
stream.skipNBytes(1 + 2 + 1 + 1 + 1 + 1)
}
}
}

View File

@ -1,26 +0,0 @@
package ru.dbotthepony.kstarbound.world.api
import ru.dbotthepony.kstarbound.Registries
import ru.dbotthepony.kstarbound.RegistryObject
import ru.dbotthepony.kstarbound.defs.tile.LiquidDefinition
import java.io.DataInputStream
interface ILiquidState {
var def: LiquidDefinition?
var level: Float
var pressure: Float
var isInfinite: Boolean
fun read(stream: DataInputStream) {
def = Registries.liquid[stream.readUnsignedByte()]?.value
level = stream.readFloat()
pressure = stream.readFloat()
isInfinite = stream.readBoolean()
}
companion object {
fun skip(stream: DataInputStream) {
stream.skipNBytes(1 + 4 + 4 + 1)
}
}
}

View File

@ -5,30 +5,8 @@ import ru.dbotthepony.kvector.api.IStruct2i
// for getting tiles directly, avoiding manual layer specification
interface ITileAccess : ICellAccess {
// relative
fun getTile(x: Int, y: Int): ITileState?
fun getTile(x: Int, y: Int): AbstractTileState?
fun getTile(pos: IStruct2i) = getTile(pos.component1(), pos.component2())
fun getTileDirect(x: Int, y: Int): ITileState?
fun getTileDirect(x: Int, y: Int): AbstractTileState?
fun getTileDirect(pos: IStruct2i) = getTile(pos.component1(), pos.component2())
}
sealed class TileView(parent: ICellAccess) : ITileAccess, ICellAccess by parent {
class Foreground(parent: ICellAccess) : TileView(parent) {
override fun getTile(x: Int, y: Int): ITileState? {
return getCell(x, y)?.foreground
}
override fun getTileDirect(x: Int, y: Int): ITileState? {
return getCellDirect(x, y)?.foreground
}
}
class Background(parent: ICellAccess) : TileView(parent) {
override fun getTile(x: Int, y: Int): ITileState? {
return getCell(x, y)?.background
}
override fun getTileDirect(x: Int, y: Int): ITileState? {
return getCellDirect(x, y)?.background
}
}
}

View File

@ -0,0 +1,20 @@
package ru.dbotthepony.kstarbound.world.api
data class ImmutableCell(
override val foreground: ImmutableTileState = AbstractTileState.EMPTY,
override val background: ImmutableTileState = AbstractTileState.EMPTY,
override val liquid: ImmutableLiquidState = AbstractLiquidState.EMPTY,
override val dungeonId: Int = 0,
override val biome: Int = 0,
override val envBiome: Int = 0,
override val isIndestructible: Boolean = false,
) : AbstractCell() {
override fun immutable(): ImmutableCell {
return this
}
override fun mutable(): MutableCell {
return MutableCell(foreground.mutable(), background.mutable(), liquid.mutable(), dungeonId, biome, envBiome, isIndestructible)
}
}

View File

@ -0,0 +1,18 @@
package ru.dbotthepony.kstarbound.world.api
import ru.dbotthepony.kstarbound.defs.tile.LiquidDefinition
data class ImmutableLiquidState(
override val def: LiquidDefinition? = null,
override val level: Float = 0f,
override val pressure: Float = 0f,
override val isInfinite: Boolean = false,
) : AbstractLiquidState() {
override fun mutable(): MutableLiquidState {
return MutableLiquidState(def, level, pressure, isInfinite)
}
override fun immutable(): ImmutableLiquidState {
return this
}
}

View File

@ -0,0 +1,21 @@
package ru.dbotthepony.kstarbound.world.api
import ru.dbotthepony.kstarbound.defs.tile.BuiltinMetaMaterials
import ru.dbotthepony.kstarbound.defs.tile.MaterialModifier
import ru.dbotthepony.kstarbound.defs.tile.TileDefinition
data class ImmutableTileState(
override var material: TileDefinition = BuiltinMetaMaterials.NULL,
override var modifier: MaterialModifier? = null,
override var color: TileColor = TileColor.DEFAULT,
override var hueShift: Float = 0f,
override var modifierHueShift: Float = 0f,
) : AbstractTileState() {
override fun immutable(): ImmutableTileState {
return this
}
override fun mutable(): MutableTileState {
return MutableTileState(material, modifier, color, hueShift, modifierHueShift)
}
}

View File

@ -0,0 +1,38 @@
package ru.dbotthepony.kstarbound.world.api
import java.io.DataInputStream
data class MutableCell(
override var foreground: MutableTileState = MutableTileState(),
override var background: MutableTileState = MutableTileState(),
override var liquid: MutableLiquidState = MutableLiquidState(),
override var dungeonId: Int = 0,
override var biome: Int = 0,
override var envBiome: Int = 0,
override var isIndestructible: Boolean = false,
) : AbstractCell() {
fun read(stream: DataInputStream): MutableCell {
foreground.read(stream)
background.read(stream)
liquid.read(stream)
stream.skipNBytes(1) // collisionMap
dungeonId = stream.readUnsignedShort()
biome = stream.readUnsignedByte()
envBiome = stream.readUnsignedByte()
isIndestructible = stream.readBoolean()
stream.skipNBytes(1) // unknown
return this
}
override fun immutable(): ImmutableCell {
return ImmutableCell(foreground.immutable(), background.immutable(), liquid.immutable(), dungeonId, biome, envBiome, isIndestructible)
}
override fun mutable(): MutableCell {
return this
}
}

View File

@ -0,0 +1,28 @@
package ru.dbotthepony.kstarbound.world.api
import ru.dbotthepony.kstarbound.Registries
import ru.dbotthepony.kstarbound.defs.tile.LiquidDefinition
import java.io.DataInputStream
data class MutableLiquidState(
override var def: LiquidDefinition? = null,
override var level: Float = 0f,
override var pressure: Float = 0f,
override var isInfinite: Boolean = false,
) : AbstractLiquidState() {
fun read(stream: DataInputStream): MutableLiquidState {
def = Registries.liquid[stream.readUnsignedByte()]?.value
level = stream.readFloat()
pressure = stream.readFloat()
isInfinite = stream.readBoolean()
return this
}
override fun mutable(): MutableLiquidState {
return this
}
override fun immutable(): ImmutableLiquidState {
return ImmutableLiquidState(def, level, pressure, isInfinite)
}
}

View File

@ -1,20 +1,30 @@
package ru.dbotthepony.kstarbound.world.api
import ru.dbotthepony.kstarbound.Registries
import ru.dbotthepony.kstarbound.RegistryObject
import ru.dbotthepony.kstarbound.defs.tile.BuiltinMetaMaterials
import ru.dbotthepony.kstarbound.defs.tile.MaterialModifier
import ru.dbotthepony.kstarbound.defs.tile.TileDefinition
import java.io.DataInputStream
interface ITileState {
var material: TileDefinition
var modifier: MaterialModifier?
var color: TileColor
var hueShift: Float
var modifierHueShift: Float
data class MutableTileState(
override var material: TileDefinition = BuiltinMetaMaterials.NULL,
override var modifier: MaterialModifier? = null,
override var color: TileColor = TileColor.DEFAULT,
override var hueShift: Float = 0f,
override var modifierHueShift: Float = 0f,
) : AbstractTileState() {
override fun immutable(): ImmutableTileState {
val result = ImmutableTileState(material, modifier, color, hueShift, modifierHueShift)
if (result == NULL) return NULL
if (result == EMPTY) return EMPTY
return result
}
fun setHueShift(value: Int) {
override fun mutable(): MutableTileState {
return this
}
fun setHueShift(value: Int): MutableTileState {
if (value < 0) {
hueShift = 0f
} else if (value >= 255) {
@ -22,9 +32,11 @@ interface ITileState {
} else {
hueShift = value / 255f * 360f
}
return this
}
fun setModHueShift(value: Int) {
fun setModHueShift(value: Int): MutableTileState {
if (value < 0) {
modifierHueShift = 0f
} else if (value >= 255) {
@ -32,19 +44,16 @@ interface ITileState {
} else {
modifierHueShift = value / 255f * 360f
}
return this
}
fun read(stream: DataInputStream) {
fun read(stream: DataInputStream): MutableTileState {
material = Registries.tiles[stream.readUnsignedShort()]?.value ?: BuiltinMetaMaterials.EMPTY
setHueShift(stream.read())
color = TileColor.of(stream.read())
modifier = Registries.tileModifiers[stream.readUnsignedShort()]?.value
setModHueShift(stream.read())
return this
}
companion object {
fun skip(stream: DataInputStream) {
stream.skipNBytes(2 + 1 + 1 + 2 + 1)
}
}
}
}

View File

@ -0,0 +1,25 @@
package ru.dbotthepony.kstarbound.world.api
import ru.dbotthepony.kvector.api.IStruct2i
import ru.dbotthepony.kvector.vector.Vector2i
class OffsetCellAccess(private val parent: ICellAccess, var x: Int, var y: Int) : ICellAccess {
constructor(parent: ICellAccess, offset: IStruct2i) : this(parent, offset.component1(), offset.component2())
override fun getCell(x: Int, y: Int): AbstractCell {
return parent.getCell(x + this.x, y + this.y)
}
override fun getCellDirect(x: Int, y: Int): AbstractCell {
return parent.getCellDirect(x + this.x, y + this.y)
}
override fun setCell(x: Int, y: Int, cell: AbstractCell): Boolean {
return parent.setCell(x, y, cell)
}
override fun randomLongFor(x: Int, y: Int) = parent.randomLongFor(x + this.x, y + this.y)
override fun randomDoubleFor(x: Int, y: Int) = parent.randomDoubleFor(x + this.x, y + this.y)
override fun randomLongFor(pos: Vector2i) = parent.randomLongFor(pos.x + this.x, pos.y + this.y)
override fun randomDoubleFor(pos: Vector2i) = parent.randomDoubleFor(pos.x + this.x, pos.y + this.y)
}

View File

@ -0,0 +1,23 @@
package ru.dbotthepony.kstarbound.world.api
sealed class TileView(parent: ICellAccess) : ITileAccess, ICellAccess by parent {
class Foreground(parent: ICellAccess) : TileView(parent) {
override fun getTile(x: Int, y: Int): AbstractTileState? {
return getCell(x, y)?.foreground
}
override fun getTileDirect(x: Int, y: Int): AbstractTileState? {
return getCellDirect(x, y)?.foreground
}
}
class Background(parent: ICellAccess) : TileView(parent) {
override fun getTile(x: Int, y: Int): AbstractTileState? {
return getCell(x, y)?.background
}
override fun getTileDirect(x: Int, y: Int): AbstractTileState? {
return getCellDirect(x, y)?.background
}
}
}