KStarbound/src/main/kotlin/ru/dbotthepony/kstarbound/util/ExecutionSpinner.kt

145 lines
3.3 KiB
Kotlin

package ru.dbotthepony.kstarbound.util
import org.apache.logging.log4j.LogManager
import org.lwjgl.system.MemoryStack
import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.WindowsBindings
import java.util.concurrent.atomic.AtomicInteger
import java.util.concurrent.locks.LockSupport
import java.util.function.BooleanSupplier
class ExecutionSpinner(private val waiter: Runnable, private val spinner: BooleanSupplier, private val timeBetweenFrames: Long) : Runnable {
init {
Companion
}
private var lastRender = System.nanoTime()
private var frameRenderTime = 0L
private val frameRenderTimes = LongArray(60) { 1L }
private var frameRenderIndex = 0
private val renderWaitTimes = LongArray(60) { 1L }
private var renderWaitIndex = 0
val averageRenderWait: Double get() {
var sum = 0.0
for (value in renderWaitTimes)
sum += value
if (sum == 0.0)
return 0.0
sum /= 1_000_000_000.0
return sum / renderWaitTimes.size
}
val averageRenderTime: Double get() {
var sum = 0.0
for (value in frameRenderTimes)
sum += value
if (sum == 0.0)
return 0.0
sum /= 1_000_000_000.0
return sum / frameRenderTimes.size
}
private fun timeUntilNextFrame(): Long {
return Starbound.TIMESTEP_NANOS - (System.nanoTime() - lastRender) - frameRenderTime - compensate
}
private var compensate = 0L
private var carrier: Thread? = null
private val pause = AtomicInteger()
fun pause() {
pause.incrementAndGet()
}
fun unpause() {
if (pause.addAndGet(-100) <= 0) {
carrier?.let { LockSupport.unpark(it) }
}
}
fun spin(): Boolean {
carrier = Thread.currentThread()
while (pause.get() > 0) {
waiter.run()
LockSupport.park()
}
var diff = timeUntilNextFrame()
while (diff > 0L) {
waiter.run()
diff = timeUntilNextFrame()
LockSupport.parkNanos(diff)
}
compensate = -diff
val mark = System.nanoTime()
val result = spinner.asBoolean
frameRenderTime = System.nanoTime() - mark
frameRenderTimes[++frameRenderIndex % frameRenderTimes.size] = frameRenderTime
renderWaitTimes[++renderWaitIndex % renderWaitTimes.size] = System.nanoTime() - lastRender
lastRender = System.nanoTime()
return result
}
override fun run() {
while (spin()) {}
}
companion object {
private val LOGGER = LogManager.getLogger()
private var SYSTEM_SCHEDULER_RESOLUTION = 1_000_000L
init {
val bindings = WindowsBindings.INSTANCE
if (bindings != null) {
MemoryStack.stackPush().use { stack ->
val minimum = stack.mallocLong(1)
val maximum = stack.mallocLong(1)
val current = stack.mallocLong(1)
minimum.put(0L)
maximum.put(0L)
current.put(0L)
minimum.position(0)
maximum.position(0)
current.position(0)
bindings.NtQueryTimerResolution(minimum, maximum, current)
SYSTEM_SCHEDULER_RESOLUTION = current[0] * 100L
LOGGER.info("NtQueryTimerResolution(): {} ns/{} ns/{} ns min/max/current", minimum[0] * 100L, maximum[0] * 100L, current[0] * 100L)
}
}
val thread = object : Thread("Process scheduler timer hack thread") {
override fun run() {
while (true) {
try {
sleep(Int.MAX_VALUE.toLong())
} catch (err: InterruptedException) {
LOGGER.error("Timer hack thread was interrupted, ignoring.")
}
}
}
}
thread.isDaemon = true
thread.start()
}
}
}