KStarbound/src/main/kotlin/ru/dbotthepony/kstarbound/world/Chunk.kt
2024-02-03 13:46:50 +07:00

264 lines
7.0 KiB
Kotlin
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package ru.dbotthepony.kstarbound.world
import it.unimi.dsi.fastutil.objects.ObjectArrayList
import it.unimi.dsi.fastutil.objects.ObjectArraySet
import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet
import ru.dbotthepony.kstarbound.world.api.AbstractCell
import ru.dbotthepony.kstarbound.world.api.ICellAccess
import ru.dbotthepony.kstarbound.world.api.ImmutableCell
import ru.dbotthepony.kstarbound.world.api.OffsetCellAccess
import ru.dbotthepony.kstarbound.world.api.TileView
import ru.dbotthepony.kstarbound.world.entities.Entity
import ru.dbotthepony.kstarbound.world.entities.WorldObject
import ru.dbotthepony.kvector.arrays.Object2DArray
import ru.dbotthepony.kvector.util2d.AABB
import ru.dbotthepony.kvector.vector.Vector2d
import kotlin.concurrent.withLock
/**
* Чанк мира
*
* Хранит в себе тайлы и ентити внутри себя
*
* Считается, что один тайл имеет форму квадрата и сторона квадрата примерно равна полуметру,
* что будет называться Starbound Unit
*
* Весь игровой мир будет измеряться в Starbound Unit'ах
*/
abstract class Chunk<WorldType : World<WorldType, This>, This : Chunk<WorldType, This>>(
val world: WorldType,
val pos: ChunkPos,
) : ICellAccess {
var changeset = 0
private set
var tileChangeset = 0
private set
var liquidChangeset = 0
private set
var cellChangeset = 0
private set
var foregroundChangeset = 0
private set
var backgroundChangeset = 0
private set
val entities = ReferenceOpenHashSet<Entity>()
val objects = ReferenceOpenHashSet<WorldObject>()
protected val subscribers = ObjectArraySet<IChunkListener>()
// local cells' tile access
val localBackgroundView = TileView.Background(this)
val localForegroundView = TileView.Foreground(this)
// relative world cells access (accessing 0, 0 will lookup cell in world, relative to this chunk)
val worldView = OffsetCellAccess(world, pos.x * CHUNK_SIZE, pos.y * CHUNK_SIZE)
val worldBackgroundView = TileView.Background(worldView)
val worldForegroundView = TileView.Foreground(worldView)
val aabb = aabbBase + Vector2d(pos.x * CHUNK_SIZE.toDouble(), pos.y * CHUNK_SIZE.toDouble())
protected val cells = lazy {
Object2DArray(CHUNK_SIZE, CHUNK_SIZE, AbstractCell.NULL)
}
fun loadCells(source: Object2DArray<out AbstractCell>) {
val ours = cells.value
source.checkSizeEquals(ours)
for (x in 0 until CHUNK_SIZE) {
for (y in 0 until CHUNK_SIZE) {
ours[x, y] = source[x, y].immutable()
}
}
}
override fun getCell(x: Int, y: Int): AbstractCell {
if (!cells.isInitialized())
return AbstractCell.NULL
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 true
}
protected open fun foregroundChanges(x: Int, y: Int, cell: ImmutableCell) {
cellChanges(x, y, cell)
tileChangeset++
foregroundChangeset++
}
protected open fun backgroundChanges(x: Int, y: Int, cell: ImmutableCell) {
cellChanges(x, y, cell)
tileChangeset++
backgroundChangeset++
}
protected open fun liquidChanges(x: Int, y: Int, cell: ImmutableCell) {
cellChanges(x, y, cell)
liquidChangeset++
}
protected open fun cellChanges(x: Int, y: Int, cell: ImmutableCell) {
changeset++
cellChangeset++
subscribers.forEach { it.cellChanges(x, y, cell) }
}
protected inline fun forEachNeighbour(block: (This) -> Unit) {
world.chunkMap[pos.left]?.let(block)
world.chunkMap[pos.right]?.let(block)
world.chunkMap[pos.top]?.let(block)
world.chunkMap[pos.bottom]?.let(block)
world.chunkMap[pos.topLeft]?.let(block)
world.chunkMap[pos.topRight]?.let(block)
world.chunkMap[pos.bottomLeft]?.let(block)
world.chunkMap[pos.bottomRight]?.let(block)
}
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)
}
fun addListener(subscriber: IChunkListener) {
subscribers.add(subscriber)
}
fun removeListener(subscriber: IChunkListener) {
subscribers.remove(subscriber)
}
fun addEntity(entity: Entity) {
world.lock.withLock {
if (!entities.add(entity)) {
throw IllegalArgumentException("Already having having entity $entity")
}
changeset++
subscribers.forEach { it.onEntityAdded(entity) }
}
}
fun transferEntity(entity: Entity, otherChunk: Chunk<*, *>) {
world.lock.withLock {
if (otherChunk == this)
throw IllegalArgumentException("what?")
if (this::class.java != otherChunk::class.java) {
throw IllegalArgumentException("Incompatible types: $this !is $otherChunk")
}
if (!entities.add(entity)) {
throw IllegalArgumentException("Already containing $entity")
}
changeset++
otherChunk.subscribers.forEach { it.onEntityRemoved(entity) }
subscribers.forEach { it.onEntityAdded(entity) }
if (!otherChunk.entities.remove(entity)) {
throw IllegalStateException("Unable to remove $entity from $otherChunk after transfer")
}
}
}
fun removeEntity(entity: Entity) {
world.lock.withLock {
if (!entities.remove(entity)) {
throw IllegalArgumentException("Already not having entity $entity")
}
changeset++
subscribers.forEach { it.onEntityRemoved(entity) }
}
}
override fun toString(): String {
return "${this::class.simpleName}(pos=$pos, entityCount=${entities.size}, world=$world)"
}
fun addObject(obj: WorldObject) {
world.lock.withLock {
if (!objects.add(obj))
throw IllegalStateException("$this already has object $obj!")
if (!world.objects.add(obj))
throw IllegalStateException("World $world already has object $obj!")
obj.spawn(world)
subscribers.forEach { it.onObjectAdded(obj) }
}
}
fun removeObject(obj: WorldObject) {
world.lock.withLock {
if (!objects.remove(obj))
throw IllegalStateException("$this does not have object $obj!")
if (!world.objects.remove(obj))
throw IllegalStateException("World $world does not have object $obj!")
subscribers.forEach { it.onObjectRemoved(obj) }
}
}
open fun remove() {
world.lock.withLock {
for (obj in ObjectArrayList(objects)) {
if (!world.objects.remove(obj)) {
throw IllegalStateException("World $world does not have object $obj!")
}
}
for (ent in ObjectArrayList(entities)) {
ent.chunk = null
}
}
}
open fun think() {
}
companion object {
private val aabbBase = AABB(
Vector2d.ZERO,
Vector2d(CHUNK_SIZE.toDouble(), CHUNK_SIZE.toDouble()),
)
}
}