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() {