worldgensandbox: reviving old feature of showing real progress

This commit is contained in:
minjaesong
2023-11-15 14:14:35 +09:00
parent 4dae2fcc66
commit 996359c9c7

View File

@@ -19,12 +19,11 @@ import com.sudoplay.joise.ModuleMap
import com.sudoplay.joise.ModulePropertyMap import com.sudoplay.joise.ModulePropertyMap
import com.sudoplay.joise.module.* import com.sudoplay.joise.module.*
import net.torvald.random.HQRNG import net.torvald.random.HQRNG
import net.torvald.terrarum.FlippingSpriteBatch import net.torvald.terrarum.*
import net.torvald.terrarum.blockproperties.Block import net.torvald.terrarum.blockproperties.Block
import net.torvald.terrarum.concurrent.RunnableFun import net.torvald.terrarum.concurrent.RunnableFun
import net.torvald.terrarum.concurrent.ThreadExecutor import net.torvald.terrarum.concurrent.ThreadExecutor
import net.torvald.terrarum.concurrent.sliceEvenly import net.torvald.terrarum.concurrent.sliceEvenly
import net.torvald.terrarum.inUse
import net.torvald.terrarum.modulebasegame.worldgenerator.* import net.torvald.terrarum.modulebasegame.worldgenerator.*
import net.torvald.terrarum.worlddrawer.toRGBA import net.torvald.terrarum.worlddrawer.toRGBA
import net.torvald.terrarumsansbitmap.gdx.TerrarumSansBitmap import net.torvald.terrarumsansbitmap.gdx.TerrarumSansBitmap
@@ -32,7 +31,7 @@ import java.util.concurrent.Future
import kotlin.math.* import kotlin.math.*
import kotlin.random.Random import kotlin.random.Random
import net.torvald.terrarum.modulebasegame.worldgenerator.Terragen import net.torvald.terrarum.modulebasegame.worldgenerator.Terragen
import net.torvald.terrarum.sqr import java.io.PrintStream
const val NOISEBOX_WIDTH = 1200 const val NOISEBOX_WIDTH = 1200
const val NOISEBOX_HEIGHT = 2400 const val NOISEBOX_HEIGHT = 2400
@@ -56,9 +55,7 @@ class WorldgenNoiseSandbox : ApplicationAdapter() {
private var seed = 10000L private var seed = 10000L
private var initialGenDone = false private var initialGenDone = false
private var generateKeyLatched = false
private var generationTimeInMeasure = false
private var generationStartTime = 0L private var generationStartTime = 0L
private var genSlices: Int = 0 private var genSlices: Int = 0
private var genFutures: Array<Future<*>?> = arrayOfNulls(genSlices) private var genFutures: Array<Future<*>?> = arrayOfNulls(genSlices)
@@ -92,6 +89,7 @@ class WorldgenNoiseSandbox : ApplicationAdapter() {
if (!initialGenDone) { if (!initialGenDone) {
renderNoise(NOISEMAKER) renderNoise(NOISEMAKER)
initialGenDone = true
} }
// draw using pixmap // draw using pixmap
@@ -102,28 +100,15 @@ class WorldgenNoiseSandbox : ApplicationAdapter() {
} }
// read key input // read key input
if (!generateKeyLatched && Gdx.input.isKeyPressed(Input.Keys.SPACE)) { if (Gdx.input.isKeyPressed(Input.Keys.SPACE) && worldgenDone) {
generateKeyLatched = true
seed = RNG.nextLong() seed = RNG.nextLong()
renderNoise(NOISEMAKER) 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 // draw timer
batch.inUse { batch.inUse {
if (!generationTimeInMeasure) { if (worldgenDone) {
font.draw(batch, "Generation time: ${generationTime} seconds", 8f, 8f) font.draw(batch, "Generation time: ${generationTime} seconds", 8f, 8f)
} }
else { 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<NoiseMaker, Any>) { private fun renderNoise(noiseMaker: Pair<NoiseMaker, Any>, callback: () -> Unit = {}) {
generationStartTime = System.nanoTime() generationStartTime = System.nanoTime()
generationTimeInMeasure = true
// erase first // erase first
testTex.setColor(colourNull) testTex.setColor(colourNull)
@@ -180,83 +173,55 @@ class WorldgenNoiseSandbox : ApplicationAdapter() {
testColSet.shuffle() testColSet.shuffle()
testColSet2.shuffle() testColSet2.shuffle()
// render noisemap to pixmap worldgenDone = false
/* Thread {
I've got two ideas to resolve noisy artefact when noise generation runs concurrently: val runnables: List<RunnableFun> = (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 noiseMaker.first.draw(x, y, localJoise.mapIndexed { index, it ->
2) 1 thread has its own copy of Joise (threads have different INSTANCEs of Joise with same params) it.get(sampleX, sampleY, sampleZ)
}, testTex)
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<RunnableFun> = 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)
} }
} }
} }
}*/
// 1. stupid one-block-is-one-coroutine approach (seemingly works?) threadExecutor.renew()
/*val joise = getNoiseGenerator(seed) runnables.forEach {
val runnables: List<RunnableFun> = runs.map { i -> { threadExecutor.submit(it)
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()
NOISE_MAKER.draw(x, y, joise.map { it.get(sampleX, sampleY, sampleZ) }, testTex) threadExecutor.join()
} }*/
// 2. each runner gets their own copy of Joise worldgenDone = true
val runnables: List<RunnableFun> = (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
noiseMaker.first.draw(x, y, localJoise.mapIndexed { index, it -> val time = System.nanoTime() - generationStartTime
it.get(sampleX, sampleY, sampleZ) generationTime = time / 1000000000f
}, testTex) callback()
} }.start()
}
} }
threadExecutor.renew()
runnables.forEach {
threadExecutor.submit(it)
}
threadExecutor.join()
initialGenDone = true
} }
var worldgenDone = true; private set
override fun dispose() { override fun dispose() {
testTex.dispose() testTex.dispose()
tempTex.dispose() tempTex.dispose()
} }
} }
fun main(args: Array<String>) { fun main(args: Array<String>) {
ShaderProgram.pedantic = false ShaderProgram.pedantic = false
@@ -270,7 +235,7 @@ fun main(args: Array<String>) {
Lwjgl3Application(WorldgenNoiseSandbox(), appConfig) Lwjgl3Application(WorldgenNoiseSandbox(), appConfig)
} }
internal interface NoiseMaker { interface NoiseMaker {
fun draw(x: Int, y: Int, noiseValue: List<Double>, outTex: Pixmap) fun draw(x: Int, y: Int, noiseValue: List<Double>, outTex: Pixmap)
fun getGenerator(seed: Long, params: Any): List<Joise> fun getGenerator(seed: Long, params: Any): List<Joise>
} }