Parallel move(), remove more outdated code, complete MailboxExecutorService
This commit is contained in:
parent
30b168766d
commit
5fb6d817fc
@ -120,11 +120,10 @@ fun main() {
|
|||||||
|
|
||||||
//client.world!!.parallax = Starbound.parallaxAccess["garden"]
|
//client.world!!.parallax = Starbound.parallaxAccess["garden"]
|
||||||
|
|
||||||
val item = Registries.items.values.random()
|
|
||||||
val rand = Random()
|
val rand = Random()
|
||||||
|
|
||||||
for (i in 0 .. 128) {
|
for (i in 0 .. 128) {
|
||||||
val item = ItemEntity(client.world!!, item.value)
|
val item = ItemEntity(client.world!!, Registries.items.values.random().value)
|
||||||
|
|
||||||
item.position = Vector2d(225.0 - i, 685.0)
|
item.position = Vector2d(225.0 - i, 685.0)
|
||||||
item.spawn()
|
item.spawn()
|
||||||
|
@ -14,7 +14,7 @@ import org.lwjgl.opengl.GLCapabilities
|
|||||||
import org.lwjgl.system.MemoryStack
|
import org.lwjgl.system.MemoryStack
|
||||||
import org.lwjgl.system.MemoryUtil
|
import org.lwjgl.system.MemoryUtil
|
||||||
import ru.dbotthepony.kstarbound.LoadingLog
|
import ru.dbotthepony.kstarbound.LoadingLog
|
||||||
import ru.dbotthepony.kstarbound.util.ManualExecutorService
|
import ru.dbotthepony.kstarbound.util.MailboxExecutorService
|
||||||
import ru.dbotthepony.kstarbound.world.PIXELS_IN_STARBOUND_UNIT
|
import ru.dbotthepony.kstarbound.world.PIXELS_IN_STARBOUND_UNIT
|
||||||
import ru.dbotthepony.kstarbound.world.PIXELS_IN_STARBOUND_UNITf
|
import ru.dbotthepony.kstarbound.world.PIXELS_IN_STARBOUND_UNITf
|
||||||
import ru.dbotthepony.kstarbound.Starbound
|
import ru.dbotthepony.kstarbound.Starbound
|
||||||
@ -92,7 +92,7 @@ class StarboundClient : Closeable {
|
|||||||
// client specific executor which will accept tasks which involve probable
|
// client specific executor which will accept tasks which involve probable
|
||||||
// callback to foreground executor to initialize thread-unsafe data
|
// callback to foreground executor to initialize thread-unsafe data
|
||||||
// In above case too many threads will introduce big congestion for resources, stalling entire workload; wasting cpu resources
|
// In above case too many threads will introduce big congestion for resources, stalling entire workload; wasting cpu resources
|
||||||
val backgroundExecutor = ForkJoinPool(Runtime.getRuntime().availableProcessors().coerceAtMost(4), {
|
val executor = ForkJoinPool(Runtime.getRuntime().availableProcessors().coerceAtMost(4), {
|
||||||
object : ForkJoinWorkerThread(it) {
|
object : ForkJoinWorkerThread(it) {
|
||||||
init {
|
init {
|
||||||
name = "Background Executor for '${thread.name}'-${threadCounter.incrementAndGet()}"
|
name = "Background Executor for '${thread.name}'-${threadCounter.incrementAndGet()}"
|
||||||
@ -108,7 +108,7 @@ class StarboundClient : Closeable {
|
|||||||
}
|
}
|
||||||
}, null, false)
|
}, null, false)
|
||||||
|
|
||||||
val foregroundExecutor = ManualExecutorService(thread)
|
val mailbox = MailboxExecutorService(thread)
|
||||||
val capabilities: GLCapabilities
|
val capabilities: GLCapabilities
|
||||||
|
|
||||||
var viewportX: Int = 0
|
var viewportX: Int = 0
|
||||||
@ -322,7 +322,7 @@ class StarboundClient : Closeable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun executeQueuedTasks() {
|
private fun executeQueuedTasks() {
|
||||||
foregroundExecutor.executeQueuedTasks()
|
mailbox.executeQueuedTasks()
|
||||||
|
|
||||||
var next = openglCleanQueue.poll() as CleanRef?
|
var next = openglCleanQueue.poll() as CleanRef?
|
||||||
|
|
||||||
|
@ -18,9 +18,9 @@ class StreamVertexBuilder(
|
|||||||
) {
|
) {
|
||||||
val builder = VertexBuilder(attributes, type, initialCapacity)
|
val builder = VertexBuilder(attributes, type, initialCapacity)
|
||||||
|
|
||||||
private val vao = client.foregroundExecutor.submit(Callable { VertexArrayObject() })
|
private val vao = client.mailbox.submit(Callable { VertexArrayObject() })
|
||||||
private val vbo = client.foregroundExecutor.submit(Callable { BufferObject.VBO() })
|
private val vbo = client.mailbox.submit(Callable { BufferObject.VBO() })
|
||||||
private val ebo = client.foregroundExecutor.submit(Callable { BufferObject.EBO() })
|
private val ebo = client.mailbox.submit(Callable { BufferObject.EBO() })
|
||||||
|
|
||||||
private var initialized = false
|
private var initialized = false
|
||||||
|
|
||||||
|
@ -49,14 +49,14 @@ class TileRenderers(val client: StarboundClient) {
|
|||||||
fun getMaterialRenderer(defName: String): TileRenderer {
|
fun getMaterialRenderer(defName: String): TileRenderer {
|
||||||
return matCache.get(defName) {
|
return matCache.get(defName) {
|
||||||
val def = Registries.tiles[defName] // TODO: Пустой рендерер
|
val def = Registries.tiles[defName] // TODO: Пустой рендерер
|
||||||
client.foregroundExecutor.submit(Callable { TileRenderer(this, def!!.value) }).get()
|
client.mailbox.submit(Callable { TileRenderer(this, def!!.value) }).get()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getModifierRenderer(defName: String): TileRenderer {
|
fun getModifierRenderer(defName: String): TileRenderer {
|
||||||
return modCache.get(defName) {
|
return modCache.get(defName) {
|
||||||
val def = Registries.tileModifiers[defName] // TODO: Пустой рендерер
|
val def = Registries.tileModifiers[defName] // TODO: Пустой рендерер
|
||||||
client.foregroundExecutor.submit(Callable { TileRenderer(this, def!!.value) }).get()
|
client.mailbox.submit(Callable { TileRenderer(this, def!!.value) }).get()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,52 +0,0 @@
|
|||||||
package ru.dbotthepony.kstarbound.client.render.entity
|
|
||||||
|
|
||||||
import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap
|
|
||||||
import ru.dbotthepony.kstarbound.client.world.ClientChunk
|
|
||||||
import ru.dbotthepony.kstarbound.client.StarboundClient
|
|
||||||
import ru.dbotthepony.kstarbound.world.entities.Entity
|
|
||||||
import ru.dbotthepony.kvector.arrays.Matrix4fStack
|
|
||||||
import ru.dbotthepony.kvector.vector.Vector2d
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Базовый класс, отвечающий за отрисовку определённого ентити в мире
|
|
||||||
*
|
|
||||||
* Считается, что процесс отрисовки ограничен лишь одним слоем (т.е. отрисовка происходит в один проход)
|
|
||||||
*/
|
|
||||||
open class EntityRenderer(val client: StarboundClient, val entity: Entity, open var chunk: ClientChunk?) {
|
|
||||||
open val renderPos: Vector2d get() = entity.position
|
|
||||||
|
|
||||||
open fun render(stack: Matrix4fStack) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
open val layer: Int get() = Z_LEVEL_ENTITIES
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
/**
|
|
||||||
* Pseudo Z position for entities, for them to appear behind tile geometry,
|
|
||||||
* but in front of background walls geometry
|
|
||||||
*/
|
|
||||||
const val Z_LEVEL_ENTITIES = 30000
|
|
||||||
|
|
||||||
private val renderers = Reference2ObjectOpenHashMap<Class<*>, (client: StarboundClient, entity: Entity, chunk: ClientChunk?) -> EntityRenderer>()
|
|
||||||
|
|
||||||
@Suppress("unchecked_cast")
|
|
||||||
fun <T : Entity> registerRenderer(clazz: Class<T>, renderer: (client: StarboundClient, entity: T, chunk: ClientChunk?) -> EntityRenderer) {
|
|
||||||
check(renderers.put(clazz, renderer as (client: StarboundClient, entity: Entity, chunk: ClientChunk?) -> EntityRenderer) == null) { "Already has renderer for ${clazz.canonicalName}!" }
|
|
||||||
}
|
|
||||||
|
|
||||||
inline fun <reified T : Entity> registerRenderer(noinline renderer: (client: StarboundClient, entity: T, chunk: ClientChunk?) -> EntityRenderer) {
|
|
||||||
registerRenderer(T::class.java, renderer)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getRender(client: StarboundClient, entity: Entity, chunk: ClientChunk? = null): EntityRenderer {
|
|
||||||
val factory = renderers[entity::class.java] ?: return EntityRenderer(client, entity, chunk)
|
|
||||||
return factory.invoke(client, entity, chunk)
|
|
||||||
}
|
|
||||||
|
|
||||||
init {
|
|
||||||
registerRenderer(::ItemRenderer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
|||||||
package ru.dbotthepony.kstarbound.client.render.entity
|
|
||||||
|
|
||||||
import ru.dbotthepony.kstarbound.client.world.ClientChunk
|
|
||||||
import ru.dbotthepony.kstarbound.client.StarboundClient
|
|
||||||
import ru.dbotthepony.kstarbound.world.entities.ItemEntity
|
|
||||||
import ru.dbotthepony.kvector.arrays.Matrix4fStack
|
|
||||||
|
|
||||||
class ItemRenderer(client: StarboundClient, entity: ItemEntity, chunk: ClientChunk?) : EntityRenderer(client, entity, chunk) {
|
|
||||||
private val def = entity.def
|
|
||||||
private val textures = def.inventoryIcon?.stream()?.map { it.image }?.toList() ?: listOf()
|
|
||||||
|
|
||||||
override fun render(stack: Matrix4fStack) {
|
|
||||||
//client.programs.positionTexture.use()
|
|
||||||
//client.programs.positionTexture.texture0 = 0
|
|
||||||
|
|
||||||
//for (texture in textures) {
|
|
||||||
// client.textures2D[0] = texture.image?.texture ?: continue
|
|
||||||
//}
|
|
||||||
|
|
||||||
entity.hitboxes.forEach { it.render(client) }
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,9 +1,7 @@
|
|||||||
package ru.dbotthepony.kstarbound.client.world
|
package ru.dbotthepony.kstarbound.client.world
|
||||||
|
|
||||||
import ru.dbotthepony.kstarbound.client.render.entity.EntityRenderer
|
|
||||||
import ru.dbotthepony.kstarbound.world.Chunk
|
import ru.dbotthepony.kstarbound.world.Chunk
|
||||||
import ru.dbotthepony.kstarbound.world.ChunkPos
|
import ru.dbotthepony.kstarbound.world.ChunkPos
|
||||||
import ru.dbotthepony.kstarbound.world.entities.Entity
|
|
||||||
|
|
||||||
class ClientChunk(world: ClientWorld, pos: ChunkPos) : Chunk<ClientWorld, ClientChunk>(world, pos){
|
class ClientChunk(world: ClientWorld, pos: ChunkPos) : Chunk<ClientWorld, ClientChunk>(world, pos){
|
||||||
override fun foregroundChanges(cell: Cell) {
|
override fun foregroundChanges(cell: Cell) {
|
||||||
|
@ -92,7 +92,7 @@ class ClientWorld(
|
|||||||
|
|
||||||
isDirty = false
|
isDirty = false
|
||||||
|
|
||||||
currentBakeTask = client.backgroundExecutor.submit(Callable {
|
currentBakeTask = client.executor.submit(Callable {
|
||||||
val meshes = LayeredRenderer(client)
|
val meshes = LayeredRenderer(client)
|
||||||
|
|
||||||
for (x in 0 until renderRegionWidth) {
|
for (x in 0 until renderRegionWidth) {
|
||||||
|
@ -1,10 +1,8 @@
|
|||||||
package ru.dbotthepony.kstarbound.defs.image
|
package ru.dbotthepony.kstarbound.defs.image
|
||||||
|
|
||||||
import com.github.benmanes.caffeine.cache.AsyncLoadingCache
|
import com.github.benmanes.caffeine.cache.AsyncLoadingCache
|
||||||
import com.github.benmanes.caffeine.cache.Cache
|
|
||||||
import com.github.benmanes.caffeine.cache.CacheLoader
|
import com.github.benmanes.caffeine.cache.CacheLoader
|
||||||
import com.github.benmanes.caffeine.cache.Caffeine
|
import com.github.benmanes.caffeine.cache.Caffeine
|
||||||
import com.github.benmanes.caffeine.cache.LoadingCache
|
|
||||||
import com.github.benmanes.caffeine.cache.Scheduler
|
import com.github.benmanes.caffeine.cache.Scheduler
|
||||||
import com.google.common.collect.ImmutableList
|
import com.google.common.collect.ImmutableList
|
||||||
import com.google.gson.JsonArray
|
import com.google.gson.JsonArray
|
||||||
@ -35,7 +33,6 @@ import ru.dbotthepony.kvector.vector.Vector2i
|
|||||||
import ru.dbotthepony.kvector.vector.Vector4i
|
import ru.dbotthepony.kvector.vector.Vector4i
|
||||||
import java.io.BufferedInputStream
|
import java.io.BufferedInputStream
|
||||||
import java.io.FileNotFoundException
|
import java.io.FileNotFoundException
|
||||||
import java.lang.ref.Cleaner
|
|
||||||
import java.lang.ref.WeakReference
|
import java.lang.ref.WeakReference
|
||||||
import java.nio.ByteBuffer
|
import java.nio.ByteBuffer
|
||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
@ -131,7 +128,7 @@ class Image private constructor(
|
|||||||
|
|
||||||
tex.textureMinFilter = GL45.GL_NEAREST
|
tex.textureMinFilter = GL45.GL_NEAREST
|
||||||
tex.textureMagFilter = GL45.GL_NEAREST
|
tex.textureMagFilter = GL45.GL_NEAREST
|
||||||
}, client.foregroundExecutor)
|
}, client.mailbox)
|
||||||
|
|
||||||
tex
|
tex
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,19 @@
|
|||||||
package ru.dbotthepony.kstarbound.util
|
package ru.dbotthepony.kstarbound.util
|
||||||
|
|
||||||
import com.google.common.util.concurrent.Futures
|
import com.google.common.util.concurrent.Futures
|
||||||
import it.unimi.dsi.fastutil.objects.ObjectHeapPriorityQueue
|
|
||||||
import java.util.LinkedList
|
import java.util.LinkedList
|
||||||
import java.util.concurrent.Callable
|
import java.util.concurrent.Callable
|
||||||
import java.util.concurrent.ConcurrentLinkedQueue
|
import java.util.concurrent.ConcurrentLinkedQueue
|
||||||
import java.util.concurrent.Delayed
|
import java.util.concurrent.Delayed
|
||||||
import java.util.concurrent.Future
|
import java.util.concurrent.Future
|
||||||
import java.util.concurrent.FutureTask
|
import java.util.concurrent.FutureTask
|
||||||
|
import java.util.concurrent.RejectedExecutionException
|
||||||
import java.util.concurrent.ScheduledExecutorService
|
import java.util.concurrent.ScheduledExecutorService
|
||||||
import java.util.concurrent.ScheduledFuture
|
import java.util.concurrent.ScheduledFuture
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import java.util.concurrent.locks.LockSupport
|
import java.util.concurrent.locks.LockSupport
|
||||||
|
import java.util.concurrent.locks.ReentrantLock
|
||||||
|
import kotlin.concurrent.withLock
|
||||||
|
|
||||||
private fun <E : Comparable<E>> LinkedList<E>.enqueue(value: E) {
|
private fun <E : Comparable<E>> LinkedList<E>.enqueue(value: E) {
|
||||||
if (isEmpty()) {
|
if (isEmpty()) {
|
||||||
@ -35,7 +37,7 @@ private fun <E : Comparable<E>> LinkedList<E>.enqueue(value: E) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ManualExecutorService(val thread: Thread = Thread.currentThread()) : ScheduledExecutorService {
|
class MailboxExecutorService(val thread: Thread = Thread.currentThread()) : ScheduledExecutorService {
|
||||||
private val executeQueue = ConcurrentLinkedQueue<Runnable>()
|
private val executeQueue = ConcurrentLinkedQueue<Runnable>()
|
||||||
private val futureQueue = ConcurrentLinkedQueue<FutureTask<*>>()
|
private val futureQueue = ConcurrentLinkedQueue<FutureTask<*>>()
|
||||||
private val timerBacklog = ConcurrentLinkedQueue<Timer<*>>()
|
private val timerBacklog = ConcurrentLinkedQueue<Timer<*>>()
|
||||||
@ -43,6 +45,12 @@ class ManualExecutorService(val thread: Thread = Thread.currentThread()) : Sched
|
|||||||
|
|
||||||
private val timers = LinkedList<Timer<*>>()
|
private val timers = LinkedList<Timer<*>>()
|
||||||
private val repeatableTimers = LinkedList<RepeatableTimer>()
|
private val repeatableTimers = LinkedList<RepeatableTimer>()
|
||||||
|
private val executionLock = ReentrantLock()
|
||||||
|
|
||||||
|
@Volatile
|
||||||
|
private var isShutdown = false
|
||||||
|
@Volatile
|
||||||
|
private var isTerminated = false
|
||||||
|
|
||||||
private val timeOrigin = JVMTimeSource()
|
private val timeOrigin = JVMTimeSource()
|
||||||
|
|
||||||
@ -94,84 +102,108 @@ class ManualExecutorService(val thread: Thread = Thread.currentThread()) : Sched
|
|||||||
fun executeQueuedTasks() {
|
fun executeQueuedTasks() {
|
||||||
check(isSameThread()) { "Trying to execute queued tasks in thread ${Thread.currentThread()}, while correct thread is $thread" }
|
check(isSameThread()) { "Trying to execute queued tasks in thread ${Thread.currentThread()}, while correct thread is $thread" }
|
||||||
|
|
||||||
var next = executeQueue.poll()
|
if (isShutdown) {
|
||||||
|
if (!isTerminated) {
|
||||||
|
isTerminated = true
|
||||||
|
|
||||||
while (next != null) {
|
executionLock.withLock {
|
||||||
next.run()
|
timers.clear()
|
||||||
next = executeQueue.poll()
|
repeatableTimers.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var next2 = futureQueue.poll()
|
executionLock.withLock {
|
||||||
|
var next = executeQueue.poll()
|
||||||
|
|
||||||
while (next2 != null) {
|
while (next != null) {
|
||||||
next2.run()
|
if (isTerminated) return
|
||||||
Thread.interrupted()
|
next.run()
|
||||||
next2 = futureQueue.poll()
|
next = executeQueue.poll()
|
||||||
}
|
}
|
||||||
|
|
||||||
var next3 = timerBacklog.poll()
|
var next2 = futureQueue.poll()
|
||||||
|
|
||||||
while (next3 != null) {
|
while (next2 != null) {
|
||||||
if (next3.isCancelled) {
|
if (isTerminated) return
|
||||||
// do nothing
|
next2.run()
|
||||||
} else if (next3.executeAt <= timeOrigin.nanos) {
|
|
||||||
next3.run()
|
|
||||||
Thread.interrupted()
|
Thread.interrupted()
|
||||||
} else {
|
next2 = futureQueue.poll()
|
||||||
timers.enqueue(next3)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
next3 = timerBacklog.poll()
|
var next3 = timerBacklog.poll()
|
||||||
}
|
|
||||||
|
|
||||||
var next4 = repeatableTimersBacklog.poll()
|
while (next3 != null) {
|
||||||
|
if (isTerminated) return
|
||||||
|
if (next3.isCancelled) {
|
||||||
|
// do nothing
|
||||||
|
} else if (next3.executeAt <= timeOrigin.nanos) {
|
||||||
|
next3.run()
|
||||||
|
Thread.interrupted()
|
||||||
|
} else {
|
||||||
|
timers.enqueue(next3)
|
||||||
|
}
|
||||||
|
|
||||||
while (next4 != null) {
|
next3 = timerBacklog.poll()
|
||||||
if (next4.isCancelled) {
|
|
||||||
// do nothing
|
|
||||||
} else {
|
|
||||||
repeatableTimers.enqueue(next4)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
next4 = repeatableTimersBacklog.poll()
|
var next4 = repeatableTimersBacklog.poll()
|
||||||
}
|
|
||||||
|
|
||||||
while (!timers.isEmpty()) {
|
while (next4 != null) {
|
||||||
val first = timers.first
|
if (isTerminated) return
|
||||||
|
|
||||||
if (first.isCancelled) {
|
if (next4.isCancelled) {
|
||||||
timers.removeFirst()
|
// do nothing
|
||||||
} else if (first.executeAt <= timeOrigin.nanos) {
|
} else {
|
||||||
first.run()
|
repeatableTimers.enqueue(next4)
|
||||||
Thread.interrupted()
|
}
|
||||||
timers.removeFirst()
|
|
||||||
} else {
|
next4 = repeatableTimersBacklog.poll()
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (repeatableTimers.isNotEmpty()) {
|
while (!timers.isEmpty()) {
|
||||||
val executed = LinkedList<RepeatableTimer>()
|
if (isTerminated) return
|
||||||
|
val first = timers.first
|
||||||
|
|
||||||
while (repeatableTimers.isNotEmpty()) {
|
if (first.isCancelled) {
|
||||||
val first = repeatableTimers.first
|
timers.removeFirst()
|
||||||
|
} else if (first.executeAt <= timeOrigin.nanos) {
|
||||||
if (first.isDone) {
|
first.run()
|
||||||
repeatableTimers.removeFirst()
|
Thread.interrupted()
|
||||||
} else if (first.next <= timeOrigin.nanos) {
|
timers.removeFirst()
|
||||||
first.runAndReset()
|
|
||||||
executed.add(first)
|
|
||||||
repeatableTimers.removeFirst()
|
|
||||||
} else {
|
} else {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
executed.forEach { repeatableTimers.enqueue(it) }
|
if (repeatableTimers.isNotEmpty()) {
|
||||||
|
val executed = LinkedList<RepeatableTimer>()
|
||||||
|
|
||||||
|
while (repeatableTimers.isNotEmpty()) {
|
||||||
|
if (isTerminated) return
|
||||||
|
val first = repeatableTimers.first
|
||||||
|
|
||||||
|
if (first.isDone) {
|
||||||
|
repeatableTimers.removeFirst()
|
||||||
|
} else if (first.next <= timeOrigin.nanos) {
|
||||||
|
first.runAndReset()
|
||||||
|
executed.add(first)
|
||||||
|
repeatableTimers.removeFirst()
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
executed.forEach { repeatableTimers.enqueue(it) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun execute(command: Runnable) {
|
override fun execute(command: Runnable) {
|
||||||
|
if (isShutdown) throw RejectedExecutionException("This mailbox is shutting down")
|
||||||
|
|
||||||
if (isSameThread()) {
|
if (isSameThread()) {
|
||||||
command.run()
|
command.run()
|
||||||
} else {
|
} else {
|
||||||
@ -181,19 +213,42 @@ class ManualExecutorService(val thread: Thread = Thread.currentThread()) : Sched
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun shutdown() {
|
override fun shutdown() {
|
||||||
throw UnsupportedOperationException()
|
isShutdown = true
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun shutdownNow(): MutableList<Runnable> {
|
override fun shutdownNow(): MutableList<Runnable> {
|
||||||
throw UnsupportedOperationException()
|
isShutdown = true
|
||||||
|
isTerminated = true
|
||||||
|
|
||||||
|
val result = ArrayList<Runnable>()
|
||||||
|
|
||||||
|
executionLock.withLock {
|
||||||
|
executeQueue.forEach { result.add(it) }
|
||||||
|
executeQueue.clear()
|
||||||
|
|
||||||
|
futureQueue.forEach {
|
||||||
|
it.cancel(false)
|
||||||
|
result.add(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
futureQueue.clear()
|
||||||
|
|
||||||
|
timers.forEach { it.cancel(false) }
|
||||||
|
repeatableTimers.forEach { it.cancel(false) }
|
||||||
|
|
||||||
|
timers.clear()
|
||||||
|
repeatableTimers.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun isShutdown(): Boolean {
|
override fun isShutdown(): Boolean {
|
||||||
return false
|
return isShutdown
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun isTerminated(): Boolean {
|
override fun isTerminated(): Boolean {
|
||||||
return false
|
return isTerminated
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun awaitTermination(timeout: Long, unit: TimeUnit): Boolean {
|
override fun awaitTermination(timeout: Long, unit: TimeUnit): Boolean {
|
||||||
@ -201,21 +256,26 @@ class ManualExecutorService(val thread: Thread = Thread.currentThread()) : Sched
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun <T : Any?> submit(task: Callable<T>): Future<T> {
|
override fun <T : Any?> submit(task: Callable<T>): Future<T> {
|
||||||
|
if (isShutdown) throw RejectedExecutionException("This mailbox is shutting down")
|
||||||
if (isSameThread()) return Futures.immediateFuture(task.call())
|
if (isSameThread()) return Futures.immediateFuture(task.call())
|
||||||
return FutureTask(task).also { futureQueue.add(it); LockSupport.unpark(thread) }
|
return FutureTask(task).also { futureQueue.add(it); LockSupport.unpark(thread) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun <T : Any?> submit(task: Runnable, result: T): Future<T> {
|
override fun <T : Any?> submit(task: Runnable, result: T): Future<T> {
|
||||||
|
if (isShutdown) throw RejectedExecutionException("This mailbox is shutting down")
|
||||||
if (isSameThread()) { task.run(); return Futures.immediateFuture(result) }
|
if (isSameThread()) { task.run(); return Futures.immediateFuture(result) }
|
||||||
return FutureTask { task.run(); result }.also { futureQueue.add(it); LockSupport.unpark(thread) }
|
return FutureTask { task.run(); result }.also { futureQueue.add(it); LockSupport.unpark(thread) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun submit(task: Runnable): Future<*> {
|
override fun submit(task: Runnable): Future<*> {
|
||||||
|
if (isShutdown) throw RejectedExecutionException("This mailbox is shutting down")
|
||||||
if (isSameThread()) { task.run(); return Futures.immediateVoidFuture() }
|
if (isSameThread()) { task.run(); return Futures.immediateVoidFuture() }
|
||||||
return FutureTask { task.run() }.also { futureQueue.add(it); LockSupport.unpark(thread) }
|
return FutureTask { task.run() }.also { futureQueue.add(it); LockSupport.unpark(thread) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun <T : Any?> invokeAll(tasks: Collection<Callable<T>>): List<Future<T>> {
|
override fun <T : Any?> invokeAll(tasks: Collection<Callable<T>>): List<Future<T>> {
|
||||||
|
if (isShutdown) throw RejectedExecutionException("This mailbox is shutting down")
|
||||||
|
|
||||||
if (isSameThread()) {
|
if (isSameThread()) {
|
||||||
return tasks.map { Futures.immediateFuture(it.call()) }
|
return tasks.map { Futures.immediateFuture(it.call()) }
|
||||||
} else {
|
} else {
|
||||||
@ -228,6 +288,8 @@ class ManualExecutorService(val thread: Thread = Thread.currentThread()) : Sched
|
|||||||
timeout: Long,
|
timeout: Long,
|
||||||
unit: TimeUnit
|
unit: TimeUnit
|
||||||
): List<Future<T>> {
|
): List<Future<T>> {
|
||||||
|
if (isShutdown) throw RejectedExecutionException("This mailbox is shutting down")
|
||||||
|
|
||||||
if (isSameThread()) {
|
if (isSameThread()) {
|
||||||
return tasks.map { Futures.immediateFuture(it.call()) }
|
return tasks.map { Futures.immediateFuture(it.call()) }
|
||||||
} else {
|
} else {
|
||||||
@ -239,6 +301,8 @@ class ManualExecutorService(val thread: Thread = Thread.currentThread()) : Sched
|
|||||||
if (tasks.isEmpty())
|
if (tasks.isEmpty())
|
||||||
throw NoSuchElementException("Provided task list is empty")
|
throw NoSuchElementException("Provided task list is empty")
|
||||||
|
|
||||||
|
if (isShutdown) throw RejectedExecutionException("This mailbox is shutting down")
|
||||||
|
|
||||||
if (isSameThread()) {
|
if (isSameThread()) {
|
||||||
return tasks.first().call()
|
return tasks.first().call()
|
||||||
} else {
|
} else {
|
||||||
@ -250,6 +314,8 @@ class ManualExecutorService(val thread: Thread = Thread.currentThread()) : Sched
|
|||||||
if (tasks.isEmpty())
|
if (tasks.isEmpty())
|
||||||
throw NoSuchElementException("Provided task list is empty")
|
throw NoSuchElementException("Provided task list is empty")
|
||||||
|
|
||||||
|
if (isShutdown) throw RejectedExecutionException("This mailbox is shutting down")
|
||||||
|
|
||||||
if (isSameThread()) {
|
if (isSameThread()) {
|
||||||
return tasks.first().call()
|
return tasks.first().call()
|
||||||
} else {
|
} else {
|
||||||
@ -258,6 +324,8 @@ class ManualExecutorService(val thread: Thread = Thread.currentThread()) : Sched
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun <V> join(future: Future<V>): V {
|
fun <V> join(future: Future<V>): V {
|
||||||
|
if (isShutdown) throw RejectedExecutionException("This mailbox is shutting down")
|
||||||
|
|
||||||
if (!isSameThread())
|
if (!isSameThread())
|
||||||
return future.get()
|
return future.get()
|
||||||
|
|
||||||
@ -270,6 +338,7 @@ class ManualExecutorService(val thread: Thread = Thread.currentThread()) : Sched
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun schedule(command: Runnable, delay: Long, unit: TimeUnit): ScheduledFuture<*> {
|
override fun schedule(command: Runnable, delay: Long, unit: TimeUnit): ScheduledFuture<*> {
|
||||||
|
if (isShutdown) throw RejectedExecutionException("This mailbox is shutting down")
|
||||||
val timer = Timer({ command.run() }, timeOrigin.nanos + TimeUnit.NANOSECONDS.convert(delay, unit))
|
val timer = Timer({ command.run() }, timeOrigin.nanos + TimeUnit.NANOSECONDS.convert(delay, unit))
|
||||||
|
|
||||||
if (isSameThread() && delay <= 0L) {
|
if (isSameThread() && delay <= 0L) {
|
||||||
@ -285,6 +354,8 @@ class ManualExecutorService(val thread: Thread = Thread.currentThread()) : Sched
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun <V : Any?> schedule(callable: Callable<V>, delay: Long, unit: TimeUnit): ScheduledFuture<V> {
|
override fun <V : Any?> schedule(callable: Callable<V>, delay: Long, unit: TimeUnit): ScheduledFuture<V> {
|
||||||
|
if (isShutdown) throw RejectedExecutionException("This mailbox is shutting down")
|
||||||
|
|
||||||
val timer = Timer(callable, timeOrigin.nanos + TimeUnit.NANOSECONDS.convert(delay, unit))
|
val timer = Timer(callable, timeOrigin.nanos + TimeUnit.NANOSECONDS.convert(delay, unit))
|
||||||
|
|
||||||
if (isSameThread() && delay <= 0L) {
|
if (isSameThread() && delay <= 0L) {
|
||||||
@ -305,6 +376,8 @@ class ManualExecutorService(val thread: Thread = Thread.currentThread()) : Sched
|
|||||||
period: Long,
|
period: Long,
|
||||||
unit: TimeUnit
|
unit: TimeUnit
|
||||||
): ScheduledFuture<*> {
|
): ScheduledFuture<*> {
|
||||||
|
if (isShutdown) throw RejectedExecutionException("This mailbox is shutting down")
|
||||||
|
|
||||||
return RepeatableTimer(
|
return RepeatableTimer(
|
||||||
command,
|
command,
|
||||||
timeOrigin.nanos + TimeUnit.NANOSECONDS.convert(initialDelay, unit),
|
timeOrigin.nanos + TimeUnit.NANOSECONDS.convert(initialDelay, unit),
|
||||||
@ -317,6 +390,8 @@ class ManualExecutorService(val thread: Thread = Thread.currentThread()) : Sched
|
|||||||
delay: Long,
|
delay: Long,
|
||||||
unit: TimeUnit
|
unit: TimeUnit
|
||||||
): ScheduledFuture<*> {
|
): ScheduledFuture<*> {
|
||||||
|
if (isShutdown) throw RejectedExecutionException("This mailbox is shutting down")
|
||||||
|
|
||||||
return RepeatableTimer(
|
return RepeatableTimer(
|
||||||
command,
|
command,
|
||||||
timeOrigin.nanos + TimeUnit.NANOSECONDS.convert(initialDelay, unit),
|
timeOrigin.nanos + TimeUnit.NANOSECONDS.convert(initialDelay, unit),
|
@ -0,0 +1,21 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.util
|
||||||
|
|
||||||
|
import java.util.Spliterator
|
||||||
|
import java.util.concurrent.RecursiveAction
|
||||||
|
|
||||||
|
class ParallelPerform<E>(val spliterator: Spliterator<E>, val action: (E) -> Unit, val targetSize: Long = 32L) : RecursiveAction() {
|
||||||
|
override fun compute() {
|
||||||
|
if (spliterator.estimateSize() <= targetSize) {
|
||||||
|
spliterator.forEachRemaining(action)
|
||||||
|
} else {
|
||||||
|
val split = spliterator.trySplit() ?: return spliterator.forEachRemaining(action)
|
||||||
|
|
||||||
|
val task0 = ParallelPerform(spliterator, action, targetSize)
|
||||||
|
val task1 = ParallelPerform(split, action, targetSize)
|
||||||
|
task0.fork()
|
||||||
|
task1.fork()
|
||||||
|
task0.join()
|
||||||
|
task1.join()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -17,7 +17,9 @@ import ru.dbotthepony.kvector.arrays.Object2DArray
|
|||||||
import ru.dbotthepony.kvector.util2d.AABB
|
import ru.dbotthepony.kvector.util2d.AABB
|
||||||
import ru.dbotthepony.kvector.vector.Vector2d
|
import ru.dbotthepony.kvector.vector.Vector2d
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
import java.util.concurrent.locks.ReentrantLock
|
||||||
import kotlin.collections.ArrayList
|
import kotlin.collections.ArrayList
|
||||||
|
import kotlin.concurrent.withLock
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Чанк мира
|
* Чанк мира
|
||||||
@ -90,11 +92,6 @@ abstract class Chunk<WorldType : World<WorldType, This>, This : Chunk<WorldType,
|
|||||||
|
|
||||||
val aabb = aabbBase + Vector2d(pos.x * CHUNK_SIZE.toDouble(), pos.y * CHUNK_SIZE.toDouble())
|
val aabb = aabbBase + Vector2d(pos.x * CHUNK_SIZE.toDouble(), pos.y * CHUNK_SIZE.toDouble())
|
||||||
|
|
||||||
var isPhysicsDirty = false
|
|
||||||
|
|
||||||
private val collisionCache = ArrayList<AABB>()
|
|
||||||
private val collisionCacheView = Collections.unmodifiableCollection(collisionCache)
|
|
||||||
|
|
||||||
protected open fun foregroundChanges(cell: Cell) {
|
protected open fun foregroundChanges(cell: Cell) {
|
||||||
cellChanges(cell)
|
cellChanges(cell)
|
||||||
tileChangeset++
|
tileChangeset++
|
||||||
@ -264,42 +261,48 @@ abstract class Chunk<WorldType : World<WorldType, This>, This : Chunk<WorldType,
|
|||||||
protected open fun onEntityRemoved(entity: Entity) { }
|
protected open fun onEntityRemoved(entity: Entity) { }
|
||||||
|
|
||||||
fun addEntity(entity: Entity) {
|
fun addEntity(entity: Entity) {
|
||||||
if (!entities.add(entity)) {
|
world.entityMoveLock.withLock {
|
||||||
throw IllegalArgumentException("Already having having entity $entity")
|
if (!entities.add(entity)) {
|
||||||
}
|
throw IllegalArgumentException("Already having having entity $entity")
|
||||||
|
}
|
||||||
|
|
||||||
changeset++
|
changeset++
|
||||||
onEntityAdded(entity)
|
onEntityAdded(entity)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun transferEntity(entity: Entity, otherChunk: Chunk<*, *>) {
|
fun transferEntity(entity: Entity, otherChunk: Chunk<*, *>) {
|
||||||
if (otherChunk == this)
|
world.entityMoveLock.withLock {
|
||||||
throw IllegalArgumentException("what?")
|
if (otherChunk == this)
|
||||||
|
throw IllegalArgumentException("what?")
|
||||||
|
|
||||||
if (this::class.java != otherChunk::class.java) {
|
if (this::class.java != otherChunk::class.java) {
|
||||||
throw IllegalArgumentException("Incompatible types: $this !is $otherChunk")
|
throw IllegalArgumentException("Incompatible types: $this !is $otherChunk")
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!entities.add(entity)) {
|
if (!entities.add(entity)) {
|
||||||
throw IllegalArgumentException("Already containing $entity")
|
throw IllegalArgumentException("Already containing $entity")
|
||||||
}
|
}
|
||||||
|
|
||||||
changeset++
|
changeset++
|
||||||
onEntityTransferedToThis(entity, otherChunk as This)
|
onEntityTransferedToThis(entity, otherChunk as This)
|
||||||
otherChunk.onEntityTransferedFromThis(entity, this as This)
|
otherChunk.onEntityTransferedFromThis(entity, this as This)
|
||||||
|
|
||||||
if (!otherChunk.entities.remove(entity)) {
|
if (!otherChunk.entities.remove(entity)) {
|
||||||
throw IllegalStateException("Unable to remove $entity from $otherChunk after transfer")
|
throw IllegalStateException("Unable to remove $entity from $otherChunk after transfer")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun removeEntity(entity: Entity) {
|
fun removeEntity(entity: Entity) {
|
||||||
if (!entities.remove(entity)) {
|
world.entityMoveLock.withLock {
|
||||||
throw IllegalArgumentException("Already not having entity $entity")
|
if (!entities.remove(entity)) {
|
||||||
}
|
throw IllegalArgumentException("Already not having entity $entity")
|
||||||
|
}
|
||||||
|
|
||||||
changeset++
|
changeset++
|
||||||
onEntityRemoved(entity)
|
onEntityRemoved(entity)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
|
@ -1,20 +1,17 @@
|
|||||||
package ru.dbotthepony.kstarbound.world
|
package ru.dbotthepony.kstarbound.world
|
||||||
|
|
||||||
import com.google.common.base.Predicate
|
|
||||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap
|
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap
|
||||||
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet
|
|
||||||
import it.unimi.dsi.fastutil.objects.ReferenceLinkedOpenHashSet
|
import it.unimi.dsi.fastutil.objects.ReferenceLinkedOpenHashSet
|
||||||
import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet
|
import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet
|
||||||
import ru.dbotthepony.kstarbound.client.world.ClientWorld
|
|
||||||
import ru.dbotthepony.kstarbound.math.*
|
import ru.dbotthepony.kstarbound.math.*
|
||||||
import ru.dbotthepony.kstarbound.util.ManualExecutorService
|
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.ICellAccess
|
||||||
import ru.dbotthepony.kstarbound.world.api.IChunkCell
|
import ru.dbotthepony.kstarbound.world.api.IChunkCell
|
||||||
import ru.dbotthepony.kstarbound.world.api.TileView
|
import ru.dbotthepony.kstarbound.world.api.TileView
|
||||||
import ru.dbotthepony.kstarbound.world.entities.Entity
|
import ru.dbotthepony.kstarbound.world.entities.Entity
|
||||||
import ru.dbotthepony.kstarbound.world.entities.WorldObject
|
import ru.dbotthepony.kstarbound.world.entities.WorldObject
|
||||||
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.Poly
|
import ru.dbotthepony.kstarbound.world.physics.Poly
|
||||||
import ru.dbotthepony.kvector.api.IStruct2d
|
import ru.dbotthepony.kvector.api.IStruct2d
|
||||||
import ru.dbotthepony.kvector.api.IStruct2i
|
import ru.dbotthepony.kvector.api.IStruct2i
|
||||||
@ -24,6 +21,8 @@ import ru.dbotthepony.kvector.vector.Vector2d
|
|||||||
import ru.dbotthepony.kvector.vector.Vector2i
|
import ru.dbotthepony.kvector.vector.Vector2i
|
||||||
import java.lang.ref.ReferenceQueue
|
import java.lang.ref.ReferenceQueue
|
||||||
import java.lang.ref.WeakReference
|
import java.lang.ref.WeakReference
|
||||||
|
import java.util.concurrent.ForkJoinPool
|
||||||
|
import java.util.concurrent.locks.ReentrantLock
|
||||||
import java.util.random.RandomGenerator
|
import java.util.random.RandomGenerator
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
@ -47,7 +46,7 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
|
|||||||
|
|
||||||
val background = TileView.Background(this)
|
val background = TileView.Background(this)
|
||||||
val foreground = TileView.Foreground(this)
|
val foreground = TileView.Foreground(this)
|
||||||
val executor = ManualExecutorService()
|
val mailbox = MailboxExecutorService()
|
||||||
|
|
||||||
final override fun randomLongFor(x: Int, y: Int) = super.randomLongFor(x, y) xor seed
|
final override fun randomLongFor(x: Int, y: Int) = super.randomLongFor(x, y) xor seed
|
||||||
|
|
||||||
@ -233,17 +232,18 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
|
|||||||
val chunkMap: ChunkMap = if (size != null) ArrayChunkMap() else HashChunkMap()
|
val chunkMap: ChunkMap = if (size != null) ArrayChunkMap() else HashChunkMap()
|
||||||
|
|
||||||
val random: RandomGenerator = RandomGenerator.of("Xoroshiro128PlusPlus")
|
val random: RandomGenerator = RandomGenerator.of("Xoroshiro128PlusPlus")
|
||||||
|
|
||||||
var gravity = Vector2d(0.0, -EARTH_FREEFALL_ACCELERATION)
|
var gravity = Vector2d(0.0, -EARTH_FREEFALL_ACCELERATION)
|
||||||
|
|
||||||
var ticks = 0
|
// used by world chunks to synchronize entity additions/transfer
|
||||||
private set
|
val entityMoveLock = ReentrantLock()
|
||||||
|
|
||||||
fun think() {
|
fun think() {
|
||||||
try {
|
try {
|
||||||
executor.executeQueuedTasks()
|
mailbox.executeQueuedTasks()
|
||||||
|
ForkJoinPool.commonPool().submit(ParallelPerform(entities.spliterator(), Entity::move)).join()
|
||||||
|
mailbox.executeQueuedTasks()
|
||||||
|
|
||||||
thinkInner()
|
thinkInner()
|
||||||
ticks++
|
|
||||||
} catch(err: Throwable) {
|
} catch(err: Throwable) {
|
||||||
throw RuntimeException("Ticking world $this", err)
|
throw RuntimeException("Ticking world $this", err)
|
||||||
}
|
}
|
||||||
@ -265,20 +265,6 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
|
|||||||
|
|
||||||
protected abstract fun chunkFactory(pos: ChunkPos): ChunkType
|
protected abstract fun chunkFactory(pos: ChunkPos): ChunkType
|
||||||
|
|
||||||
fun testSpace(aabb: AABB, predicate: Predicate<IChunkCell> = Predicate { !it.foreground.material.collisionKind.isEmpty }): Boolean {
|
|
||||||
val tiles = aabb.encasingIntAABB()
|
|
||||||
|
|
||||||
for (x in tiles.mins.x .. tiles.maxs.x) {
|
|
||||||
for (y in tiles.mins.y .. tiles.maxs.y) {
|
|
||||||
if (predicate.test(getCell(x, y) ?: continue)) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
fun queryCollisions(aabb: AABB): MutableList<CollisionPoly> {
|
fun queryCollisions(aabb: AABB): MutableList<CollisionPoly> {
|
||||||
val result = ArrayList<CollisionPoly>()
|
val result = ArrayList<CollisionPoly>()
|
||||||
val tiles = aabb.encasingIntAABB()
|
val tiles = aabb.encasingIntAABB()
|
||||||
@ -287,12 +273,18 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
|
|||||||
for (y in tiles.mins.y .. tiles.maxs.y) {
|
for (y in tiles.mins.y .. tiles.maxs.y) {
|
||||||
val cell = getCell(x, y) ?: continue
|
val cell = getCell(x, y) ?: continue
|
||||||
|
|
||||||
for (poly in cell.polies) {
|
result.add(CollisionPoly(
|
||||||
result.add(CollisionPoly(poly, cell.foreground.material.collisionKind, Vector2d(EARTH_FREEFALL_ACCELERATION, 0.0)))
|
BLOCK_POLY + Vector2d(x.toDouble(), y.toDouble()),
|
||||||
}
|
cell.foreground.material.collisionKind,
|
||||||
|
//velocity = Vector2d(EARTH_FREEFALL_ACCELERATION, 0.0)
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val BLOCK_POLY = Poly(listOf(Vector2d.ZERO, Vector2d(0.0, 1.0), Vector2d(1.0, 1.0), Vector2d(1.0, 0.0)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,10 +31,6 @@ interface IChunkCell : IStruct2i {
|
|||||||
return y
|
return y
|
||||||
}
|
}
|
||||||
|
|
||||||
val polies: Collection<Poly> get() {
|
|
||||||
return listOf(rect + Vector2d(this.x.toDouble(), this.y.toDouble()))
|
|
||||||
}
|
|
||||||
|
|
||||||
val foreground: ITileState
|
val foreground: ITileState
|
||||||
val background: ITileState
|
val background: ITileState
|
||||||
val liquid: ILiquidState
|
val liquid: ILiquidState
|
||||||
|
@ -4,12 +4,16 @@ import ru.dbotthepony.kstarbound.GlobalDefaults
|
|||||||
import ru.dbotthepony.kstarbound.Starbound
|
import ru.dbotthepony.kstarbound.Starbound
|
||||||
import ru.dbotthepony.kstarbound.client.StarboundClient
|
import ru.dbotthepony.kstarbound.client.StarboundClient
|
||||||
import ru.dbotthepony.kstarbound.defs.BaseMovementParameters
|
import ru.dbotthepony.kstarbound.defs.BaseMovementParameters
|
||||||
|
import ru.dbotthepony.kstarbound.math.roundTowardsPositiveInfinity
|
||||||
|
import ru.dbotthepony.kstarbound.util.MailboxExecutorService
|
||||||
import ru.dbotthepony.kstarbound.world.Chunk
|
import ru.dbotthepony.kstarbound.world.Chunk
|
||||||
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.Poly
|
import ru.dbotthepony.kstarbound.world.physics.Poly
|
||||||
import ru.dbotthepony.kvector.util2d.AABB
|
import ru.dbotthepony.kvector.util2d.AABB
|
||||||
import ru.dbotthepony.kvector.vector.Vector2d
|
import ru.dbotthepony.kvector.vector.Vector2d
|
||||||
|
import java.util.EnumSet
|
||||||
|
|
||||||
abstract class Entity(val world: World<*, *>) {
|
abstract class Entity(val world: World<*, *>) {
|
||||||
var chunk: Chunk<*, *>? = null
|
var chunk: Chunk<*, *>? = null
|
||||||
@ -49,6 +53,7 @@ abstract class Entity(val world: World<*, *>) {
|
|||||||
|
|
||||||
val old = field
|
val old = field
|
||||||
field = Vector2d(world.x.cell(value.x), world.y.cell(value.y))
|
field = Vector2d(world.x.cell(value.x), world.y.cell(value.y))
|
||||||
|
physicsSleepTicks = 0
|
||||||
|
|
||||||
if (isSpawned && !isRemoved) {
|
if (isSpawned && !isRemoved) {
|
||||||
val oldChunkPos = world.chunkFromCell(old)
|
val oldChunkPos = world.chunkFromCell(old)
|
||||||
@ -61,7 +66,22 @@ abstract class Entity(val world: World<*, *>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var velocity = Vector2d.ZERO
|
var velocity = Vector2d.ZERO
|
||||||
val hitboxes = ArrayList<Poly>()
|
set(value) {
|
||||||
|
field = value
|
||||||
|
physicsSleepTicks = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
var physicsSleepTicks = 0
|
||||||
|
val mailbox = MailboxExecutorService(world.mailbox.thread)
|
||||||
|
|
||||||
|
protected val hitboxes = ArrayList<Poly>()
|
||||||
|
protected val collisionFilter: EnumSet<CollisionType> = EnumSet.of(CollisionType.NONE)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* true - whitelist, false - blacklist
|
||||||
|
*/
|
||||||
|
protected var collisionFilterMode = false
|
||||||
|
|
||||||
open var movementParameters: BaseMovementParameters = GlobalDefaults.movementParameters
|
open var movementParameters: BaseMovementParameters = GlobalDefaults.movementParameters
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -95,6 +115,7 @@ abstract class Entity(val world: World<*, *>) {
|
|||||||
throw IllegalStateException("Already removed")
|
throw IllegalStateException("Already removed")
|
||||||
|
|
||||||
isRemoved = true
|
isRemoved = true
|
||||||
|
mailbox.shutdownNow()
|
||||||
|
|
||||||
if (isSpawned) {
|
if (isSpawned) {
|
||||||
world.entities.remove(this)
|
world.entities.remove(this)
|
||||||
@ -102,53 +123,89 @@ abstract class Entity(val world: World<*, *>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* this function is executed sequentially
|
||||||
|
*/
|
||||||
fun think() {
|
fun think() {
|
||||||
if (!isSpawned) {
|
if (!isSpawned) {
|
||||||
throw IllegalStateException("Tried to think before spawning in world")
|
throw IllegalStateException("Tried to think before spawning in world")
|
||||||
}
|
}
|
||||||
|
|
||||||
move()
|
mailbox.executeQueuedTasks()
|
||||||
thinkInner()
|
thinkInner()
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract fun thinkInner()
|
protected abstract fun thinkInner()
|
||||||
|
|
||||||
protected open fun move() {
|
/**
|
||||||
|
* this function is executed in parallel
|
||||||
|
*/
|
||||||
|
// TODO: Ghost collisions occur, where objects trip on edges
|
||||||
|
open fun move() {
|
||||||
|
if (physicsSleepTicks > PHYSICS_TICKS_UNTIL_SLEEP) return
|
||||||
|
var physicsSleepTicks = physicsSleepTicks
|
||||||
velocity += world.gravity * Starbound.TICK_TIME_ADVANCE
|
velocity += world.gravity * Starbound.TICK_TIME_ADVANCE
|
||||||
position += velocity * Starbound.TICK_TIME_ADVANCE
|
|
||||||
|
|
||||||
if (hitboxes.isEmpty()) return
|
if (hitboxes.isEmpty()) {
|
||||||
|
position += velocity * Starbound.TICK_TIME_ADVANCE
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
for (i in 0 until 10) {
|
val steps = roundTowardsPositiveInfinity(velocity.length / 30.0 / hitboxes.stream().map { it.aabb }.reduce(AABB::combine).get().let { it.width.coerceAtLeast(it.height).coerceAtLeast(0.1) })
|
||||||
val localHitboxes = hitboxes.map { it + position }
|
val dt = Starbound.TICK_TIME_ADVANCE / steps
|
||||||
val polies = world.queryCollisions(localHitboxes.stream().map { it.aabb }.reduce(AABB::combine).get().enlarge(1.0, 1.0)).filter { !it.type.isEmpty }
|
|
||||||
if (polies.isEmpty()) break
|
|
||||||
|
|
||||||
val intersects = ArrayList<Poly.Penetration<CollisionPoly>>()
|
for (step in 0 until steps) {
|
||||||
|
position += velocity * dt
|
||||||
|
|
||||||
localHitboxes.forEach { hitbox ->
|
for (i in 0 until 10) {
|
||||||
polies.forEach { poly -> hitbox.intersect(poly.poly, poly)?.let { intersects.add(it) } }
|
val localHitboxes = hitboxes.map { it + position }
|
||||||
}
|
|
||||||
|
|
||||||
if (intersects.isEmpty())
|
val polies = world.queryCollisions(
|
||||||
break
|
localHitboxes.stream().map { it.aabb }.reduce(AABB::combine).get().enlarge(1.0, 1.0)
|
||||||
else {
|
).filter {
|
||||||
val max = intersects.max()
|
if (collisionFilterMode)
|
||||||
// resolve collision
|
it.type in collisionFilter
|
||||||
position += max.vector
|
else
|
||||||
// collision response
|
it.type !in collisionFilter
|
||||||
velocity -= max.axis * velocity.dot(max.axis)
|
}
|
||||||
|
|
||||||
val gravityDot = world.gravity.unitVector.dot(max.axis)
|
if (polies.isEmpty()) break
|
||||||
// impulse?
|
|
||||||
velocity += max.data.velocity * gravityDot * Starbound.TICK_TIME_ADVANCE
|
val intersects = ArrayList<Poly.Penetration<CollisionPoly>>()
|
||||||
// friction
|
|
||||||
velocity *= 1.0 - gravityDot * 0.08
|
localHitboxes.forEach { hitbox ->
|
||||||
|
polies.forEach { poly -> hitbox.intersect(poly.poly, poly)?.let { intersects.add(it) } }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (intersects.isEmpty()) {
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
val max = intersects.max()
|
||||||
|
// resolve collision
|
||||||
|
position += max.vector
|
||||||
|
// collision response
|
||||||
|
val response = max.axis * velocity.dot(max.axis * (1.0 + max.data.bounceFactor) * (1.0 + movementParameters.bounceFactor.orElse(0.0)))
|
||||||
|
velocity -= response
|
||||||
|
|
||||||
|
val gravityDot = world.gravity.unitVector.dot(max.axis)
|
||||||
|
// impulse?
|
||||||
|
velocity += max.data.velocity * gravityDot * dt
|
||||||
|
// friction
|
||||||
|
velocity *= 1.0 - gravityDot * 0.08
|
||||||
|
|
||||||
|
onTouch(response, max.axis, max.data)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (velocity.lengthSquared < 0.25) {
|
||||||
|
physicsSleepTicks++
|
||||||
|
}
|
||||||
|
|
||||||
|
this.physicsSleepTicks = physicsSleepTicks
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun onTouchSurface(velocity: Vector2d, normal: Vector2d) {
|
protected open fun onTouch(velocity: Vector2d, normal: Vector2d, poly: CollisionPoly) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -162,4 +219,8 @@ abstract class Entity(val world: World<*, *>) {
|
|||||||
open fun hurt(amount: Double): Boolean {
|
open fun hurt(amount: Double): Boolean {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val PHYSICS_TICKS_UNTIL_SLEEP = 16
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,5 +5,6 @@ import ru.dbotthepony.kvector.vector.Vector2d
|
|||||||
data class CollisionPoly(
|
data class CollisionPoly(
|
||||||
val poly: Poly,
|
val poly: Poly,
|
||||||
val type: CollisionType,
|
val type: CollisionType,
|
||||||
|
val bounceFactor: Double = 0.0,
|
||||||
val velocity: Vector2d = Vector2d.ZERO
|
val velocity: Vector2d = Vector2d.ZERO
|
||||||
)
|
)
|
||||||
|
@ -1,10 +1,15 @@
|
|||||||
package ru.dbotthepony.kstarbound.world.physics
|
package ru.dbotthepony.kstarbound.world.physics
|
||||||
|
|
||||||
enum class CollisionType(val isEmpty: Boolean) {
|
enum class CollisionType(val isEmpty: Boolean) {
|
||||||
|
// not loaded, block collisions by default
|
||||||
NULL(true),
|
NULL(true),
|
||||||
|
// air
|
||||||
NONE(true),
|
NONE(true),
|
||||||
|
// including stairs made of platforms
|
||||||
PLATFORM(false),
|
PLATFORM(false),
|
||||||
DYNAMIC(false),
|
DYNAMIC(false),
|
||||||
SLIPPERY(false),
|
SLIPPERY(false),
|
||||||
BLOCK(false);
|
BLOCK(false),
|
||||||
|
// stairs made out of blocks
|
||||||
|
BLOCK_SLOPE(false);
|
||||||
}
|
}
|
||||||
|
@ -201,7 +201,7 @@ class Poly private constructor(val edges: ImmutableList<Edge>, val vertices: Imm
|
|||||||
intersections.add(Penetration(normal, projectOther.component2() - projectThis.component1(), data))
|
intersections.add(Penetration(normal, projectOther.component2() - projectThis.component1(), data))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (intersections.last().penetration.absoluteValue <= EPSILON) {
|
if (intersections.last().penetration == 0.0) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -251,7 +251,6 @@ class Poly private constructor(val edges: ImmutableList<Edge>, val vertices: Imm
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object : TypeAdapterFactory {
|
companion object : TypeAdapterFactory {
|
||||||
const val EPSILON = 0.01
|
|
||||||
private val identity = Matrix3f.identity()
|
private val identity = Matrix3f.identity()
|
||||||
|
|
||||||
override fun <T : Any?> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
|
override fun <T : Any?> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
|
||||||
|
Loading…
Reference in New Issue
Block a user