From 293033671862cbeea82c03b2190d6b216fbad2fe Mon Sep 17 00:00:00 2001 From: minjaesong Date: Wed, 21 Oct 2020 11:06:35 +0900 Subject: [PATCH] trying to fix the threadexecutor, at least it will throw error if a job failed --- .../terrarum/concurrent/ThreadParallel.kt | 47 ++++++++++++++----- .../modulebasegame/worldgenerator/Biomegen.kt | 14 ++---- .../modulebasegame/worldgenerator/Terragen.kt | 35 ++++++-------- .../modulebasegame/worldgenerator/Worldgen.kt | 7 ++- 4 files changed, 56 insertions(+), 47 deletions(-) diff --git a/src/net/torvald/terrarum/concurrent/ThreadParallel.kt b/src/net/torvald/terrarum/concurrent/ThreadParallel.kt index 42e980390..a93c571b4 100644 --- a/src/net/torvald/terrarum/concurrent/ThreadParallel.kt +++ b/src/net/torvald/terrarum/concurrent/ThreadParallel.kt @@ -1,9 +1,6 @@ package net.torvald.terrarum.concurrent -import java.util.concurrent.ExecutorService -import java.util.concurrent.Executors -import java.util.concurrent.Future -import java.util.concurrent.TimeUnit +import java.util.concurrent.* import kotlin.math.absoluteValue typealias RunnableFun = () -> Unit @@ -14,32 +11,58 @@ typealias ThreadableFun = (Int) -> Unit object ThreadExecutor { val threadCount = Runtime.getRuntime().availableProcessors() // not using (logicalCores + 1) method; it's often better idea to reserve one extra thread for other jobs in the app private lateinit var executor: ExecutorService// = Executors.newFixedThreadPool(threadCount) + val futures = ArrayList>() + private var isOpen = true private fun checkShutdown() { try { if (executor.isTerminated) throw IllegalStateException("Executor terminated, renew the executor service.") - if (executor.isShutdown) + if (!isOpen || executor.isShutdown) throw IllegalStateException("Pool is closed, come back when all the threads are terminated.") } catch (e: UninitializedPropertyAccessException) {} } fun renew() { + try { + if (!executor.isTerminated && !executor.isShutdown) throw IllegalStateException("Pool is still running") + } + catch (_: UninitializedPropertyAccessException) {} + executor = Executors.newFixedThreadPool(threadCount) + futures.clear() + isOpen = true } - fun submit(t: Runnable): Future<*> { + /*fun invokeAll(ts: List>) { checkShutdown() - return executor.submit(t) - } - fun submit(f: RunnableFun): Future<*> { - checkShutdown() - return executor.submit { f() } - } + executor.invokeAll(ts) + }*/ + fun submit(t: Callable) { + checkShutdown() + val fut = executor.submit(t) + futures.add(fut) + } + /*fun submit(f: RunnableFun) { + checkShutdown() + val fut = executor.submit { f() } + futures.add(fut) + }*/ + + // https://stackoverflow.com/questions/28818494/threads-stopping-prematurely-for-certain-values fun join() { println("ThreadExecutor.join") + isOpen = false + futures.forEach { + try { + it.get() + } + catch (e: ExecutionException) { + throw e + } + } executor.shutdown() // thread status of completed ones will be WAIT instead of TERMINATED without this line... executor.awaitTermination(24L, TimeUnit.HOURS) } diff --git a/src/net/torvald/terrarum/modulebasegame/worldgenerator/Biomegen.kt b/src/net/torvald/terrarum/modulebasegame/worldgenerator/Biomegen.kt index 9a9ae1d28..f9e4643fd 100644 --- a/src/net/torvald/terrarum/modulebasegame/worldgenerator/Biomegen.kt +++ b/src/net/torvald/terrarum/modulebasegame/worldgenerator/Biomegen.kt @@ -23,21 +23,13 @@ class Biomegen(world: GameWorld, seed: Long, params: Any) : Gen(world, seed, par private val genSlices = maxOf(ThreadExecutor.threadCount, world.width / 8) - private var genFutures: Array?> = arrayOfNulls(genSlices) - override var generationStarted: Boolean = false - override val generationDone: Boolean - get() = generationStarted && genFutures.fold(true) { acc, f -> acc and (f?.isDone ?: true) } - private val YHEIGHT_MAGIC = 2800.0 / 3.0 private val YHEIGHT_DIVISOR = 2.0 / 7.0 - override fun run() { - - generationStarted = true - + override fun getDone() { ThreadExecutor.renew() - (0 until world.width).sliceEvenly(genSlices).mapIndexed { i, xs -> - genFutures[i] = ThreadExecutor.submit { + (0 until world.width).sliceEvenly(genSlices).map { xs -> + ThreadExecutor.submit { val localJoise = getGenerator(seed, params as BiomegenParams) for (x in xs) { for (y in 0 until world.height) { diff --git a/src/net/torvald/terrarum/modulebasegame/worldgenerator/Terragen.kt b/src/net/torvald/terrarum/modulebasegame/worldgenerator/Terragen.kt index 676523b76..3c47cca21 100644 --- a/src/net/torvald/terrarum/modulebasegame/worldgenerator/Terragen.kt +++ b/src/net/torvald/terrarum/modulebasegame/worldgenerator/Terragen.kt @@ -20,33 +20,28 @@ class Terragen(world: GameWorld, seed: Long, params: Any) : Gen(world, seed, par private val genSlices = maxOf(ThreadExecutor.threadCount, world.width / 8) - private var genFutures: Array?> = arrayOfNulls(genSlices) - override var generationStarted: Boolean = false - override val generationDone: Boolean - get() = generationStarted && genFutures.fold(true) { acc, f -> acc and (f?.isDone ?: true) } - private val YHEIGHT_MAGIC = 2800.0 / 3.0 private val YHEIGHT_DIVISOR = 2.0 / 7.0 - override fun run() { - - generationStarted = true - + override fun getDone() { ThreadExecutor.renew() (0 until world.width).sliceEvenly(genSlices).mapIndexed { i, xs -> - genFutures[i] = ThreadExecutor.submit { + ThreadExecutor.submit { val localJoise = getGenerator(seed, params as TerragenParams) - for (x in xs) { - for (y in 0 until world.height) { - val sampleTheta = (x.toDouble() / world.width) * TWO_PI - val sampleOffset = world.width / 8.0 - 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 - (world.height - YHEIGHT_MAGIC) * YHEIGHT_DIVISOR // Q&D offsetting to make ratio of sky:ground to be constant - // DEBUG NOTE: it is the OFFSET FROM THE IDEAL VALUE (observed land height - (HEIGHT * DIVISOR)) that must be constant - val noise = localJoise.map { it.get(sampleX, sampleY, sampleZ) } + val localLock = java.lang.Object() // in an attempt to fix the "premature exit" issue of a thread run + synchronized(localLock) { // also see: https://stackoverflow.com/questions/28818494/threads-stopping-prematurely-for-certain-values + for (x in xs) { + for (y in 0 until world.height) { + val sampleTheta = (x.toDouble() / world.width) * TWO_PI + val sampleOffset = world.width / 8.0 + 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 - (world.height - YHEIGHT_MAGIC) * YHEIGHT_DIVISOR // Q&D offsetting to make ratio of sky:ground to be constant + // DEBUG NOTE: it is the OFFSET FROM THE IDEAL VALUE (observed land height - (HEIGHT * DIVISOR)) that must be constant + val noise = localJoise.map { it.get(sampleX, sampleY, sampleZ) } - draw(x, y, noise, world) + draw(x, y, noise, world) + } } } } diff --git a/src/net/torvald/terrarum/modulebasegame/worldgenerator/Worldgen.kt b/src/net/torvald/terrarum/modulebasegame/worldgenerator/Worldgen.kt index f3c2fb32a..1c756a28e 100644 --- a/src/net/torvald/terrarum/modulebasegame/worldgenerator/Worldgen.kt +++ b/src/net/torvald/terrarum/modulebasegame/worldgenerator/Worldgen.kt @@ -3,6 +3,7 @@ package net.torvald.terrarum.modulebasegame.worldgenerator import net.torvald.terrarum.AppLoader import net.torvald.terrarum.AppLoader.printdbg import net.torvald.terrarum.gameworld.GameWorld +import java.util.concurrent.Callable /** * New world generator. @@ -34,7 +35,7 @@ object Worldgen { val it = jobs[i] AppLoader.getLoadScreen().addMessage(it.loadingScreenName) - it.theWork.run() + it.theWork.getDone() } printdbg(this, "Generation job finished") @@ -46,9 +47,7 @@ object Worldgen { } abstract class Gen(val world: GameWorld, val seed: Long, val params: Any) { - abstract var generationStarted: Boolean - abstract val generationDone: Boolean - open fun run() { } + open fun getDone() { } // trying to use different name so that it won't be confused with Runnable or Callable } data class WorldgenParams(