diff --git a/src/net/torvald/terrarum/tests/WorldgenNoiseSandbox.kt b/src/net/torvald/terrarum/tests/WorldgenNoiseSandbox.kt index c35f418aa..85a3250d4 100644 --- a/src/net/torvald/terrarum/tests/WorldgenNoiseSandbox.kt +++ b/src/net/torvald/terrarum/tests/WorldgenNoiseSandbox.kt @@ -19,12 +19,11 @@ import com.sudoplay.joise.ModuleMap import com.sudoplay.joise.ModulePropertyMap import com.sudoplay.joise.module.* import net.torvald.random.HQRNG -import net.torvald.terrarum.FlippingSpriteBatch +import net.torvald.terrarum.* import net.torvald.terrarum.blockproperties.Block import net.torvald.terrarum.concurrent.RunnableFun import net.torvald.terrarum.concurrent.ThreadExecutor import net.torvald.terrarum.concurrent.sliceEvenly -import net.torvald.terrarum.inUse import net.torvald.terrarum.modulebasegame.worldgenerator.* import net.torvald.terrarum.worlddrawer.toRGBA import net.torvald.terrarumsansbitmap.gdx.TerrarumSansBitmap @@ -32,7 +31,7 @@ import java.util.concurrent.Future import kotlin.math.* import kotlin.random.Random import net.torvald.terrarum.modulebasegame.worldgenerator.Terragen -import net.torvald.terrarum.sqr +import java.io.PrintStream const val NOISEBOX_WIDTH = 1200 const val NOISEBOX_HEIGHT = 2400 @@ -56,9 +55,7 @@ class WorldgenNoiseSandbox : ApplicationAdapter() { private var seed = 10000L private var initialGenDone = false - private var generateKeyLatched = false - private var generationTimeInMeasure = false private var generationStartTime = 0L private var genSlices: Int = 0 private var genFutures: Array?> = arrayOfNulls(genSlices) @@ -92,6 +89,7 @@ class WorldgenNoiseSandbox : ApplicationAdapter() { if (!initialGenDone) { renderNoise(NOISEMAKER) + initialGenDone = true } // draw using pixmap @@ -102,28 +100,15 @@ class WorldgenNoiseSandbox : ApplicationAdapter() { } // read key input - if (!generateKeyLatched && Gdx.input.isKeyPressed(Input.Keys.SPACE)) { - generateKeyLatched = true + if (Gdx.input.isKeyPressed(Input.Keys.SPACE) && worldgenDone) { seed = RNG.nextLong() renderNoise(NOISEMAKER) } - val coroutineExecFinished = genFutures.fold(true) { acc, it -> acc and (it?.isDone ?: true) } - // check if generation is done - if (coroutineExecFinished) { - generateKeyLatched = false - } - - // finish time measurement - if (coroutineExecFinished && generationTimeInMeasure) { - generationTimeInMeasure = false - val time = System.nanoTime() - generationStartTime - generationTime = time / 1000000000f - } // draw timer batch.inUse { - if (!generationTimeInMeasure) { + if (worldgenDone) { font.draw(batch, "Generation time: ${generationTime} seconds", 8f, 8f) } else { @@ -168,10 +153,18 @@ class WorldgenNoiseSandbox : ApplicationAdapter() { } } + private fun printStackTrace(obj: Any, out: PrintStream = System.out) { + val indentation = " ".repeat(obj.javaClass.simpleName.length + 4) + Thread.currentThread().stackTrace.forEachIndexed { index, it -> + if (index == 1) + out.println("[${obj.javaClass.simpleName}]> $it") + else if (index > 1) + out.println("$indentation$it") + } + } - private fun renderNoise(noiseMaker: Pair) { + private fun renderNoise(noiseMaker: Pair, callback: () -> Unit = {}) { generationStartTime = System.nanoTime() - generationTimeInMeasure = true // erase first testTex.setColor(colourNull) @@ -180,83 +173,55 @@ class WorldgenNoiseSandbox : ApplicationAdapter() { testColSet.shuffle() testColSet2.shuffle() - // render noisemap to pixmap + worldgenDone = false - /* - I've got two ideas to resolve noisy artefact when noise generation runs concurrently: + Thread { + val runnables: List = (0 until testTex.width).sliceEvenly(genSlices).map { range -> + { + val localJoise = noiseMaker.first.getGenerator(seed, noiseMaker.second) + for (x in range) { + for (y in 0 until NOISEBOX_HEIGHT) { + val sampleTheta = (x.toDouble() / NOISEBOX_WIDTH) * TWO_PI + val sampleX = + sin(sampleTheta) * sampleOffset + sampleOffset // plus sampleOffset to make only + val sampleZ = + cos(sampleTheta) * sampleOffset + sampleOffset // positive points are to be sampled + val sampleY = + y - (NOISEBOX_HEIGHT - Terragen.YHEIGHT_MAGIC) * Terragen.YHEIGHT_DIVISOR // Q&D offsetting to make ratio of sky:ground to be constant - 1) 1 block = 1 coroutine - 2) 1 thread has its own copy of Joise (threads have different INSTANCEs of Joise with same params) - - Method 1) seemingly works but may break if the operation is more complex - Method 2) also works - - --CuriousTorvald, 2020-04-29 - */ - - // 0. naive concurrent approach - // CULPRIT: one global instance of Joise that all the threads try to access (and modify local variables) at the same time - /*val runnables: List = xSlices.map { range -> - { - for (x in range) { - for (y in 0 until HEIGHT) { - val sampleTheta = (x.toDouble() / WIDTH) * TWO_PI - val sampleX = sin(sampleTheta) * sampleOffset + sampleOffset // plus sampleOffset to make only - val sampleZ = cos(sampleTheta) * sampleOffset + sampleOffset // positive points are to be sampled - val sampleY = y.toDouble() - - NOISE_MAKER.draw(x, y, joise.map { it.get(sampleX, sampleY, sampleZ) }, testTex) + noiseMaker.first.draw(x, y, localJoise.mapIndexed { index, it -> + it.get(sampleX, sampleY, sampleZ) + }, testTex) + } } } } - }*/ - // 1. stupid one-block-is-one-coroutine approach (seemingly works?) - /*val joise = getNoiseGenerator(seed) - val runnables: List = runs.map { i -> { - val (x, y) = (i % WIDTH) to (i / WIDTH) - val sampleTheta = (x.toDouble() / WIDTH) * TWO_PI - val sampleX = sin(sampleTheta) * sampleOffset + sampleOffset // plus sampleOffset to make only - val sampleZ = cos(sampleTheta) * sampleOffset + sampleOffset // positive points are to be sampled - val sampleY = y.toDouble() + threadExecutor.renew() + runnables.forEach { + threadExecutor.submit(it) + } - NOISE_MAKER.draw(x, y, joise.map { it.get(sampleX, sampleY, sampleZ) }, testTex) - } }*/ + threadExecutor.join() - // 2. each runner gets their own copy of Joise - val runnables: List = (0 until testTex.width).sliceEvenly(genSlices).map { range -> { - val localJoise = noiseMaker.first.getGenerator(seed, noiseMaker.second) - for (x in range) { - for (y in 0 until NOISEBOX_HEIGHT) { - val sampleTheta = (x.toDouble() / NOISEBOX_WIDTH) * TWO_PI - val sampleX = sin(sampleTheta) * sampleOffset + sampleOffset // plus sampleOffset to make only - val sampleZ = cos(sampleTheta) * sampleOffset + sampleOffset // positive points are to be sampled - val sampleY = y - (NOISEBOX_HEIGHT - Terragen.YHEIGHT_MAGIC) * Terragen.YHEIGHT_DIVISOR // Q&D offsetting to make ratio of sky:ground to be constant + worldgenDone = true - noiseMaker.first.draw(x, y, localJoise.mapIndexed { index, it -> - it.get(sampleX, sampleY, sampleZ) - }, testTex) - } - } - } } + val time = System.nanoTime() - generationStartTime + generationTime = time / 1000000000f + callback() + }.start() - - threadExecutor.renew() - runnables.forEach { - threadExecutor.submit(it) - } - - threadExecutor.join() - - initialGenDone = true } + var worldgenDone = true; private set + override fun dispose() { testTex.dispose() tempTex.dispose() } } + fun main(args: Array) { ShaderProgram.pedantic = false @@ -270,7 +235,7 @@ fun main(args: Array) { Lwjgl3Application(WorldgenNoiseSandbox(), appConfig) } -internal interface NoiseMaker { +interface NoiseMaker { fun draw(x: Int, y: Int, noiseValue: List, outTex: Pixmap) fun getGenerator(seed: Long, params: Any): List }