new thread pooling strategy and test program WIP

This commit is contained in:
Minjae Song
2018-12-14 22:53:25 +09:00
parent 6f49dcff4b
commit 513c5a17eb
8 changed files with 231 additions and 32 deletions

View File

@@ -204,7 +204,7 @@ open class IngameInstance(val batch: SpriteBatch) : Screen {
fun insertionSortLastElem(arr: ArrayList<Actor>) { fun insertionSortLastElem(arr: ArrayList<Actor>) {
lock(ReentrantLock()) { ReentrantLock().lock {
var j = arr.lastIndex - 1 var j = arr.lastIndex - 1
val x = arr.last() val x = arr.last()
while (j >= 0 && arr[j] > x) { while (j >= 0 && arr[j] > x) {
@@ -215,13 +215,14 @@ open class IngameInstance(val batch: SpriteBatch) : Screen {
} }
} }
inline fun lock(lock: Lock, body: () -> Unit) { }
lock.lock()
try { inline fun Lock.lock(body: () -> Unit) {
body() this.lock()
} try {
finally { body()
lock.unlock()
}
} }
} finally {
this.unlock()
}
}

View File

@@ -1,6 +1,10 @@
package net.torvald.terrarum.concurrent package net.torvald.terrarum.concurrent
import net.torvald.terrarum.Terrarum import net.torvald.terrarum.Terrarum
import net.torvald.terrarum.lock
import java.util.concurrent.locks.Lock
import java.util.concurrent.locks.ReentrantLock
import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
typealias RunnableFun = () -> Unit typealias RunnableFun = () -> Unit
@@ -57,7 +61,7 @@ object ThreadParallel {
* Primitive locking * Primitive locking
*/ */
fun allFinished(): Boolean { fun allFinished(): Boolean {
pool.forEach { if (it?.state != Thread.State.TERMINATED) return false } pool.forEach { if (it != null && it.state != Thread.State.TERMINATED) return false }
return true return true
} }
} }
@@ -71,24 +75,30 @@ object BlockingThreadPool {
val threadCount = Terrarum.THREADS // modify this to your taste val threadCount = Terrarum.THREADS // modify this to your taste
private val pool: Array<Thread?> = Array(threadCount, { null }) private val pool: Array<Thread?> = Array(threadCount, { null })
private var tasks: List<RunnableFun> = ArrayList<RunnableFun>() private var tasks: List<RunnableFun> = ArrayList<RunnableFun>()
private var allTasksDone = false @Volatile private var dispatchedTasks = 0
private var dispatchedTasks = 0
private var threadPrefix = "" private var threadPrefix = ""
/** @return false on failure (likely the previous jobs not finished), true on success */
fun map(prefix: String, tasks: List<RunnableFun>) = setTasks(tasks, prefix) fun map(prefix: String, tasks: List<RunnableFun>) = setTasks(tasks, prefix)
fun setTasks(tasks: List<RunnableFun>, prefix: String) { /** @return false on failure (likely the previous jobs not finished), true on success */
fun setTasks(tasks: List<RunnableFun>, prefix: String): Boolean {
if (!allFinished())
return false
this.tasks = tasks this.tasks = tasks
dispatchedTasks = 0 dispatchedTasks = 0
threadPrefix = prefix threadPrefix = prefix
return true
} }
fun dequeueTask(): RunnableFun { private fun dequeueTask(): RunnableFun {
dispatchedTasks += 1 dispatchedTasks += 1
return tasks[dispatchedTasks - 1] return tasks[dispatchedTasks - 1]
} }
fun startAllWaitForDie() { fun startAllWaitForDie() {
while (!allTasksDone) { while (dispatchedTasks <= tasks.lastIndex) {
// marble rolling down the slanted channel-track of threads, if a channel is empty (a task assigned // marble rolling down the slanted channel-track of threads, if a channel is empty (a task assigned
// to the thread is dead) the marble will roll into the channel, and the marble is a task #MarbleMachineX // to the thread is dead) the marble will roll into the channel, and the marble is a task #MarbleMachineX
for (i in 0 until threadCount) { for (i in 0 until threadCount) {
@@ -96,7 +106,8 @@ object BlockingThreadPool {
// of marbles and put it into an empty channel whenever we encounter one // of marbles and put it into an empty channel whenever we encounter one
// SO WHAT WE DO is first fill any empty channels: // SO WHAT WE DO is first fill any empty channels:
if (pool[i] == null || pool[i]!!.state == Thread.State.TERMINATED) { if (dispatchedTasks <= tasks.lastIndex && // because cache invalidation damnit
(pool[i] == null || pool[i]!!.state == Thread.State.TERMINATED)) {
pool[i] = Thread(dequeueTask().makeRunnable(), "$threadPrefix-$dispatchedTasks") // thread name index is one-based pool[i] = Thread(dequeueTask().makeRunnable(), "$threadPrefix-$dispatchedTasks") // thread name index is one-based
pool[i]!!.start() pool[i]!!.start()
} }
@@ -109,6 +120,11 @@ object BlockingThreadPool {
} }
} }
fun allFinished(): Boolean {
pool.forEach { if (it != null && it.state != Thread.State.TERMINATED) return false }
return true
}
private fun RunnableFun.makeRunnable() = Runnable { this.invoke() } private fun RunnableFun.makeRunnable() = Runnable { this.invoke() }
} }

View File

@@ -869,7 +869,7 @@ open class Ingame(batch: SpriteBatch) : IngameInstance(batch) {
} }
private fun insertionSortLastElemAV(arr: ArrayList<ActorWithBody>) { // out-projection doesn't work, duh private fun insertionSortLastElemAV(arr: ArrayList<ActorWithBody>) { // out-projection doesn't work, duh
lock(ReentrantLock()) { ReentrantLock().lock {
var j = arr.lastIndex - 1 var j = arr.lastIndex - 1
val x = arr.last() val x = arr.last()
while (j >= 0 && arr[j] > x) { while (j >= 0 && arr[j] > x) {

View File

@@ -9,6 +9,7 @@ import net.torvald.terrarum.itemproperties.ItemCodex
import net.torvald.terrarum.itemproperties.ItemCodex.ITEM_DYNAMIC import net.torvald.terrarum.itemproperties.ItemCodex.ITEM_DYNAMIC
import net.torvald.terrarum.itemproperties.ItemCodex.ITEM_WALLS import net.torvald.terrarum.itemproperties.ItemCodex.ITEM_WALLS
import net.torvald.terrarum.itemproperties.ItemID import net.torvald.terrarum.itemproperties.ItemID
import net.torvald.terrarum.lock
import net.torvald.terrarum.modulebasegame.Ingame import net.torvald.terrarum.modulebasegame.Ingame
import java.util.* import java.util.*
import java.util.concurrent.locks.Lock import java.util.concurrent.locks.Lock
@@ -253,7 +254,7 @@ class ActorInventory(val actor: Pocketed, var maxCapacity: Int, var capacityMode
return itemList[index] return itemList[index]
} }
private fun insertionSortLastElem(arr: ArrayList<InventoryPair>) { private fun insertionSortLastElem(arr: ArrayList<InventoryPair>) {
lock(ReentrantLock()) { ReentrantLock().lock {
var j = arr.lastIndex - 1 var j = arr.lastIndex - 1
val x = arr.last() val x = arr.last()
while (j >= 0 && arr[j].item > x.item) { while (j >= 0 && arr[j].item > x.item) {
@@ -284,15 +285,6 @@ class ActorInventory(val actor: Pocketed, var maxCapacity: Int, var capacityMode
} }
return -(low + 1) // key not found return -(low + 1) // key not found
} }
inline fun lock(lock: Lock, body: () -> Unit) {
lock.lock()
try {
body()
}
finally {
lock.unlock()
}
}
} }
data class InventoryPair(val item: GameItem, var amount: Int) data class InventoryPair(val item: GameItem, var amount: Int)

View File

@@ -76,8 +76,8 @@ object WorldSimulator {
fun moveFluids(delta: Float) { fun moveFluids(delta: Float) {
makeFluidMapFromWorld() makeFluidMapFromWorld()
//simCompression() simCompression()
for (y in 1 until fluidMap.size - 1) { /*for (y in 1 until fluidMap.size - 1) {
for (x in 1 until fluidMap[0].size - 1) { for (x in 1 until fluidMap[0].size - 1) {
val worldX = x + updateXFrom val worldX = x + updateXFrom
val worldY = y + updateYFrom val worldY = y + updateYFrom
@@ -98,7 +98,7 @@ object WorldSimulator {
fluidNewMap[y + 1][x] += remainingMass fluidNewMap[y + 1][x] += remainingMass
} }
} }
} }*/
fluidmapToWorld() fluidmapToWorld()
} }

View File

@@ -0,0 +1,180 @@
package net.torvald.terrarum.tests
import com.badlogic.gdx.Gdx
import com.badlogic.gdx.Input
import com.badlogic.gdx.InputAdapter
import com.badlogic.gdx.ScreenAdapter
import com.badlogic.gdx.backends.lwjgl.LwjglApplication
import com.badlogic.gdx.backends.lwjgl.LwjglApplicationConfiguration
import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.graphics.OrthographicCamera
import com.badlogic.gdx.graphics.Pixmap
import com.badlogic.gdx.graphics.Texture
import com.badlogic.gdx.graphics.g2d.SpriteBatch
import com.badlogic.gdx.graphics.glutils.ShaderProgram
import com.sudoplay.joise.Joise
import com.sudoplay.joise.module.ModuleBasisFunction
import com.sudoplay.joise.module.ModuleFractal
import com.sudoplay.joise.module.ModuleScaleDomain
import com.sudoplay.joise.module.ModuleScaleOffset
import net.torvald.random.HQRNG
import net.torvald.terrarum.AppLoader
import net.torvald.terrarum.AppLoader.printdbg
import net.torvald.terrarum.concurrent.BlockingThreadPool
import net.torvald.terrarum.concurrent.RunnableFun
import net.torvald.terrarum.concurrent.ParallelUtils.sliceEvenly
import net.torvald.terrarum.concurrent.ThreadParallel
import net.torvald.terrarum.inUse
import net.torvald.terrarum.modulebasegame.Ingame
import net.torvald.terrarum.roundInt
import kotlin.math.absoluteValue
import kotlin.system.measureNanoTime
import kotlin.system.measureTimeMillis
/**
* Created by minjaesong on 2018-12-14.
*/
class NoiseGenerator : ScreenAdapter() {
lateinit var batch: SpriteBatch
lateinit var camera: OrthographicCamera
lateinit var pixmap: Pixmap
lateinit var texture: Texture
private val IMAGE_SIZE = 1024
private val IMAGE_SIZEF = IMAGE_SIZE.toFloat()
private val IMAGE_SIZED = IMAGE_SIZE.toDouble()
private val RNG = HQRNG()
override fun show() {
Gdx.input.inputProcessor = NoiseGeneratorController(this)
batch = SpriteBatch()
camera = OrthographicCamera(AppLoader.appConfig.width.toFloat(), AppLoader.appConfig.height.toFloat())
camera.setToOrtho(true, AppLoader.appConfig.width.toFloat(), AppLoader.appConfig.height.toFloat())
camera.update()
Gdx.gl20.glViewport(0, 0, AppLoader.appConfig.width, AppLoader.appConfig.height)
pixmap = Pixmap(IMAGE_SIZE, IMAGE_SIZE, Pixmap.Format.RGBA8888)
texture = Texture(1, 1, Pixmap.Format.RGBA8888)
}
var regenerate = true
private var pixelsInSingleJob = (IMAGE_SIZE * IMAGE_SIZE) / 16 // CHANGE THIS VALUE HERE
private val jobsCount: Int
get() = (IMAGE_SIZE * IMAGE_SIZE) / pixelsInSingleJob
private val rawPixelsList: List<IntRange>
get() = (0 until IMAGE_SIZE * IMAGE_SIZE).sliceEvenly(jobsCount)
private fun makeGenFun(seed: Long, index: Int) = { //i: Int ->
val module = ModuleFractal()
module.setType(ModuleFractal.FractalType.BILLOW)
module.setAllSourceBasisTypes(ModuleBasisFunction.BasisType.GRADIENT)
module.setAllSourceInterpolationTypes(ModuleBasisFunction.InterpolationType.QUINTIC)
module.setNumOctaves(10)
module.setFrequency(8.0)
module.seed = seed
val moduleScale = ModuleScaleOffset()
moduleScale.setSource(module)
moduleScale.setScale(0.5)
moduleScale.setOffset(0.0)
val noiseModule = Joise(moduleScale)
for (c in rawPixelsList[index]) {
val x = c % IMAGE_SIZE
val y = c / IMAGE_SIZE
val uvX = x / IMAGE_SIZED
val uvY = y / IMAGE_SIZED
val noiseValue = noiseModule.get(uvX, uvY).absoluteValue
val rgb = (noiseValue * 255.0).roundInt()
pixmap.drawPixel(x, y, (rgb shl 24) or (rgb shl 16) or (rgb shl 8) or 0xFF)
}
}
private var timerStart = 0L
private var timerFired = false
override fun render(delta: Float) {
Gdx.graphics.setTitle(Ingame.getCanonicalTitle())
// regen
if (timerFired && BlockingThreadPool.allFinished()) {
val timeTook = System.currentTimeMillis() - timerStart
timerFired = false
printdbg(this, "> $timeTook ms")
}
if (regenerate && BlockingThreadPool.allFinished()) {
printdbg(this, "Reticulating splines...")
regenerate = false
// don't join while rendering noise
timerStart = System.currentTimeMillis()
timerFired = true
val seed = RNG.nextLong()
val jobs = List(jobsCount) { makeGenFun(seed, it) }
BlockingThreadPool.setTasks(jobs, "")
BlockingThreadPool.startAllWaitForDie()
}
// render
texture.dispose()
texture = Texture(pixmap)
batch.inUse {
batch.color = Color.WHITE
batch.draw(texture, 0f, 0f)
}
}
override fun pause() {
super.pause()
}
override fun resume() {
super.resume()
}
override fun resize(width: Int, height: Int) {
super.resize(width, height)
}
override fun dispose() {
pixmap.dispose()
texture.dispose()
}
}
class NoiseGeneratorController(val host: NoiseGenerator) : InputAdapter() {
override fun keyDown(keycode: Int): Boolean {
if (keycode == Input.Keys.SPACE) {
host.regenerate = true
}
return true
}
}
fun main(args: Array<String>) {
ShaderProgram.pedantic = false
val appConfig = LwjglApplicationConfiguration()
appConfig.vSyncEnabled = false
appConfig.resizable = false//true;
appConfig.width = 1024
appConfig.height = 1024
appConfig.backgroundFPS = 9999
appConfig.foregroundFPS = 9999
appConfig.forceExit = false
LwjglApplication(AppLoader(appConfig, NoiseGenerator()), appConfig)
}

View File

@@ -9,6 +9,7 @@ import com.badlogic.gdx.graphics.Texture
import com.badlogic.gdx.graphics.g2d.SpriteBatch import com.badlogic.gdx.graphics.g2d.SpriteBatch
import com.badlogic.gdx.graphics.glutils.ShaderProgram import com.badlogic.gdx.graphics.glutils.ShaderProgram
import net.torvald.terrarum.* import net.torvald.terrarum.*
import net.torvald.terrarum.modulebasegame.Ingame
import net.torvald.terrarum.ui.UINSMenu import net.torvald.terrarum.ui.UINSMenu
/** /**
@@ -83,6 +84,9 @@ class UITestPad1 : ScreenAdapter() {
var _dct = 0f var _dct = 0f
override fun render(delta: Float) { override fun render(delta: Float) {
Gdx.graphics.setTitle(Ingame.getCanonicalTitle())
// UPDATE // UPDATE
nsMenu.update(delta) nsMenu.update(delta)

View File

@@ -111,7 +111,13 @@ class UINSMenu(
} }
private fun popSubMenu() { private fun popSubMenu() {
if (listStack.size == 1) throw Error("Tried to pop root menu") if (listStack.size == 1) {
System.err.println("[UINSMenu] Tried to pop root menu")
Thread.currentThread().getStackTrace().forEach {
System.err.println(it)
}
return
}
val poppedUIItem = listStack.pop() val poppedUIItem = listStack.pop()
width -= poppedUIItem.ui.width width -= poppedUIItem.ui.width