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"]
|
||||
|
||||
val item = Registries.items.values.random()
|
||||
val rand = Random()
|
||||
|
||||
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.spawn()
|
||||
|
@ -14,7 +14,7 @@ import org.lwjgl.opengl.GLCapabilities
|
||||
import org.lwjgl.system.MemoryStack
|
||||
import org.lwjgl.system.MemoryUtil
|
||||
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_UNITf
|
||||
import ru.dbotthepony.kstarbound.Starbound
|
||||
@ -92,7 +92,7 @@ class StarboundClient : Closeable {
|
||||
// client specific executor which will accept tasks which involve probable
|
||||
// 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
|
||||
val backgroundExecutor = ForkJoinPool(Runtime.getRuntime().availableProcessors().coerceAtMost(4), {
|
||||
val executor = ForkJoinPool(Runtime.getRuntime().availableProcessors().coerceAtMost(4), {
|
||||
object : ForkJoinWorkerThread(it) {
|
||||
init {
|
||||
name = "Background Executor for '${thread.name}'-${threadCounter.incrementAndGet()}"
|
||||
@ -108,7 +108,7 @@ class StarboundClient : Closeable {
|
||||
}
|
||||
}, null, false)
|
||||
|
||||
val foregroundExecutor = ManualExecutorService(thread)
|
||||
val mailbox = MailboxExecutorService(thread)
|
||||
val capabilities: GLCapabilities
|
||||
|
||||
var viewportX: Int = 0
|
||||
@ -322,7 +322,7 @@ class StarboundClient : Closeable {
|
||||
}
|
||||
|
||||
private fun executeQueuedTasks() {
|
||||
foregroundExecutor.executeQueuedTasks()
|
||||
mailbox.executeQueuedTasks()
|
||||
|
||||
var next = openglCleanQueue.poll() as CleanRef?
|
||||
|
||||
|
@ -18,9 +18,9 @@ class StreamVertexBuilder(
|
||||
) {
|
||||
val builder = VertexBuilder(attributes, type, initialCapacity)
|
||||
|
||||
private val vao = client.foregroundExecutor.submit(Callable { VertexArrayObject() })
|
||||
private val vbo = client.foregroundExecutor.submit(Callable { BufferObject.VBO() })
|
||||
private val ebo = client.foregroundExecutor.submit(Callable { BufferObject.EBO() })
|
||||
private val vao = client.mailbox.submit(Callable { VertexArrayObject() })
|
||||
private val vbo = client.mailbox.submit(Callable { BufferObject.VBO() })
|
||||
private val ebo = client.mailbox.submit(Callable { BufferObject.EBO() })
|
||||
|
||||
private var initialized = false
|
||||
|
||||
|
@ -49,14 +49,14 @@ class TileRenderers(val client: StarboundClient) {
|
||||
fun getMaterialRenderer(defName: String): TileRenderer {
|
||||
return matCache.get(defName) {
|
||||
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 {
|
||||
return modCache.get(defName) {
|
||||
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
|
||||
|
||||
import ru.dbotthepony.kstarbound.client.render.entity.EntityRenderer
|
||||
import ru.dbotthepony.kstarbound.world.Chunk
|
||||
import ru.dbotthepony.kstarbound.world.ChunkPos
|
||||
import ru.dbotthepony.kstarbound.world.entities.Entity
|
||||
|
||||
class ClientChunk(world: ClientWorld, pos: ChunkPos) : Chunk<ClientWorld, ClientChunk>(world, pos){
|
||||
override fun foregroundChanges(cell: Cell) {
|
||||
|
@ -92,7 +92,7 @@ class ClientWorld(
|
||||
|
||||
isDirty = false
|
||||
|
||||
currentBakeTask = client.backgroundExecutor.submit(Callable {
|
||||
currentBakeTask = client.executor.submit(Callable {
|
||||
val meshes = LayeredRenderer(client)
|
||||
|
||||
for (x in 0 until renderRegionWidth) {
|
||||
|
@ -1,10 +1,8 @@
|
||||
package ru.dbotthepony.kstarbound.defs.image
|
||||
|
||||
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.Caffeine
|
||||
import com.github.benmanes.caffeine.cache.LoadingCache
|
||||
import com.github.benmanes.caffeine.cache.Scheduler
|
||||
import com.google.common.collect.ImmutableList
|
||||
import com.google.gson.JsonArray
|
||||
@ -35,7 +33,6 @@ import ru.dbotthepony.kvector.vector.Vector2i
|
||||
import ru.dbotthepony.kvector.vector.Vector4i
|
||||
import java.io.BufferedInputStream
|
||||
import java.io.FileNotFoundException
|
||||
import java.lang.ref.Cleaner
|
||||
import java.lang.ref.WeakReference
|
||||
import java.nio.ByteBuffer
|
||||
import java.time.Duration
|
||||
@ -131,7 +128,7 @@ class Image private constructor(
|
||||
|
||||
tex.textureMinFilter = GL45.GL_NEAREST
|
||||
tex.textureMagFilter = GL45.GL_NEAREST
|
||||
}, client.foregroundExecutor)
|
||||
}, client.mailbox)
|
||||
|
||||
tex
|
||||
}
|
||||
|
@ -1,17 +1,19 @@
|
||||
package ru.dbotthepony.kstarbound.util
|
||||
|
||||
import com.google.common.util.concurrent.Futures
|
||||
import it.unimi.dsi.fastutil.objects.ObjectHeapPriorityQueue
|
||||
import java.util.LinkedList
|
||||
import java.util.concurrent.Callable
|
||||
import java.util.concurrent.ConcurrentLinkedQueue
|
||||
import java.util.concurrent.Delayed
|
||||
import java.util.concurrent.Future
|
||||
import java.util.concurrent.FutureTask
|
||||
import java.util.concurrent.RejectedExecutionException
|
||||
import java.util.concurrent.ScheduledExecutorService
|
||||
import java.util.concurrent.ScheduledFuture
|
||||
import java.util.concurrent.TimeUnit
|
||||
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) {
|
||||
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 futureQueue = ConcurrentLinkedQueue<FutureTask<*>>()
|
||||
private val timerBacklog = ConcurrentLinkedQueue<Timer<*>>()
|
||||
@ -43,6 +45,12 @@ class ManualExecutorService(val thread: Thread = Thread.currentThread()) : Sched
|
||||
|
||||
private val timers = LinkedList<Timer<*>>()
|
||||
private val repeatableTimers = LinkedList<RepeatableTimer>()
|
||||
private val executionLock = ReentrantLock()
|
||||
|
||||
@Volatile
|
||||
private var isShutdown = false
|
||||
@Volatile
|
||||
private var isTerminated = false
|
||||
|
||||
private val timeOrigin = JVMTimeSource()
|
||||
|
||||
@ -94,84 +102,108 @@ class ManualExecutorService(val thread: Thread = Thread.currentThread()) : Sched
|
||||
fun executeQueuedTasks() {
|
||||
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) {
|
||||
next.run()
|
||||
next = executeQueue.poll()
|
||||
executionLock.withLock {
|
||||
timers.clear()
|
||||
repeatableTimers.clear()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var next2 = futureQueue.poll()
|
||||
executionLock.withLock {
|
||||
var next = executeQueue.poll()
|
||||
|
||||
while (next2 != null) {
|
||||
next2.run()
|
||||
Thread.interrupted()
|
||||
next2 = futureQueue.poll()
|
||||
}
|
||||
while (next != null) {
|
||||
if (isTerminated) return
|
||||
next.run()
|
||||
next = executeQueue.poll()
|
||||
}
|
||||
|
||||
var next3 = timerBacklog.poll()
|
||||
var next2 = futureQueue.poll()
|
||||
|
||||
while (next3 != null) {
|
||||
if (next3.isCancelled) {
|
||||
// do nothing
|
||||
} else if (next3.executeAt <= timeOrigin.nanos) {
|
||||
next3.run()
|
||||
while (next2 != null) {
|
||||
if (isTerminated) return
|
||||
next2.run()
|
||||
Thread.interrupted()
|
||||
} else {
|
||||
timers.enqueue(next3)
|
||||
next2 = futureQueue.poll()
|
||||
}
|
||||
|
||||
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) {
|
||||
if (next4.isCancelled) {
|
||||
// do nothing
|
||||
} else {
|
||||
repeatableTimers.enqueue(next4)
|
||||
next3 = timerBacklog.poll()
|
||||
}
|
||||
|
||||
next4 = repeatableTimersBacklog.poll()
|
||||
}
|
||||
var next4 = repeatableTimersBacklog.poll()
|
||||
|
||||
while (!timers.isEmpty()) {
|
||||
val first = timers.first
|
||||
while (next4 != null) {
|
||||
if (isTerminated) return
|
||||
|
||||
if (first.isCancelled) {
|
||||
timers.removeFirst()
|
||||
} else if (first.executeAt <= timeOrigin.nanos) {
|
||||
first.run()
|
||||
Thread.interrupted()
|
||||
timers.removeFirst()
|
||||
} else {
|
||||
break
|
||||
if (next4.isCancelled) {
|
||||
// do nothing
|
||||
} else {
|
||||
repeatableTimers.enqueue(next4)
|
||||
}
|
||||
|
||||
next4 = repeatableTimersBacklog.poll()
|
||||
}
|
||||
}
|
||||
|
||||
if (repeatableTimers.isNotEmpty()) {
|
||||
val executed = LinkedList<RepeatableTimer>()
|
||||
while (!timers.isEmpty()) {
|
||||
if (isTerminated) return
|
||||
val first = timers.first
|
||||
|
||||
while (repeatableTimers.isNotEmpty()) {
|
||||
val first = repeatableTimers.first
|
||||
|
||||
if (first.isDone) {
|
||||
repeatableTimers.removeFirst()
|
||||
} else if (first.next <= timeOrigin.nanos) {
|
||||
first.runAndReset()
|
||||
executed.add(first)
|
||||
repeatableTimers.removeFirst()
|
||||
if (first.isCancelled) {
|
||||
timers.removeFirst()
|
||||
} else if (first.executeAt <= timeOrigin.nanos) {
|
||||
first.run()
|
||||
Thread.interrupted()
|
||||
timers.removeFirst()
|
||||
} else {
|
||||
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) {
|
||||
if (isShutdown) throw RejectedExecutionException("This mailbox is shutting down")
|
||||
|
||||
if (isSameThread()) {
|
||||
command.run()
|
||||
} else {
|
||||
@ -181,19 +213,42 @@ class ManualExecutorService(val thread: Thread = Thread.currentThread()) : Sched
|
||||
}
|
||||
|
||||
override fun shutdown() {
|
||||
throw UnsupportedOperationException()
|
||||
isShutdown = true
|
||||
}
|
||||
|
||||
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 {
|
||||
return false
|
||||
return isShutdown
|
||||
}
|
||||
|
||||
override fun isTerminated(): Boolean {
|
||||
return false
|
||||
return isTerminated
|
||||
}
|
||||
|
||||
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> {
|
||||
if (isShutdown) throw RejectedExecutionException("This mailbox is shutting down")
|
||||
if (isSameThread()) return Futures.immediateFuture(task.call())
|
||||
return FutureTask(task).also { futureQueue.add(it); LockSupport.unpark(thread) }
|
||||
}
|
||||
|
||||
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) }
|
||||
return FutureTask { task.run(); result }.also { futureQueue.add(it); LockSupport.unpark(thread) }
|
||||
}
|
||||
|
||||
override fun submit(task: Runnable): Future<*> {
|
||||
if (isShutdown) throw RejectedExecutionException("This mailbox is shutting down")
|
||||
if (isSameThread()) { task.run(); return Futures.immediateVoidFuture() }
|
||||
return FutureTask { task.run() }.also { futureQueue.add(it); LockSupport.unpark(thread) }
|
||||
}
|
||||
|
||||
override fun <T : Any?> invokeAll(tasks: Collection<Callable<T>>): List<Future<T>> {
|
||||
if (isShutdown) throw RejectedExecutionException("This mailbox is shutting down")
|
||||
|
||||
if (isSameThread()) {
|
||||
return tasks.map { Futures.immediateFuture(it.call()) }
|
||||
} else {
|
||||
@ -228,6 +288,8 @@ class ManualExecutorService(val thread: Thread = Thread.currentThread()) : Sched
|
||||
timeout: Long,
|
||||
unit: TimeUnit
|
||||
): List<Future<T>> {
|
||||
if (isShutdown) throw RejectedExecutionException("This mailbox is shutting down")
|
||||
|
||||
if (isSameThread()) {
|
||||
return tasks.map { Futures.immediateFuture(it.call()) }
|
||||
} else {
|
||||
@ -239,6 +301,8 @@ class ManualExecutorService(val thread: Thread = Thread.currentThread()) : Sched
|
||||
if (tasks.isEmpty())
|
||||
throw NoSuchElementException("Provided task list is empty")
|
||||
|
||||
if (isShutdown) throw RejectedExecutionException("This mailbox is shutting down")
|
||||
|
||||
if (isSameThread()) {
|
||||
return tasks.first().call()
|
||||
} else {
|
||||
@ -250,6 +314,8 @@ class ManualExecutorService(val thread: Thread = Thread.currentThread()) : Sched
|
||||
if (tasks.isEmpty())
|
||||
throw NoSuchElementException("Provided task list is empty")
|
||||
|
||||
if (isShutdown) throw RejectedExecutionException("This mailbox is shutting down")
|
||||
|
||||
if (isSameThread()) {
|
||||
return tasks.first().call()
|
||||
} else {
|
||||
@ -258,6 +324,8 @@ class ManualExecutorService(val thread: Thread = Thread.currentThread()) : Sched
|
||||
}
|
||||
|
||||
fun <V> join(future: Future<V>): V {
|
||||
if (isShutdown) throw RejectedExecutionException("This mailbox is shutting down")
|
||||
|
||||
if (!isSameThread())
|
||||
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<*> {
|
||||
if (isShutdown) throw RejectedExecutionException("This mailbox is shutting down")
|
||||
val timer = Timer({ command.run() }, timeOrigin.nanos + TimeUnit.NANOSECONDS.convert(delay, unit))
|
||||
|
||||
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> {
|
||||
if (isShutdown) throw RejectedExecutionException("This mailbox is shutting down")
|
||||
|
||||
val timer = Timer(callable, timeOrigin.nanos + TimeUnit.NANOSECONDS.convert(delay, unit))
|
||||
|
||||
if (isSameThread() && delay <= 0L) {
|
||||
@ -305,6 +376,8 @@ class ManualExecutorService(val thread: Thread = Thread.currentThread()) : Sched
|
||||
period: Long,
|
||||
unit: TimeUnit
|
||||
): ScheduledFuture<*> {
|
||||
if (isShutdown) throw RejectedExecutionException("This mailbox is shutting down")
|
||||
|
||||
return RepeatableTimer(
|
||||
command,
|
||||
timeOrigin.nanos + TimeUnit.NANOSECONDS.convert(initialDelay, unit),
|
||||
@ -317,6 +390,8 @@ class ManualExecutorService(val thread: Thread = Thread.currentThread()) : Sched
|
||||
delay: Long,
|
||||
unit: TimeUnit
|
||||
): ScheduledFuture<*> {
|
||||
if (isShutdown) throw RejectedExecutionException("This mailbox is shutting down")
|
||||
|
||||
return RepeatableTimer(
|
||||
command,
|
||||
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.vector.Vector2d
|
||||
import java.util.*
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
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())
|
||||
|
||||
var isPhysicsDirty = false
|
||||
|
||||
private val collisionCache = ArrayList<AABB>()
|
||||
private val collisionCacheView = Collections.unmodifiableCollection(collisionCache)
|
||||
|
||||
protected open fun foregroundChanges(cell: Cell) {
|
||||
cellChanges(cell)
|
||||
tileChangeset++
|
||||
@ -264,42 +261,48 @@ abstract class Chunk<WorldType : World<WorldType, This>, This : Chunk<WorldType,
|
||||
protected open fun onEntityRemoved(entity: Entity) { }
|
||||
|
||||
fun addEntity(entity: Entity) {
|
||||
if (!entities.add(entity)) {
|
||||
throw IllegalArgumentException("Already having having entity $entity")
|
||||
}
|
||||
world.entityMoveLock.withLock {
|
||||
if (!entities.add(entity)) {
|
||||
throw IllegalArgumentException("Already having having entity $entity")
|
||||
}
|
||||
|
||||
changeset++
|
||||
onEntityAdded(entity)
|
||||
changeset++
|
||||
onEntityAdded(entity)
|
||||
}
|
||||
}
|
||||
|
||||
fun transferEntity(entity: Entity, otherChunk: Chunk<*, *>) {
|
||||
if (otherChunk == this)
|
||||
throw IllegalArgumentException("what?")
|
||||
world.entityMoveLock.withLock {
|
||||
if (otherChunk == this)
|
||||
throw IllegalArgumentException("what?")
|
||||
|
||||
if (this::class.java != otherChunk::class.java) {
|
||||
throw IllegalArgumentException("Incompatible types: $this !is $otherChunk")
|
||||
}
|
||||
if (this::class.java != otherChunk::class.java) {
|
||||
throw IllegalArgumentException("Incompatible types: $this !is $otherChunk")
|
||||
}
|
||||
|
||||
if (!entities.add(entity)) {
|
||||
throw IllegalArgumentException("Already containing $entity")
|
||||
}
|
||||
if (!entities.add(entity)) {
|
||||
throw IllegalArgumentException("Already containing $entity")
|
||||
}
|
||||
|
||||
changeset++
|
||||
onEntityTransferedToThis(entity, otherChunk as This)
|
||||
otherChunk.onEntityTransferedFromThis(entity, this as This)
|
||||
changeset++
|
||||
onEntityTransferedToThis(entity, otherChunk as This)
|
||||
otherChunk.onEntityTransferedFromThis(entity, this as This)
|
||||
|
||||
if (!otherChunk.entities.remove(entity)) {
|
||||
throw IllegalStateException("Unable to remove $entity from $otherChunk after transfer")
|
||||
if (!otherChunk.entities.remove(entity)) {
|
||||
throw IllegalStateException("Unable to remove $entity from $otherChunk after transfer")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun removeEntity(entity: Entity) {
|
||||
if (!entities.remove(entity)) {
|
||||
throw IllegalArgumentException("Already not having entity $entity")
|
||||
}
|
||||
world.entityMoveLock.withLock {
|
||||
if (!entities.remove(entity)) {
|
||||
throw IllegalArgumentException("Already not having entity $entity")
|
||||
}
|
||||
|
||||
changeset++
|
||||
onEntityRemoved(entity)
|
||||
changeset++
|
||||
onEntityRemoved(entity)
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
|
@ -1,20 +1,17 @@
|
||||
package ru.dbotthepony.kstarbound.world
|
||||
|
||||
import com.google.common.base.Predicate
|
||||
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.ReferenceOpenHashSet
|
||||
import ru.dbotthepony.kstarbound.client.world.ClientWorld
|
||||
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.IChunkCell
|
||||
import ru.dbotthepony.kstarbound.world.api.TileView
|
||||
import ru.dbotthepony.kstarbound.world.entities.Entity
|
||||
import ru.dbotthepony.kstarbound.world.entities.WorldObject
|
||||
import ru.dbotthepony.kstarbound.world.physics.CollisionPoly
|
||||
import ru.dbotthepony.kstarbound.world.physics.CollisionType
|
||||
import ru.dbotthepony.kstarbound.world.physics.Poly
|
||||
import ru.dbotthepony.kvector.api.IStruct2d
|
||||
import ru.dbotthepony.kvector.api.IStruct2i
|
||||
@ -24,6 +21,8 @@ import ru.dbotthepony.kvector.vector.Vector2d
|
||||
import ru.dbotthepony.kvector.vector.Vector2i
|
||||
import java.lang.ref.ReferenceQueue
|
||||
import java.lang.ref.WeakReference
|
||||
import java.util.concurrent.ForkJoinPool
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
import java.util.random.RandomGenerator
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
@ -47,7 +46,7 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
|
||||
|
||||
val background = TileView.Background(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
|
||||
|
||||
@ -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 random: RandomGenerator = RandomGenerator.of("Xoroshiro128PlusPlus")
|
||||
|
||||
var gravity = Vector2d(0.0, -EARTH_FREEFALL_ACCELERATION)
|
||||
|
||||
var ticks = 0
|
||||
private set
|
||||
// used by world chunks to synchronize entity additions/transfer
|
||||
val entityMoveLock = ReentrantLock()
|
||||
|
||||
fun think() {
|
||||
try {
|
||||
executor.executeQueuedTasks()
|
||||
mailbox.executeQueuedTasks()
|
||||
ForkJoinPool.commonPool().submit(ParallelPerform(entities.spliterator(), Entity::move)).join()
|
||||
mailbox.executeQueuedTasks()
|
||||
|
||||
thinkInner()
|
||||
ticks++
|
||||
} catch(err: Throwable) {
|
||||
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
|
||||
|
||||
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> {
|
||||
val result = ArrayList<CollisionPoly>()
|
||||
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) {
|
||||
val cell = getCell(x, y) ?: continue
|
||||
|
||||
for (poly in cell.polies) {
|
||||
result.add(CollisionPoly(poly, cell.foreground.material.collisionKind, Vector2d(EARTH_FREEFALL_ACCELERATION, 0.0)))
|
||||
}
|
||||
result.add(CollisionPoly(
|
||||
BLOCK_POLY + Vector2d(x.toDouble(), y.toDouble()),
|
||||
cell.foreground.material.collisionKind,
|
||||
//velocity = Vector2d(EARTH_FREEFALL_ACCELERATION, 0.0)
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
val polies: Collection<Poly> get() {
|
||||
return listOf(rect + Vector2d(this.x.toDouble(), this.y.toDouble()))
|
||||
}
|
||||
|
||||
val foreground: ITileState
|
||||
val background: ITileState
|
||||
val liquid: ILiquidState
|
||||
|
@ -4,12 +4,16 @@ import ru.dbotthepony.kstarbound.GlobalDefaults
|
||||
import ru.dbotthepony.kstarbound.Starbound
|
||||
import ru.dbotthepony.kstarbound.client.StarboundClient
|
||||
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.World
|
||||
import ru.dbotthepony.kstarbound.world.physics.CollisionPoly
|
||||
import ru.dbotthepony.kstarbound.world.physics.CollisionType
|
||||
import ru.dbotthepony.kstarbound.world.physics.Poly
|
||||
import ru.dbotthepony.kvector.util2d.AABB
|
||||
import ru.dbotthepony.kvector.vector.Vector2d
|
||||
import java.util.EnumSet
|
||||
|
||||
abstract class Entity(val world: World<*, *>) {
|
||||
var chunk: Chunk<*, *>? = null
|
||||
@ -49,6 +53,7 @@ abstract class Entity(val world: World<*, *>) {
|
||||
|
||||
val old = field
|
||||
field = Vector2d(world.x.cell(value.x), world.y.cell(value.y))
|
||||
physicsSleepTicks = 0
|
||||
|
||||
if (isSpawned && !isRemoved) {
|
||||
val oldChunkPos = world.chunkFromCell(old)
|
||||
@ -61,7 +66,22 @@ abstract class Entity(val world: World<*, *>) {
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
/**
|
||||
@ -95,6 +115,7 @@ abstract class Entity(val world: World<*, *>) {
|
||||
throw IllegalStateException("Already removed")
|
||||
|
||||
isRemoved = true
|
||||
mailbox.shutdownNow()
|
||||
|
||||
if (isSpawned) {
|
||||
world.entities.remove(this)
|
||||
@ -102,53 +123,89 @@ abstract class Entity(val world: World<*, *>) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* this function is executed sequentially
|
||||
*/
|
||||
fun think() {
|
||||
if (!isSpawned) {
|
||||
throw IllegalStateException("Tried to think before spawning in world")
|
||||
}
|
||||
|
||||
move()
|
||||
mailbox.executeQueuedTasks()
|
||||
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
|
||||
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 localHitboxes = hitboxes.map { it + position }
|
||||
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 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 dt = Starbound.TICK_TIME_ADVANCE / steps
|
||||
|
||||
val intersects = ArrayList<Poly.Penetration<CollisionPoly>>()
|
||||
for (step in 0 until steps) {
|
||||
position += velocity * dt
|
||||
|
||||
localHitboxes.forEach { hitbox ->
|
||||
polies.forEach { poly -> hitbox.intersect(poly.poly, poly)?.let { intersects.add(it) } }
|
||||
}
|
||||
for (i in 0 until 10) {
|
||||
val localHitboxes = hitboxes.map { it + position }
|
||||
|
||||
if (intersects.isEmpty())
|
||||
break
|
||||
else {
|
||||
val max = intersects.max()
|
||||
// resolve collision
|
||||
position += max.vector
|
||||
// collision response
|
||||
velocity -= max.axis * velocity.dot(max.axis)
|
||||
val polies = world.queryCollisions(
|
||||
localHitboxes.stream().map { it.aabb }.reduce(AABB::combine).get().enlarge(1.0, 1.0)
|
||||
).filter {
|
||||
if (collisionFilterMode)
|
||||
it.type in collisionFilter
|
||||
else
|
||||
it.type !in collisionFilter
|
||||
}
|
||||
|
||||
val gravityDot = world.gravity.unitVector.dot(max.axis)
|
||||
// impulse?
|
||||
velocity += max.data.velocity * gravityDot * Starbound.TICK_TIME_ADVANCE
|
||||
// friction
|
||||
velocity *= 1.0 - gravityDot * 0.08
|
||||
if (polies.isEmpty()) break
|
||||
|
||||
val intersects = ArrayList<Poly.Penetration<CollisionPoly>>()
|
||||
|
||||
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 {
|
||||
return false
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val PHYSICS_TICKS_UNTIL_SLEEP = 16
|
||||
}
|
||||
}
|
||||
|
@ -5,5 +5,6 @@ import ru.dbotthepony.kvector.vector.Vector2d
|
||||
data class CollisionPoly(
|
||||
val poly: Poly,
|
||||
val type: CollisionType,
|
||||
val bounceFactor: Double = 0.0,
|
||||
val velocity: Vector2d = Vector2d.ZERO
|
||||
)
|
||||
|
@ -1,10 +1,15 @@
|
||||
package ru.dbotthepony.kstarbound.world.physics
|
||||
|
||||
enum class CollisionType(val isEmpty: Boolean) {
|
||||
// not loaded, block collisions by default
|
||||
NULL(true),
|
||||
// air
|
||||
NONE(true),
|
||||
// including stairs made of platforms
|
||||
PLATFORM(false),
|
||||
DYNAMIC(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))
|
||||
}
|
||||
|
||||
if (intersections.last().penetration.absoluteValue <= EPSILON) {
|
||||
if (intersections.last().penetration == 0.0) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
@ -251,7 +251,6 @@ class Poly private constructor(val edges: ImmutableList<Edge>, val vertices: Imm
|
||||
}
|
||||
|
||||
companion object : TypeAdapterFactory {
|
||||
const val EPSILON = 0.01
|
||||
private val identity = Matrix3f.identity()
|
||||
|
||||
override fun <T : Any?> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
|
||||
|
Loading…
Reference in New Issue
Block a user