diff --git a/.idea/libraries/lib.xml b/.idea/libraries/lib.xml index 8759a8be1..65eddedc5 100644 --- a/.idea/libraries/lib.xml +++ b/.idea/libraries/lib.xml @@ -19,6 +19,7 @@ + diff --git a/src/net/torvald/terrarum/DefaultConfig.kt b/src/net/torvald/terrarum/DefaultConfig.kt index c94fb8451..5f3b367b3 100644 --- a/src/net/torvald/terrarum/DefaultConfig.kt +++ b/src/net/torvald/terrarum/DefaultConfig.kt @@ -20,7 +20,8 @@ object DefaultConfig { jsonObject.addProperty("imtooyoungtodie", false) // no perma-death jsonObject.addProperty("language", AppLoader.getSysLang()) jsonObject.addProperty("notificationshowuptime", 6500) - jsonObject.addProperty("multithread", false) // experimental! + jsonObject.addProperty("multithread", true) // experimental! + jsonObject.addProperty("multithreadedlight", false) // experimental! diff --git a/src/net/torvald/terrarum/concurrent/ThreadParallel.kt b/src/net/torvald/terrarum/concurrent/ThreadParallel.kt index 951e407cd..c6bc08c6a 100644 --- a/src/net/torvald/terrarum/concurrent/ThreadParallel.kt +++ b/src/net/torvald/terrarum/concurrent/ThreadParallel.kt @@ -6,7 +6,9 @@ import net.torvald.terrarum.Terrarum * Created by minjaesong on 2016-05-25. */ object ThreadParallel { - private val pool: Array = Array(Terrarum.THREADS, { null }) + val threads = Terrarum.THREADS // modify this to your taste + + private val pool: Array = Array(threads, { null }) /** * Map Runnable object to certain index of the thread pool. @@ -14,18 +16,21 @@ object ThreadParallel { * @param runnable * @param prefix Will name each thread like "Foo-1", "Foo-2", etc. */ - fun map(index: Int, runnable: Runnable, prefix: String) { + fun map(index: Int, prefix: String, runnable: Runnable) { pool[index] = Thread(runnable, "$prefix-$index") } - fun map(index: Int, runFunc: (Int) -> Unit, prefix: String) { + /** + * @param runFunc A function that takes an int input (the index), and returns nothing + */ + fun map(index: Int, prefix: String, runFunc: (Int) -> Unit) { val runnable = object : Runnable { override fun run() { runFunc(index) } } - map(index, runnable, prefix) + map(index, prefix, runnable) } /** @@ -50,4 +55,81 @@ object ThreadParallel { pool.forEach { if (it?.state != Thread.State.TERMINATED) return false } return true } +} + +object ParallelUtils { + fun Iterable.parallelMap(transform: (T) -> R): List { + val tasks = this.sliceEvenly(ThreadParallel.threads) + val destination = Array(ThreadParallel.threads) { ArrayList() } + tasks.forEachIndexed { index, list -> + ThreadParallel.map(index, "ParallelUtils.parallelMap@${this.javaClass.canonicalName}") { + for (item in list) + destination[index].add(transform(item as T)) + } + } + + ThreadParallel.startAllWaitForDie() + + return destination.flatten() + } + + /** + * Shallow flat of the array + */ + fun Array>.flatten(): List { + val al = ArrayList() + this.forEach { it.forEach { al.add(it) } } + return al + } + + /** + * Shallow flat of the iterable + */ + fun Iterable>.flatten(): List { + val al = ArrayList() + this.forEach { it.forEach { al.add(it) } } + return al + } + + /** + * Shallow flat of the array + */ + fun Array>.flatten(): List { + val al = ArrayList() + this.forEach { it.forEach { al.add(it) } } + return al + } + + fun Iterable<*>.sliceEvenly(slices: Int): List> = this.toList().sliceEvenly(slices) + + fun List<*>.sliceEvenly(slices: Int): List> { + return (0 until slices).map { + this.subList( + this.size.toFloat().div(slices).times(it).roundInt(), + this.size.toFloat().div(slices).times(it + 1).roundInt() + ) + } + } + + fun Array<*>.sliceEvenly(slices: Int): List> { + return (0 until slices).map { + this.sliceArray( + this.size.toFloat().div(slices).times(it).roundInt() until + this.size.toFloat().div(slices).times(it + 1).roundInt() + ) + } + } + + fun IntRange.sliceEvenly(slices: Int): List { + if (this.step != 1) throw UnsupportedOperationException("Sorry, step != 1") + val size = this.last - this.first + 1f + + return (0 until slices).map { + size.div(slices).times(it).roundInt() until + size.div(slices).times(it + 1).roundInt() + } + } + + + private inline fun Float.roundInt(): Int = Math.round(this) } \ No newline at end of file diff --git a/src/net/torvald/terrarum/modulebasegame/Ingame.kt b/src/net/torvald/terrarum/modulebasegame/Ingame.kt index 40afc4392..9f47a0e27 100644 --- a/src/net/torvald/terrarum/modulebasegame/Ingame.kt +++ b/src/net/torvald/terrarum/modulebasegame/Ingame.kt @@ -671,12 +671,11 @@ open class Ingame(batch: SpriteBatch) : IngameInstance(batch) { // set up indices for (i in 0..Terrarum.THREADS - 1) { ThreadParallel.map( - i, + i, "ActorUpdate", ThreadActorUpdate( actors.div(Terrarum.THREADS).times(i).roundInt(), - actors.div(Terrarum.THREADS).times(i.plus(1)).roundInt() - 1 - ), - "ActorUpdate" + actors.div(Terrarum.THREADS).times(i + 1).roundInt() - 1 + ) ) } diff --git a/src/net/torvald/terrarum/modulebasegame/worldgenerator/WorldGenerator.kt b/src/net/torvald/terrarum/modulebasegame/worldgenerator/WorldGenerator.kt index eea11114c..1b29992ce 100644 --- a/src/net/torvald/terrarum/modulebasegame/worldgenerator/WorldGenerator.kt +++ b/src/net/torvald/terrarum/modulebasegame/worldgenerator/WorldGenerator.kt @@ -781,13 +781,12 @@ object WorldGenerator { // set up indices for (i in 0 until Terrarum.THREADS) { ThreadParallel.map( - i, + i, "SampleJoiseMap", ThreadProcessNoiseLayers( HEIGHT.toFloat().div(Terrarum.THREADS).times(i).roundInt(), HEIGHT.toFloat().div(Terrarum.THREADS).times(i.plus(1)).roundInt() - 1, noiseRecords - ), - "SampleJoiseMap" + ) ) } diff --git a/src/net/torvald/terrarum/worlddrawer/LightmapRendererNew.kt b/src/net/torvald/terrarum/worlddrawer/LightmapRendererNew.kt index 98ab85190..1b827c47d 100644 --- a/src/net/torvald/terrarum/worlddrawer/LightmapRendererNew.kt +++ b/src/net/torvald/terrarum/worlddrawer/LightmapRendererNew.kt @@ -10,14 +10,17 @@ import net.torvald.terrarum.blockproperties.BlockCodex import com.jme3.math.FastMath import net.torvald.terrarum.AppLoader.printdbg import net.torvald.terrarum.Terrarum +import net.torvald.terrarum.blendNormal import net.torvald.terrarum.gameworld.GameWorld import net.torvald.terrarum.blockproperties.Block import net.torvald.terrarum.gameactors.* import net.torvald.terrarum.gameactors.ActorWBMovable import net.torvald.terrarum.ceilInt +import net.torvald.terrarum.concurrent.ThreadParallel import net.torvald.terrarum.floorInt import net.torvald.terrarum.modulebasegame.IngameRenderer -import java.util.* +import net.torvald.terrarum.concurrent.ParallelUtils.sliceEvenly +import kotlin.collections.ArrayList import kotlin.system.measureNanoTime /** @@ -148,6 +151,10 @@ object LightmapRenderer { // TODO in regard of "colour math against integers", take Int private fun setLight(x: Int, y: Int, colour: Color) { + setLightOf(lightmap, x, y, colour) + } + + private fun setLightOf(list: Array, x: Int, y: Int, colour: Color) { if (y - for_y_start + overscan_open in 0 until LIGHTMAP_HEIGHT && x - for_x_start + overscan_open in 0 until LIGHTMAP_WIDTH) { @@ -155,7 +162,7 @@ object LightmapRenderer { val xpos = x - for_x_start + overscan_open //lightmap[ypos][xpos] = colour - lightmap[ypos * LIGHTMAP_WIDTH + xpos] = colour + list[ypos * LIGHTMAP_WIDTH + xpos] = colour } } @@ -195,9 +202,9 @@ object LightmapRenderer { // Because of inevitable overlaps on the area, it only works with ADDITIVE blend (aka maxblend) - // each usually takes 1-3 miliseconds when not threaded + // each usually takes 8 000 000..12 000 000 miliseconds total when not threaded - if (!Terrarum.getConfigBoolean("multithread")) { + if (!Terrarum.getConfigBoolean("multithreadedlight")) { // Round 1 Terrarum.debugTimers["Renderer.Light1"] = measureNanoTime { for (y in for_y_start - overscan_open..for_y_end) { @@ -233,13 +240,103 @@ object LightmapRenderer { } } } + + Terrarum.debugTimers["Renderer.LightSequential"] = + Terrarum.debugTimers["Renderer.Light1"]!! + + Terrarum.debugTimers["Renderer.Light2"]!! + + Terrarum.debugTimers["Renderer.Light3"]!! + + Terrarum.debugTimers["Renderer.Light4"]!! } else { - TODO() - //val bufferForPasses = arrayOf(lightmap.) + Terrarum.debugTimers["Renderer.LightPre"] = measureNanoTime { + + val bufferForPasses = arrayOf( + lightmap.copyOf(), lightmap.copyOf(), lightmap.copyOf(), lightmap.copyOf() + ) + //val combiningBuffer = Array(lightmap.size) { Color(0) } + + // this is kind of inefficient... + val calcTask = ArrayList() + + // Round 1 preload + for (y in for_y_start - overscan_open..for_y_end) { + for (x in for_x_start - overscan_open..for_x_end) { + calcTask.add(ThreadedLightmapUpdateMessage(x, y, 1)) + } + } + + // Round 2 preload + for (y in for_y_end + overscan_open downTo for_y_start) { + for (x in for_x_start - overscan_open..for_x_end) { + calcTask.add(ThreadedLightmapUpdateMessage(x, y, 2)) + } + } + + // Round 3 preload + for (y in for_y_end + overscan_open downTo for_y_start) { + for (x in for_x_end + overscan_open downTo for_x_start) { + calcTask.add(ThreadedLightmapUpdateMessage(x, y, 3)) + } + } + + // Round 4 preload + for (y in for_y_start - overscan_open..for_y_end) { + for (x in for_x_end + overscan_open downTo for_x_start) { + calcTask.add(ThreadedLightmapUpdateMessage(x, y, 4)) + } + } + + val calcTasks = calcTask.sliceEvenly(Terrarum.THREADS) + val combineTasks = (0 until lightmap.size).sliceEvenly(Terrarum.THREADS) + + + // couldn't help but do this nested timer + + Terrarum.debugTimers["Renderer.LightParallel${Terrarum.THREADS}x"] = measureNanoTime { + calcTasks.forEachIndexed { index, list -> + ThreadParallel.map(index, "LightCalculate") { index -> // this index is that index + list.forEach { + val msg = it as ThreadedLightmapUpdateMessage + + setLightOf(bufferForPasses[it.pass - 1], it.x, it.y, calculate(it.x, it.y, it.pass)) + //setLightOf(bufferForPasses[it.pass - 1], it.x, it.y, calculate(it.x, it.y, 1)) + } + } + } + + ThreadParallel.startAllWaitForDie() + } + + Terrarum.debugTimers["Runderer.LightPost"] = measureNanoTime { + combineTasks.forEachIndexed { index, intRange -> + ThreadParallel.map(index, "LightCombine") { index -> // this index is that index + for (i in intRange) { + val max1 = bufferForPasses[0][i] maxBlend bufferForPasses[1][i] + val max2 = bufferForPasses[2][i] maxBlend bufferForPasses[3][i] + val max = max1 maxBlend max2 + + lightmap[i] = max + } + } + } + + ThreadParallel.startAllWaitForDie() + } + } + + + // get correct Renderer.LightPre by subtracting some shits + Terrarum.debugTimers["Renderer.LightParaTotal"] = Terrarum.debugTimers["Renderer.LightPre"]!! + Terrarum.debugTimers["Renderer.LightPre"] = + Terrarum.debugTimers["Renderer.LightPre"]!! - + Terrarum.debugTimers["Renderer.LightParallel${Terrarum.THREADS}x"]!! - + Terrarum.debugTimers["Runderer.LightPost"]!! + + // accuracy may suffer (overheads maybe?) but it doesn't matter (i think...) } } + internal data class ThreadedLightmapUpdateMessage(val x: Int, val y: Int, val pass: Int) private fun buildLanternmap() { @@ -282,9 +379,14 @@ object LightmapRenderer { private var thisTileOpacity = Color(0f,0f,0f,0f) private var sunLight = Color(0f,0f,0f,0f) - + /** + * @param pass one-based + */ private fun calculate(x: Int, y: Int, pass: Int): Color = calculate(x, y, pass, false) + /** + * @param pass one-based + */ private fun calculate(x: Int, y: Int, pass: Int, doNotCalculateAmbient: Boolean): Color { // O(9n) == O(n) where n is a size of the map // TODO devise multithreading on this @@ -392,9 +494,9 @@ object LightmapRenderer { val this_y_end = for_y_end// + overscan_open // wipe out beforehand. You DO need this - lightBuffer.blending = Pixmap.Blending.None // gonna overwrite + lightBuffer.blending = Pixmap.Blending.None // gonna overwrite (remove this line causes the world to go bit darker) lightBuffer.setColor(colourNull) - lightBuffer.fillRectangle(0, 0, lightBuffer.width, lightBuffer.height) + lightBuffer.fill() // write to colour buffer @@ -443,8 +545,6 @@ object LightmapRenderer { batch.draw(_lightBufferAsTex, 0f, 0f, _lightBufferAsTex.width * DRAW_TILE_SIZE, _lightBufferAsTex.height * DRAW_TILE_SIZE) - - } fun dispose() {