Performance improvement bits
This commit is contained in:
parent
164040b45b
commit
a46269aa15
@ -25,6 +25,7 @@ import ru.dbotthepony.kstarbound.world.Universe
|
|||||||
import ru.dbotthepony.kstarbound.world.UniversePos
|
import ru.dbotthepony.kstarbound.world.UniversePos
|
||||||
import ru.dbotthepony.kstarbound.world.WorldGeometry
|
import ru.dbotthepony.kstarbound.world.WorldGeometry
|
||||||
import ru.dbotthepony.kstarbound.world.physics.Poly
|
import ru.dbotthepony.kstarbound.world.physics.Poly
|
||||||
|
import java.time.Duration
|
||||||
import java.util.concurrent.CopyOnWriteArrayList
|
import java.util.concurrent.CopyOnWriteArrayList
|
||||||
import java.util.random.RandomGenerator
|
import java.util.random.RandomGenerator
|
||||||
|
|
||||||
@ -299,17 +300,19 @@ class WorldTemplate(val geometry: WorldGeometry) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private val cellCache = Caffeine.newBuilder()
|
private val cellCache = Caffeine.newBuilder()
|
||||||
.maximumSize(50_000L)
|
.maximumSize(150_000L)
|
||||||
//.expireAfterAccess(Duration.ofMinutes(1))
|
.expireAfterAccess(Duration.ofMinutes(1))
|
||||||
//.executor(Starbound.EXECUTOR)
|
.executor(Starbound.EXECUTOR)
|
||||||
//.scheduler(Starbound) // don't specify scheduler since this cache is accessed very frequently
|
.scheduler(Starbound)
|
||||||
.build<Vector2i, CellInfo> { (x, y) -> cellInfo0(x, y) }
|
.build<Vector2i, CellInfo> { (x, y) -> cellInfo0(x, y) }
|
||||||
|
|
||||||
fun cellInfo(x: Int, y: Int): CellInfo {
|
fun cellInfo(x: Int, y: Int): CellInfo {
|
||||||
|
worldLayout ?: return CellInfo(x, y)
|
||||||
return cellCache.get(Vector2i(x, y))
|
return cellCache.get(Vector2i(x, y))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun cellInfo(pos: Vector2i): CellInfo {
|
fun cellInfo(pos: Vector2i): CellInfo {
|
||||||
|
worldLayout ?: return CellInfo(pos.x, pos.y)
|
||||||
return cellCache.get(pos)
|
return cellCache.get(pos)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,6 +51,7 @@ import ru.dbotthepony.kstarbound.world.api.TileColor
|
|||||||
import ru.dbotthepony.kstarbound.world.entities.AbstractEntity
|
import ru.dbotthepony.kstarbound.world.entities.AbstractEntity
|
||||||
import ru.dbotthepony.kstarbound.world.entities.ItemDropEntity
|
import ru.dbotthepony.kstarbound.world.entities.ItemDropEntity
|
||||||
import java.util.concurrent.CompletableFuture
|
import java.util.concurrent.CompletableFuture
|
||||||
|
import java.util.concurrent.CopyOnWriteArrayList
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import java.util.concurrent.locks.ReentrantLock
|
import java.util.concurrent.locks.ReentrantLock
|
||||||
import java.util.function.Predicate
|
import java.util.function.Predicate
|
||||||
@ -68,8 +69,9 @@ class ServerChunk(world: ServerWorld, pos: ChunkPos) : Chunk<ServerWorld, Server
|
|||||||
private var idleTicks = 0
|
private var idleTicks = 0
|
||||||
private var ticks = 0
|
private var ticks = 0
|
||||||
private val targetState = Channel<ChunkState>(Int.MAX_VALUE)
|
private val targetState = Channel<ChunkState>(Int.MAX_VALUE)
|
||||||
private val permanent = ArrayList<Ticket>()
|
private val permanent = CopyOnWriteArrayList<Ticket>()
|
||||||
private val temporary = ObjectAVLTreeSet<TimedTicket>()
|
private val temporary = ObjectAVLTreeSet<TimedTicket>()
|
||||||
|
private val temporaryList = CopyOnWriteArrayList<TimedTicket>()
|
||||||
private var nextTicketID = 0
|
private var nextTicketID = 0
|
||||||
// ticket lock because tickets *could* be canceled (or created) concurrently
|
// ticket lock because tickets *could* be canceled (or created) concurrently
|
||||||
// BUT, front-end ticket creation in ServerWorld is expected to be called only on ServerWorld's thread
|
// BUT, front-end ticket creation in ServerWorld is expected to be called only on ServerWorld's thread
|
||||||
@ -285,29 +287,13 @@ class ServerChunk(world: ServerWorld, pos: ChunkPos) : Chunk<ServerWorld, Server
|
|||||||
override fun cellChanges(x: Int, y: Int, cell: ImmutableCell) {
|
override fun cellChanges(x: Int, y: Int, cell: ImmutableCell) {
|
||||||
super.cellChanges(x, y, cell)
|
super.cellChanges(x, y, cell)
|
||||||
|
|
||||||
val permanent: List<Ticket>
|
|
||||||
val temporary: List<TimedTicket>
|
|
||||||
|
|
||||||
ticketsLock.withLock {
|
|
||||||
permanent = ObjectArrayList(this.permanent)
|
|
||||||
temporary = ObjectArrayList(this.temporary)
|
|
||||||
}
|
|
||||||
|
|
||||||
permanent.forEach { if (it.targetState <= state) it.listener?.onCellChanges(x, y, cell) }
|
permanent.forEach { if (it.targetState <= state) it.listener?.onCellChanges(x, y, cell) }
|
||||||
temporary.forEach { if (it.targetState <= state) it.listener?.onCellChanges(x, y, cell) }
|
temporaryList.forEach { if (it.targetState <= state) it.listener?.onCellChanges(x, y, cell) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onTileHealthUpdate(x: Int, y: Int, isBackground: Boolean, health: TileHealth) {
|
private fun onTileHealthUpdate(x: Int, y: Int, isBackground: Boolean, health: TileHealth) {
|
||||||
val permanent: List<Ticket>
|
|
||||||
val temporary: List<TimedTicket>
|
|
||||||
|
|
||||||
ticketsLock.withLock {
|
|
||||||
permanent = ObjectArrayList(this.permanent)
|
|
||||||
temporary = ObjectArrayList(this.temporary)
|
|
||||||
}
|
|
||||||
|
|
||||||
permanent.forEach { if (it.targetState <= state) it.listener?.onTileHealthUpdate(x, y, isBackground, health) }
|
permanent.forEach { if (it.targetState <= state) it.listener?.onTileHealthUpdate(x, y, isBackground, health) }
|
||||||
temporary.forEach { if (it.targetState <= state) it.listener?.onTileHealthUpdate(x, y, isBackground, health) }
|
temporaryList.forEach { if (it.targetState <= state) it.listener?.onTileHealthUpdate(x, y, isBackground, health) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private abstract inner class AbstractTicket(val targetState: ChunkState) : ITicket {
|
private abstract inner class AbstractTicket(val targetState: ChunkState) : ITicket {
|
||||||
@ -360,11 +346,13 @@ class ServerChunk(world: ServerWorld, pos: ChunkPos) : Chunk<ServerWorld, Server
|
|||||||
|
|
||||||
init {
|
init {
|
||||||
temporary.add(this)
|
temporary.add(this)
|
||||||
|
temporaryList.add(this)
|
||||||
this@ServerChunk.targetState.trySend(targetState)
|
this@ServerChunk.targetState.trySend(targetState)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun cancel0() {
|
override fun cancel0() {
|
||||||
temporary.remove(this)
|
temporary.remove(this)
|
||||||
|
temporaryList.remove(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun prolong(ticks: Int) {
|
override fun prolong(ticks: Int) {
|
||||||
@ -375,7 +363,12 @@ class ServerChunk(world: ServerWorld, pos: ChunkPos) : Chunk<ServerWorld, Server
|
|||||||
|
|
||||||
temporary.remove(this)
|
temporary.remove(this)
|
||||||
expiresAt += ticks
|
expiresAt += ticks
|
||||||
if (timeRemaining > 0) temporary.add(this)
|
|
||||||
|
if (timeRemaining > 0) {
|
||||||
|
temporary.add(this)
|
||||||
|
} else {
|
||||||
|
temporaryList.remove(this)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -385,16 +378,8 @@ class ServerChunk(world: ServerWorld, pos: ChunkPos) : Chunk<ServerWorld, Server
|
|||||||
require(newState >= state) { "Tried to downgrade $this state from $state to $newState" }
|
require(newState >= state) { "Tried to downgrade $this state from $state to $newState" }
|
||||||
this.state = newState
|
this.state = newState
|
||||||
|
|
||||||
val permanent: List<Ticket>
|
|
||||||
val temporary: List<TimedTicket>
|
|
||||||
|
|
||||||
ticketsLock.withLock {
|
|
||||||
permanent = ObjectArrayList(this.permanent)
|
|
||||||
temporary = ObjectArrayList(this.temporary)
|
|
||||||
}
|
|
||||||
|
|
||||||
permanent.forEach { if (it.targetState <= state) it.chunk.complete(this) }
|
permanent.forEach { if (it.targetState <= state) it.chunk.complete(this) }
|
||||||
temporary.forEach { if (it.targetState <= state) it.chunk.complete(this) }
|
temporaryList.forEach { if (it.targetState <= state) it.chunk.complete(this) }
|
||||||
}
|
}
|
||||||
|
|
||||||
data class DamageResult(val result: TileDamageResult, val health: TileHealth? = null, val stateBefore: AbstractCell? = null)
|
data class DamageResult(val result: TileDamageResult, val health: TileHealth? = null, val stateBefore: AbstractCell? = null)
|
||||||
@ -498,6 +483,7 @@ class ServerChunk(world: ServerWorld, pos: ChunkPos) : Chunk<ServerWorld, Server
|
|||||||
val ticket = temporary.first()
|
val ticket = temporary.first()
|
||||||
ticket.isCanceled = true
|
ticket.isCanceled = true
|
||||||
temporary.remove(ticket)
|
temporary.remove(ticket)
|
||||||
|
temporaryList.remove(ticket)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,6 +8,10 @@ import it.unimi.dsi.fastutil.objects.Object2IntAVLTreeMap
|
|||||||
import it.unimi.dsi.fastutil.objects.ObjectAVLTreeSet
|
import it.unimi.dsi.fastutil.objects.ObjectAVLTreeSet
|
||||||
import ru.dbotthepony.kommons.util.AABB
|
import ru.dbotthepony.kommons.util.AABB
|
||||||
import ru.dbotthepony.kommons.util.KOptional
|
import ru.dbotthepony.kommons.util.KOptional
|
||||||
|
import ru.dbotthepony.kommons.vector.Vector2d
|
||||||
|
import ru.dbotthepony.kommons.vector.Vector2i
|
||||||
|
import ru.dbotthepony.kstarbound.world.entities.AbstractEntity
|
||||||
|
import ru.dbotthepony.kstarbound.world.entities.tile.TileEntity
|
||||||
import java.util.concurrent.atomic.AtomicInteger
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
import java.util.function.Predicate
|
import java.util.function.Predicate
|
||||||
|
|
||||||
@ -19,7 +23,7 @@ import java.util.function.Predicate
|
|||||||
// and allowing one entity to have multiple bounding boxes,
|
// and allowing one entity to have multiple bounding boxes,
|
||||||
// while also setting up ground for better spatial index strategies, if they
|
// while also setting up ground for better spatial index strategies, if they
|
||||||
// have to be done in the future.
|
// have to be done in the future.
|
||||||
class SpatialIndex<T>(val geometry: WorldGeometry) {
|
class EntityIndex(val geometry: WorldGeometry) {
|
||||||
private val lock = Any()
|
private val lock = Any()
|
||||||
private val map = Long2ObjectOpenHashMap<Sector>()
|
private val map = Long2ObjectOpenHashMap<Sector>()
|
||||||
private val factory = Long2ObjectFunction { Sector(it) }
|
private val factory = Long2ObjectFunction { Sector(it) }
|
||||||
@ -55,7 +59,7 @@ class SpatialIndex<T>(val geometry: WorldGeometry) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class Entry(val value: T) : Comparable<Entry> {
|
inner class Entry(val value: AbstractEntity) : Comparable<Entry> {
|
||||||
private val sectors = Object2IntAVLTreeMap<Sector>()
|
private val sectors = Object2IntAVLTreeMap<Sector>()
|
||||||
val id = counter.getAndIncrement()
|
val id = counter.getAndIncrement()
|
||||||
private val fixtures = ArrayList<Fixture>(1)
|
private val fixtures = ArrayList<Fixture>(1)
|
||||||
@ -246,8 +250,8 @@ class SpatialIndex<T>(val geometry: WorldGeometry) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun query(rect: AABB, filter: Predicate<T> = Predicate { true }, withEdges: Boolean = true): List<T> {
|
fun query(rect: AABB, filter: Predicate<in AbstractEntity> = Predicate { true }, withEdges: Boolean = true): List<AbstractEntity> {
|
||||||
val entriesDirect = ArrayList<T>()
|
val entriesDirect = ArrayList<AbstractEntity>()
|
||||||
|
|
||||||
iterate(rect, withEdges = withEdges, visitor = {
|
iterate(rect, withEdges = withEdges, visitor = {
|
||||||
if (filter.test(it)) entriesDirect.add(it)
|
if (filter.test(it)) entriesDirect.add(it)
|
||||||
@ -256,23 +260,33 @@ class SpatialIndex<T>(val geometry: WorldGeometry) {
|
|||||||
return entriesDirect
|
return entriesDirect
|
||||||
}
|
}
|
||||||
|
|
||||||
fun any(rect: AABB, filter: Predicate<T> = Predicate { true }, withEdges: Boolean = true): Boolean {
|
fun any(rect: AABB, filter: Predicate<in AbstractEntity> = Predicate { true }, withEdges: Boolean = true): Boolean {
|
||||||
return walk(rect, withEdges = withEdges, visitor = {
|
return walk(rect, withEdges = withEdges, visitor = {
|
||||||
if (filter.test(it)) KOptional(true) else KOptional()
|
if (filter.test(it)) KOptional(true) else KOptional()
|
||||||
}).orElse(false)
|
}).orElse(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun all(rect: AABB, filter: Predicate<T> = Predicate { true }, withEdges: Boolean = true): Boolean {
|
fun all(rect: AABB, filter: Predicate<in AbstractEntity> = Predicate { true }, withEdges: Boolean = true): Boolean {
|
||||||
return walk(rect, withEdges = withEdges, visitor = {
|
return walk(rect, withEdges = withEdges, visitor = {
|
||||||
if (!filter.test(it)) KOptional(false) else KOptional()
|
if (!filter.test(it)) KOptional(false) else KOptional()
|
||||||
}).orElse(true)
|
}).orElse(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun iterate(rect: AABB, visitor: (T) -> Unit, withEdges: Boolean = true) {
|
fun first(rect: AABB, filter: Predicate<in AbstractEntity> = Predicate { true }, withEdges: Boolean = true): AbstractEntity? {
|
||||||
|
return walk(rect, withEdges = withEdges, visitor = {
|
||||||
|
if (filter.test(it)) KOptional(it) else KOptional()
|
||||||
|
}).orNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun tileEntityAt(pos: Vector2i): TileEntity? {
|
||||||
|
return first(AABB(pos.toDoubleVector(), pos.toDoubleVector() + Vector2d.POSITIVE_XY), Predicate { it is TileEntity && pos in it.occupySpaces }) as TileEntity?
|
||||||
|
}
|
||||||
|
|
||||||
|
fun iterate(rect: AABB, visitor: (AbstractEntity) -> Unit, withEdges: Boolean = true) {
|
||||||
walk<Unit>(rect, { visitor(it); KOptional() }, withEdges)
|
walk<Unit>(rect, { visitor(it); KOptional() }, withEdges)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <V> walk(rect: AABB, visitor: (T) -> KOptional<V>, withEdges: Boolean = true): KOptional<V> {
|
fun <V> walk(rect: AABB, visitor: (AbstractEntity) -> KOptional<V>, withEdges: Boolean = true): KOptional<V> {
|
||||||
val seen = IntAVLTreeSet()
|
val seen = IntAVLTreeSet()
|
||||||
|
|
||||||
for (actualRegion in geometry.split(rect).first) {
|
for (actualRegion in geometry.split(rect).first) {
|
@ -17,10 +17,7 @@ import ru.dbotthepony.kommons.util.AABB
|
|||||||
import ru.dbotthepony.kommons.util.AABBi
|
import ru.dbotthepony.kommons.util.AABBi
|
||||||
import ru.dbotthepony.kommons.vector.Vector2d
|
import ru.dbotthepony.kommons.vector.Vector2d
|
||||||
import ru.dbotthepony.kommons.vector.Vector2i
|
import ru.dbotthepony.kommons.vector.Vector2i
|
||||||
import ru.dbotthepony.kstarbound.Registry
|
|
||||||
import ru.dbotthepony.kstarbound.Starbound
|
import ru.dbotthepony.kstarbound.Starbound
|
||||||
import ru.dbotthepony.kstarbound.defs.tile.TileDefinition
|
|
||||||
import ru.dbotthepony.kstarbound.defs.tile.isNotEmptyTile
|
|
||||||
import ru.dbotthepony.kstarbound.defs.world.WorldStructure
|
import ru.dbotthepony.kstarbound.defs.world.WorldStructure
|
||||||
import ru.dbotthepony.kstarbound.defs.world.WorldTemplate
|
import ru.dbotthepony.kstarbound.defs.world.WorldTemplate
|
||||||
import ru.dbotthepony.kstarbound.json.mergeJson
|
import ru.dbotthepony.kstarbound.json.mergeJson
|
||||||
@ -111,6 +108,7 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
|
|||||||
private val map = Long2ObjectOpenHashMap<ChunkType>()
|
private val map = Long2ObjectOpenHashMap<ChunkType>()
|
||||||
// see CONCURRENT_SPARSE_CHUNK_MAP
|
// see CONCURRENT_SPARSE_CHUNK_MAP
|
||||||
private val lock = Any()
|
private val lock = Any()
|
||||||
|
private val list = CopyOnWriteArrayList<ChunkType>()
|
||||||
|
|
||||||
override fun get(x: Int, y: Int): ChunkType? {
|
override fun get(x: Int, y: Int): ChunkType? {
|
||||||
if (!geometry.x.inBoundsChunk(x) || !geometry.y.inBoundsChunk(y)) return null
|
if (!geometry.x.inBoundsChunk(x) || !geometry.y.inBoundsChunk(y)) return null
|
||||||
@ -130,10 +128,18 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
|
|||||||
|
|
||||||
if (CONCURRENT_SPARSE_CHUNK_MAP) {
|
if (CONCURRENT_SPARSE_CHUNK_MAP) {
|
||||||
synchronized(lock) {
|
synchronized(lock) {
|
||||||
return map[index] ?: chunkFactory(ChunkPos(x, y)).also { map[index] = it; onChunkCreated(it) }
|
return map[index] ?: chunkFactory(ChunkPos(x, y)).also {
|
||||||
|
list.add(it)
|
||||||
|
map[index] = it
|
||||||
|
onChunkCreated(it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return map[index] ?: chunkFactory(ChunkPos(x, y)).also { map[index] = it; onChunkCreated(it) }
|
return map[index] ?: chunkFactory(ChunkPos(x, y)).also {
|
||||||
|
list.add(it)
|
||||||
|
map[index] = it
|
||||||
|
onChunkCreated(it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -147,6 +153,7 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
|
|||||||
if (chunk != null) {
|
if (chunk != null) {
|
||||||
chunk.remove()
|
chunk.remove()
|
||||||
onChunkRemoved(chunk)
|
onChunkRemoved(chunk)
|
||||||
|
list.add(chunk)
|
||||||
map.remove(index)
|
map.remove(index)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -156,19 +163,14 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
|
|||||||
if (chunk != null) {
|
if (chunk != null) {
|
||||||
chunk.remove()
|
chunk.remove()
|
||||||
onChunkRemoved(chunk)
|
onChunkRemoved(chunk)
|
||||||
|
list.add(chunk)
|
||||||
map.remove(index)
|
map.remove(index)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun chunks(): List<ChunkType> {
|
override fun chunks(): List<ChunkType> {
|
||||||
if (CONCURRENT_SPARSE_CHUNK_MAP) {
|
return list
|
||||||
synchronized(lock) {
|
|
||||||
return ObjectArrayList(map.values)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return ObjectArrayList(map.values)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override val size: Int
|
override val size: Int
|
||||||
@ -177,11 +179,15 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
|
|||||||
|
|
||||||
inner class ArrayChunkMap : ChunkMap() {
|
inner class ArrayChunkMap : ChunkMap() {
|
||||||
private val map = Object2DArray.nulls<ChunkType>(divideUp(geometry.size.x, CHUNK_SIZE), divideUp(geometry.size.y, CHUNK_SIZE))
|
private val map = Object2DArray.nulls<ChunkType>(divideUp(geometry.size.x, CHUNK_SIZE), divideUp(geometry.size.y, CHUNK_SIZE))
|
||||||
private val existing = ObjectAVLTreeSet<ChunkPos>()
|
private val list = CopyOnWriteArrayList<ChunkType>()
|
||||||
|
|
||||||
override fun compute(x: Int, y: Int): ChunkType? {
|
override fun compute(x: Int, y: Int): ChunkType? {
|
||||||
if (!geometry.x.inBoundsChunk(x) || !geometry.y.inBoundsChunk(y)) return null
|
if (!geometry.x.inBoundsChunk(x) || !geometry.y.inBoundsChunk(y)) return null
|
||||||
return map[x, y] ?: chunkFactory(ChunkPos(x, y)).also { existing.add(ChunkPos(x, y)); map[x, y] = it; onChunkCreated(it) }
|
return map[x, y] ?: chunkFactory(ChunkPos(x, y)).also {
|
||||||
|
list.add(it)
|
||||||
|
map[x, y] = it
|
||||||
|
onChunkCreated(it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun get(x: Int, y: Int): ChunkType? {
|
override fun get(x: Int, y: Int): ChunkType? {
|
||||||
@ -198,17 +204,17 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
|
|||||||
if (chunk != null) {
|
if (chunk != null) {
|
||||||
chunk.remove()
|
chunk.remove()
|
||||||
onChunkRemoved(chunk)
|
onChunkRemoved(chunk)
|
||||||
existing.remove(ChunkPos(x, y))
|
list.remove(chunk)
|
||||||
map[x, y] = null
|
map[x, y] = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun chunks(): List<ChunkType> {
|
override fun chunks(): List<ChunkType> {
|
||||||
return existing.map { (x, y) -> map[x, y] ?: throw ConcurrentModificationException() }
|
return list
|
||||||
}
|
}
|
||||||
|
|
||||||
override val size: Int
|
override val size: Int
|
||||||
get() = existing.size
|
get() = list.size
|
||||||
}
|
}
|
||||||
|
|
||||||
val chunkMap: ChunkMap = if (geometry.size.x <= 32000 && geometry.size.y <= 32000) ArrayChunkMap() else SparseChunkMap()
|
val chunkMap: ChunkMap = if (geometry.size.x <= 32000 && geometry.size.y <= 32000) ArrayChunkMap() else SparseChunkMap()
|
||||||
@ -227,7 +233,7 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
|
|||||||
|
|
||||||
val entities = Int2ObjectOpenHashMap<AbstractEntity>()
|
val entities = Int2ObjectOpenHashMap<AbstractEntity>()
|
||||||
val entityList = CopyOnWriteArrayList<AbstractEntity>()
|
val entityList = CopyOnWriteArrayList<AbstractEntity>()
|
||||||
val entityIndex = SpatialIndex<AbstractEntity>(geometry)
|
val entityIndex = EntityIndex(geometry)
|
||||||
val dynamicEntities = ArrayList<DynamicEntity>()
|
val dynamicEntities = ArrayList<DynamicEntity>()
|
||||||
|
|
||||||
var playerSpawnPosition = Vector2d.ZERO
|
var playerSpawnPosition = Vector2d.ZERO
|
||||||
|
@ -7,7 +7,6 @@ import ru.dbotthepony.kommons.io.koptional
|
|||||||
import ru.dbotthepony.kommons.util.AABB
|
import ru.dbotthepony.kommons.util.AABB
|
||||||
import ru.dbotthepony.kommons.util.KOptional
|
import ru.dbotthepony.kommons.util.KOptional
|
||||||
import ru.dbotthepony.kommons.vector.Vector2d
|
import ru.dbotthepony.kommons.vector.Vector2d
|
||||||
import ru.dbotthepony.kstarbound.Starbound
|
|
||||||
import ru.dbotthepony.kstarbound.client.StarboundClient
|
import ru.dbotthepony.kstarbound.client.StarboundClient
|
||||||
import ru.dbotthepony.kstarbound.client.render.LayeredRenderer
|
import ru.dbotthepony.kstarbound.client.render.LayeredRenderer
|
||||||
import ru.dbotthepony.kstarbound.client.world.ClientWorld
|
import ru.dbotthepony.kstarbound.client.world.ClientWorld
|
||||||
@ -22,7 +21,7 @@ import ru.dbotthepony.kstarbound.network.syncher.networkedData
|
|||||||
import ru.dbotthepony.kstarbound.server.world.ServerWorld
|
import ru.dbotthepony.kstarbound.server.world.ServerWorld
|
||||||
import ru.dbotthepony.kstarbound.util.MailboxExecutorService
|
import ru.dbotthepony.kstarbound.util.MailboxExecutorService
|
||||||
import ru.dbotthepony.kstarbound.world.LightCalculator
|
import ru.dbotthepony.kstarbound.world.LightCalculator
|
||||||
import ru.dbotthepony.kstarbound.world.SpatialIndex
|
import ru.dbotthepony.kstarbound.world.EntityIndex
|
||||||
import ru.dbotthepony.kstarbound.world.World
|
import ru.dbotthepony.kstarbound.world.World
|
||||||
import java.io.DataOutputStream
|
import java.io.DataOutputStream
|
||||||
import java.util.function.Consumer
|
import java.util.function.Consumer
|
||||||
@ -104,7 +103,7 @@ abstract class AbstractEntity(path: String) : JsonDriven(path), Comparable<Abstr
|
|||||||
val networkGroup = MasterElement(NetworkedGroup())
|
val networkGroup = MasterElement(NetworkedGroup())
|
||||||
abstract fun writeNetwork(stream: DataOutputStream, isLegacy: Boolean)
|
abstract fun writeNetwork(stream: DataOutputStream, isLegacy: Boolean)
|
||||||
|
|
||||||
protected var spatialEntry: SpatialIndex<AbstractEntity>.Entry? = null
|
protected var spatialEntry: EntityIndex.Entry? = null
|
||||||
private set
|
private set
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -2,11 +2,10 @@ package ru.dbotthepony.kstarbound.world.entities
|
|||||||
|
|
||||||
import ru.dbotthepony.kommons.math.RGBAColor
|
import ru.dbotthepony.kommons.math.RGBAColor
|
||||||
import ru.dbotthepony.kommons.util.AABB
|
import ru.dbotthepony.kommons.util.AABB
|
||||||
import ru.dbotthepony.kstarbound.Globals
|
|
||||||
import ru.dbotthepony.kstarbound.client.StarboundClient
|
import ru.dbotthepony.kstarbound.client.StarboundClient
|
||||||
import ru.dbotthepony.kstarbound.client.render.LayeredRenderer
|
import ru.dbotthepony.kstarbound.client.render.LayeredRenderer
|
||||||
import ru.dbotthepony.kstarbound.client.render.RenderLayer
|
import ru.dbotthepony.kstarbound.client.render.RenderLayer
|
||||||
import ru.dbotthepony.kstarbound.world.SpatialIndex
|
import ru.dbotthepony.kstarbound.world.EntityIndex
|
||||||
import ru.dbotthepony.kstarbound.world.World
|
import ru.dbotthepony.kstarbound.world.World
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -43,7 +42,7 @@ abstract class DynamicEntity(path: String) : AbstractEntity(path) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected var metaFixture: SpatialIndex<AbstractEntity>.Entry.Fixture? = null
|
protected var metaFixture: EntityIndex.Entry.Fixture? = null
|
||||||
private set
|
private set
|
||||||
|
|
||||||
override fun onJoinWorld(world: World<*, *>) {
|
override fun onJoinWorld(world: World<*, *>) {
|
||||||
|
@ -23,7 +23,7 @@ import ru.dbotthepony.kstarbound.network.syncher.networkedData
|
|||||||
import ru.dbotthepony.kstarbound.network.syncher.networkedFixedPoint
|
import ru.dbotthepony.kstarbound.network.syncher.networkedFixedPoint
|
||||||
import ru.dbotthepony.kstarbound.network.syncher.networkedFloat
|
import ru.dbotthepony.kstarbound.network.syncher.networkedFloat
|
||||||
import ru.dbotthepony.kstarbound.network.syncher.networkedPoly
|
import ru.dbotthepony.kstarbound.network.syncher.networkedPoly
|
||||||
import ru.dbotthepony.kstarbound.world.SpatialIndex
|
import ru.dbotthepony.kstarbound.world.EntityIndex
|
||||||
import ru.dbotthepony.kstarbound.world.World
|
import ru.dbotthepony.kstarbound.world.World
|
||||||
import ru.dbotthepony.kstarbound.world.physics.CollisionPoly
|
import ru.dbotthepony.kstarbound.world.physics.CollisionPoly
|
||||||
import ru.dbotthepony.kstarbound.world.physics.CollisionType
|
import ru.dbotthepony.kstarbound.world.physics.CollisionType
|
||||||
@ -37,9 +37,9 @@ import kotlin.math.sin
|
|||||||
open class MovementController() {
|
open class MovementController() {
|
||||||
private var world0: World<*, *>? = null
|
private var world0: World<*, *>? = null
|
||||||
val world: World<*, *> get() = world0!!
|
val world: World<*, *> get() = world0!!
|
||||||
private var spatialEntry: SpatialIndex<*>.Entry? = null
|
private var spatialEntry: EntityIndex.Entry? = null
|
||||||
|
|
||||||
fun initialize(world: World<*, *>, entry: SpatialIndex<*>.Entry?) {
|
fun initialize(world: World<*, *>, entry: EntityIndex.Entry?) {
|
||||||
fixtures.clear()
|
fixtures.clear()
|
||||||
spatialEntry?.remove()
|
spatialEntry?.remove()
|
||||||
this.world0 = world
|
this.world0 = world
|
||||||
@ -135,7 +135,7 @@ open class MovementController() {
|
|||||||
private val relativeXSurfaceVelocity = networkGroup.add(networkedFixedPoint(0.0125).also { it.interpolator = Interpolator.Linear })
|
private val relativeXSurfaceVelocity = networkGroup.add(networkedFixedPoint(0.0125).also { it.interpolator = Interpolator.Linear })
|
||||||
private val relativeYSurfaceVelocity = networkGroup.add(networkedFixedPoint(0.0125).also { it.interpolator = Interpolator.Linear })
|
private val relativeYSurfaceVelocity = networkGroup.add(networkedFixedPoint(0.0125).also { it.interpolator = Interpolator.Linear })
|
||||||
|
|
||||||
private val fixtures = ArrayList<SpatialIndex<*>.Entry.Fixture>()
|
private val fixtures = ArrayList<EntityIndex.Entry.Fixture>()
|
||||||
var fixturesChangeset: Int = 0
|
var fixturesChangeset: Int = 0
|
||||||
private set
|
private set
|
||||||
|
|
||||||
|
@ -24,9 +24,6 @@ import ru.dbotthepony.kstarbound.network.syncher.networkedEnumExtraStupid
|
|||||||
import ru.dbotthepony.kstarbound.network.syncher.networkedEventCounter
|
import ru.dbotthepony.kstarbound.network.syncher.networkedEventCounter
|
||||||
import ru.dbotthepony.kstarbound.network.syncher.networkedFixedPoint
|
import ru.dbotthepony.kstarbound.network.syncher.networkedFixedPoint
|
||||||
import ru.dbotthepony.kstarbound.network.syncher.networkedString
|
import ru.dbotthepony.kstarbound.network.syncher.networkedString
|
||||||
import ru.dbotthepony.kstarbound.world.SpatialIndex
|
|
||||||
import ru.dbotthepony.kstarbound.world.World
|
|
||||||
import ru.dbotthepony.kstarbound.world.entities.AbstractEntity
|
|
||||||
import ru.dbotthepony.kstarbound.world.entities.Animator
|
import ru.dbotthepony.kstarbound.world.entities.Animator
|
||||||
import ru.dbotthepony.kstarbound.world.entities.HumanoidActorEntity
|
import ru.dbotthepony.kstarbound.world.entities.HumanoidActorEntity
|
||||||
import ru.dbotthepony.kstarbound.world.entities.StatusController
|
import ru.dbotthepony.kstarbound.world.entities.StatusController
|
||||||
|
@ -195,21 +195,27 @@ open class WorldObject(val config: Registry.Entry<ObjectDefinition>) : TileEntit
|
|||||||
val chatPortrait by networkedString().also { networkGroup.upstream.add(it) }
|
val chatPortrait by networkedString().also { networkGroup.upstream.add(it) }
|
||||||
val chatConfig by networkedJsonElement().also { networkGroup.upstream.add(it) }
|
val chatConfig by networkedJsonElement().also { networkGroup.upstream.add(it) }
|
||||||
|
|
||||||
inner class WireNode(val position: Vector2i) {
|
inner class WireNode(val position: Vector2i, val isInput: Boolean) {
|
||||||
var state by networkedBoolean().also { networkGroup.upstream.add(it) }
|
var state by networkedBoolean().also { networkGroup.upstream.add(it) }
|
||||||
val connections = NetworkedList(WireConnection.CODEC).also { networkGroup.upstream.add(it) }
|
val connections = NetworkedList(WireConnection.CODEC).also { networkGroup.upstream.add(it) }
|
||||||
|
|
||||||
|
fun addConnection(connection: WireConnection) {
|
||||||
|
if (connection !in connections) {
|
||||||
|
connections.add(connection)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val inputNodes: ImmutableList<WireNode> = lookupProperty(JsonPath("inputNodes")) { JsonArray() }
|
val inputNodes: ImmutableList<WireNode> = lookupProperty(JsonPath("inputNodes")) { JsonArray() }
|
||||||
.asJsonArray
|
.asJsonArray
|
||||||
.stream()
|
.stream()
|
||||||
.map { WireNode(vectors.fromJsonTree(it)) }
|
.map { WireNode(vectors.fromJsonTree(it), true) }
|
||||||
.collect(ImmutableList.toImmutableList())
|
.collect(ImmutableList.toImmutableList())
|
||||||
|
|
||||||
val outputNodes: ImmutableList<WireNode> = lookupProperty(JsonPath("outputNodes")) { JsonArray() }
|
val outputNodes: ImmutableList<WireNode> = lookupProperty(JsonPath("outputNodes")) { JsonArray() }
|
||||||
.asJsonArray
|
.asJsonArray
|
||||||
.stream()
|
.stream()
|
||||||
.map { WireNode(vectors.fromJsonTree(it)) }
|
.map { WireNode(vectors.fromJsonTree(it), false) }
|
||||||
.collect(ImmutableList.toImmutableList())
|
.collect(ImmutableList.toImmutableList())
|
||||||
|
|
||||||
val offeredQuests = NetworkedList(QuestArcDescriptor.CODEC, QuestArcDescriptor.LEGACY_CODEC).also { networkGroup.upstream.add(it) }
|
val offeredQuests = NetworkedList(QuestArcDescriptor.CODEC, QuestArcDescriptor.LEGACY_CODEC).also { networkGroup.upstream.add(it) }
|
||||||
|
@ -1,20 +1,22 @@
|
|||||||
package ru.dbotthepony.kstarbound.world.entities.wire
|
package ru.dbotthepony.kstarbound.world.entities.wire
|
||||||
|
|
||||||
import ru.dbotthepony.kommons.io.StreamCodec
|
import ru.dbotthepony.kommons.io.StreamCodec
|
||||||
import ru.dbotthepony.kommons.io.readVector2i
|
import ru.dbotthepony.kommons.io.readSignedVarInt
|
||||||
import ru.dbotthepony.kommons.io.writeStruct2i
|
import ru.dbotthepony.kommons.io.readVarInt
|
||||||
import ru.dbotthepony.kommons.io.writeVarLong
|
import ru.dbotthepony.kommons.io.writeSignedVarInt
|
||||||
|
import ru.dbotthepony.kommons.io.writeVarInt
|
||||||
import ru.dbotthepony.kommons.vector.Vector2i
|
import ru.dbotthepony.kommons.vector.Vector2i
|
||||||
import ru.dbotthepony.kstarbound.network.syncher.SizeTCodec
|
import ru.dbotthepony.kstarbound.network.syncher.SizeTCodec
|
||||||
import java.io.DataInputStream
|
import java.io.DataInputStream
|
||||||
import java.io.DataOutputStream
|
import java.io.DataOutputStream
|
||||||
|
|
||||||
data class WireConnection(val entityLocation: Vector2i, val index: Int = -1) {
|
data class WireConnection(val entityLocation: Vector2i, val index: Int = 0) {
|
||||||
constructor(stream: DataInputStream) : this(stream.readVector2i(), SizeTCodec.read(stream).toInt())
|
constructor(stream: DataInputStream) : this(Vector2i(stream.readSignedVarInt(), stream.readSignedVarInt()), stream.readVarInt())
|
||||||
|
|
||||||
fun write(stream: DataOutputStream) {
|
fun write(stream: DataOutputStream) {
|
||||||
stream.writeStruct2i((entityLocation))
|
stream.writeSignedVarInt(entityLocation.x)
|
||||||
SizeTCodec.write(stream, index.toLong())
|
stream.writeSignedVarInt(entityLocation.y)
|
||||||
|
stream.writeVarInt(index)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -58,9 +58,9 @@ class KarstCaveTerrainSelector(data: Data, parameters: TerrainSelectorParameters
|
|||||||
}
|
}
|
||||||
|
|
||||||
private val layers = Caffeine.newBuilder()
|
private val layers = Caffeine.newBuilder()
|
||||||
.maximumSize(512L)
|
.maximumSize(2048L)
|
||||||
.softValues()
|
.softValues()
|
||||||
.expireAfterAccess(Duration.ofSeconds(10))
|
.expireAfterAccess(Duration.ofMinutes(1))
|
||||||
.scheduler(Starbound)
|
.scheduler(Starbound)
|
||||||
.executor(Starbound.EXECUTOR)
|
.executor(Starbound.EXECUTOR)
|
||||||
.build<Int, Layer>(::Layer)
|
.build<Int, Layer>(::Layer)
|
||||||
@ -127,9 +127,9 @@ class KarstCaveTerrainSelector(data: Data, parameters: TerrainSelectorParameters
|
|||||||
}
|
}
|
||||||
|
|
||||||
private val sectors = Caffeine.newBuilder()
|
private val sectors = Caffeine.newBuilder()
|
||||||
.maximumSize(256L)
|
.maximumSize(512L)
|
||||||
.softValues()
|
.softValues()
|
||||||
.expireAfterAccess(Duration.ofSeconds(10))
|
.expireAfterAccess(Duration.ofMinutes(1))
|
||||||
.scheduler(Starbound)
|
.scheduler(Starbound)
|
||||||
.executor(Starbound.EXECUTOR)
|
.executor(Starbound.EXECUTOR)
|
||||||
.build<Vector2i, Sector>(::Sector)
|
.build<Vector2i, Sector>(::Sector)
|
||||||
|
@ -178,9 +178,9 @@ class WormCaveTerrainSelector(data: Data, parameters: TerrainSelectorParameters)
|
|||||||
}
|
}
|
||||||
|
|
||||||
private val sectors = Caffeine.newBuilder()
|
private val sectors = Caffeine.newBuilder()
|
||||||
.maximumSize(256L)
|
.maximumSize(512L)
|
||||||
.softValues()
|
.softValues()
|
||||||
.expireAfterAccess(Duration.ofSeconds(10))
|
.expireAfterAccess(Duration.ofMinutes(1))
|
||||||
.scheduler(Starbound)
|
.scheduler(Starbound)
|
||||||
.executor(Starbound.EXECUTOR)
|
.executor(Starbound.EXECUTOR)
|
||||||
.build<Vector2i, Sector>(::Sector)
|
.build<Vector2i, Sector>(::Sector)
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package ru.dbotthepony.kstarbound.test
|
package ru.dbotthepony.kstarbound.test
|
||||||
|
|
||||||
|
import com.google.gson.JsonElement
|
||||||
import org.junit.jupiter.api.Assertions.assertFalse
|
import org.junit.jupiter.api.Assertions.assertFalse
|
||||||
import org.junit.jupiter.api.Assertions.assertTrue
|
import org.junit.jupiter.api.Assertions.assertTrue
|
||||||
import org.junit.jupiter.api.DisplayName
|
import org.junit.jupiter.api.DisplayName
|
||||||
@ -8,8 +9,12 @@ import ru.dbotthepony.kommons.util.AABB
|
|||||||
import ru.dbotthepony.kommons.util.AABBi
|
import ru.dbotthepony.kommons.util.AABBi
|
||||||
import ru.dbotthepony.kommons.vector.Vector2d
|
import ru.dbotthepony.kommons.vector.Vector2d
|
||||||
import ru.dbotthepony.kommons.vector.Vector2i
|
import ru.dbotthepony.kommons.vector.Vector2i
|
||||||
import ru.dbotthepony.kstarbound.world.SpatialIndex
|
import ru.dbotthepony.kstarbound.defs.EntityType
|
||||||
|
import ru.dbotthepony.kstarbound.json.JsonPath
|
||||||
|
import ru.dbotthepony.kstarbound.world.EntityIndex
|
||||||
import ru.dbotthepony.kstarbound.world.WorldGeometry
|
import ru.dbotthepony.kstarbound.world.WorldGeometry
|
||||||
|
import ru.dbotthepony.kstarbound.world.entities.AbstractEntity
|
||||||
|
import java.io.DataOutputStream
|
||||||
|
|
||||||
object WorldTests {
|
object WorldTests {
|
||||||
@Test
|
@Test
|
||||||
@ -42,9 +47,29 @@ object WorldTests {
|
|||||||
@DisplayName("Spatial index test")
|
@DisplayName("Spatial index test")
|
||||||
fun spatialIndex() {
|
fun spatialIndex() {
|
||||||
val geometry = WorldGeometry(Vector2i(3000, 2000), true, false)
|
val geometry = WorldGeometry(Vector2i(3000, 2000), true, false)
|
||||||
val index = SpatialIndex<Int>(geometry)
|
val index = EntityIndex(geometry)
|
||||||
|
|
||||||
val entry = index.Entry(0)
|
val entry = index.Entry(object : AbstractEntity("/") {
|
||||||
|
override fun lookupProperty(path: JsonPath, orElse: () -> JsonElement): JsonElement {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setProperty0(key: JsonPath, value: JsonElement) {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override val position: Vector2d
|
||||||
|
get() = TODO("Not yet implemented")
|
||||||
|
override val type: EntityType
|
||||||
|
get() = TODO("Not yet implemented")
|
||||||
|
|
||||||
|
override fun writeNetwork(stream: DataOutputStream, isLegacy: Boolean) {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override val metaBoundingBox: AABB
|
||||||
|
get() = TODO("Not yet implemented")
|
||||||
|
})
|
||||||
|
|
||||||
// simple
|
// simple
|
||||||
entry.fixture.move(AABB(
|
entry.fixture.move(AABB(
|
||||||
|
Loading…
Reference in New Issue
Block a user