145 lines
3.3 KiB
Kotlin
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()
|
|
}
|
|
}
|
|
}
|