it turns out the test-entering is broken; will fix later idk; Threadparallel.startAllWaitForDie seems to work as intended

This commit is contained in:
minjaesong
2019-06-21 16:05:02 +09:00
parent 306f45e7ee
commit b45caebda0
14 changed files with 310 additions and 119 deletions

Binary file not shown.

View File

@@ -1,16 +1,26 @@
module terrarum { module terrarum {
// java
requires java.desktop;
requires java.logging;
requires jdk.unsupported; // sun.misc.Unsafe
// kotlin
requires kotlin.stdlib;
// gdx
requires gdx; requires gdx;
requires gdx.backend.lwjgl; requires gdx.backend.lwjgl;
requires gdx.controllers; requires gdx.controllers;
// terrarum
requires TerrarumSansBitmap;
requires TerranVirtualDisk;
requires Terrarum.Joise;
// etc
requires GetCpuName;
requires jxinput; requires jxinput;
requires gson; requires gson;
requires GetCpuName;
requires TerrarumSansBitmap;
requires kotlin.stdlib;
requires java.desktop;
requires java.logging;
requires TerranVirtualDisk;
requires commons.codec; requires commons.codec;
requires commons.csv; requires commons.csv;
requires Terrarum.Joise;
} }

View File

@@ -125,10 +125,10 @@ public class AppLoader implements ApplicationListener {
public static String GAME_LOCALE = System.getProperty("user.language") + System.getProperty("user.country"); public static String GAME_LOCALE = System.getProperty("user.language") + System.getProperty("user.country");
public static final String systemArch = System.getProperty("os.arch"); public static final String systemArch = System.getProperty("os.arch");
public static String processor; public static String processor = "(a super-duper virtual processor)";
public static String processorVendor; public static String processorVendor = "(andromeda software development)"; // definitely not taken from "that" demogroup
public static String renderer; public static String renderer = "(a super-fancy virtual photoradiator)";
public static String rendererVendor; public static String rendererVendor = "(radiosity)";
public static final boolean is32BitJVM = !System.getProperty("sun.arch.data.model").contains("64"); public static final boolean is32BitJVM = !System.getProperty("sun.arch.data.model").contains("64");
@@ -610,15 +610,14 @@ public class AppLoader implements ApplicationListener {
if (injectScreen != null) { if (injectScreen != null) {
setScreen(injectScreen); setScreen(injectScreen);
} }
else {
ModMgr.INSTANCE.invoke(); // invoke Module Manager
AppLoader.resourcePool.loadAll();
printdbg(this, "all modules loaded successfully");
BlocksDrawer.INSTANCE.getWorld(); // will initialize the BlocksDrawer by calling dummy method
ModMgr.INSTANCE.invoke(); // invoke Module Manager LightmapRenderer.INSTANCE.hdr(0f);
AppLoader.resourcePool.loadAll(); }
printdbg(this, "all modules loaded successfully");
BlocksDrawer.INSTANCE.getWorld(); // will initialize the BlocksDrawer by calling dummy method
LightmapRenderer.INSTANCE.hdr(0f);
printdbg(this, "PostInit done"); printdbg(this, "PostInit done");

View File

@@ -15,6 +15,7 @@ import com.badlogic.gdx.utils.GdxRuntimeException
import com.jme3.math.FastMath import com.jme3.math.FastMath
import net.torvald.random.HQRNG import net.torvald.random.HQRNG
import net.torvald.terrarum.AppLoader.* import net.torvald.terrarum.AppLoader.*
import net.torvald.terrarum.concurrent.ThreadParallel
import net.torvald.terrarum.gameactors.Actor import net.torvald.terrarum.gameactors.Actor
import net.torvald.terrarum.gameactors.ActorID import net.torvald.terrarum.gameactors.ActorID
import net.torvald.terrarum.imagefont.TinyAlphNum import net.torvald.terrarum.imagefont.TinyAlphNum
@@ -166,7 +167,7 @@ object Terrarum : Screen, Disposable {
val STATE_ID_TOOL_RUMBLE_DIAGNOSIS = 0x201 val STATE_ID_TOOL_RUMBLE_DIAGNOSIS = 0x201
/** Available CPU threads */ /** Available CPU threads */
val THREADS = Runtime.getRuntime().availableProcessors() + 1 val THREADS = ThreadParallel.threadCount //Runtime.getRuntime().availableProcessors() + 1
/** /**
* If the game is multithreading. * If the game is multithreading.
@@ -196,19 +197,22 @@ object Terrarum : Screen, Disposable {
init { init {
println("$NAME version ${AppLoader.getVERSION_STRING()}") println("[Terrarum] init called by:")
println("LibGDX version ${com.badlogic.gdx.Version.VERSION}") Thread.currentThread().stackTrace.forEach { println("... $it") }
println("[Terrarum] $NAME version ${AppLoader.getVERSION_STRING()}")
println("[Terrarum] LibGDX version ${com.badlogic.gdx.Version.VERSION}")
println("os.arch = $systemArch") // debug info println("[Terrarum] os.arch = $systemArch") // debug info
if (is32BitJVM) { if (is32BitJVM) {
printdbgerr(this, "32 Bit JVM detected") printdbgerr(this, "32 Bit JVM detected")
} }
println("processor = $processor") println("[Terrarum] processor = $processor")
println("vendor = $processorVendor") println("[Terrarum] vendor = $processorVendor")
setGamepadButtonLabels() setGamepadButtonLabels()
@@ -216,6 +220,9 @@ object Terrarum : Screen, Disposable {
AppLoader.disposableSingletonsPool.add(this) AppLoader.disposableSingletonsPool.add(this)
println("[Terrarum] init complete")
} }
private fun setGamepadButtonLabels() { private fun setGamepadButtonLabels() {

View File

@@ -93,7 +93,7 @@ object BlockCodex {
} }
} }
fun getOrNull(rawIndex: Int?): BlockProp? { fun getOrNull(rawIndex: Int?): BlockProp? {//<O>
return blockProps[rawIndex] return blockProps[rawIndex]
} }

View File

@@ -1,6 +1,5 @@
package net.torvald.terrarum.concurrent package net.torvald.terrarum.concurrent
import net.torvald.terrarum.Terrarum
import kotlin.math.absoluteValue import kotlin.math.absoluteValue
typealias RunnableFun = () -> Unit typealias RunnableFun = () -> Unit
@@ -11,7 +10,7 @@ typealias ThreadableFun = (Int) -> Unit
* Created by minjaesong on 2016-05-25. * Created by minjaesong on 2016-05-25.
*/ */
object ThreadParallel { object ThreadParallel {
val threadCount = Terrarum.THREADS // modify this to your taste val threadCount = Runtime.getRuntime().availableProcessors() + 1 // modify this to your taste
private val pool: Array<Thread?> = Array(threadCount) { null } private val pool: Array<Thread?> = Array(threadCount) { null }
@@ -49,6 +48,7 @@ object ThreadParallel {
* Start all thread in the pool and wait for them to all die. If the thread in the pool is NULL, it will simply ignored. * Start all thread in the pool and wait for them to all die. If the thread in the pool is NULL, it will simply ignored.
*/ */
fun startAllWaitForDie() { fun startAllWaitForDie() {
// ThreadParallelTester says this function actually works as intended...
pool.forEach { it?.start() } pool.forEach { it?.start() }
pool.forEach { it?.join() } pool.forEach { it?.join() }
} }
@@ -69,7 +69,7 @@ object ThreadParallel {
*/ */
@Deprecated("Experimental.", ReplaceWith("ThreadParallel", "net.torvald.terrarum.concurrent.ThreadParallel")) @Deprecated("Experimental.", ReplaceWith("ThreadParallel", "net.torvald.terrarum.concurrent.ThreadParallel"))
object BlockingThreadPool { object BlockingThreadPool {
val threadCount = Terrarum.THREADS // modify this to your taste val threadCount = Runtime.getRuntime().availableProcessors() + 1 // modify this to your taste
private val pool: Array<Thread?> = Array(threadCount, { null }) private val pool: Array<Thread?> = Array(threadCount, { null })
private var tasks: List<RunnableFun> = ArrayList<RunnableFun>() private var tasks: List<RunnableFun> = ArrayList<RunnableFun>()
@Volatile private var dispatchedTasks = 0 @Volatile private var dispatchedTasks = 0
@@ -127,85 +127,84 @@ object BlockingThreadPool {
} }
} }
object ParallelUtils {
fun <T, R> Iterable<T>.parallelMap(transform: (T) -> R): List<R> {
val tasks = this.sliceEvenly(ThreadParallel.threadCount)
val destination = Array(ThreadParallel.threadCount) { ArrayList<R>() }
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() fun <T, R> Iterable<T>.parallelMap(transform: (T) -> R): List<R> {
val tasks = this.sliceEvenly(ThreadParallel.threadCount)
return destination.flatten() val destination = Array(ThreadParallel.threadCount) { ArrayList<R>() }
} tasks.forEachIndexed { index, list ->
ThreadParallel.map(index, "ParallelUtils.parallelMap@${this.javaClass.canonicalName}") {
/** for (item in list)
* Shallow flat of the array destination[index].add(transform(item as T))
*/
fun <T> Array<out Iterable<T>>.flatten(): List<T> {
val al = ArrayList<T>()
this.forEach { it.forEach { al.add(it) } }
return al
}
/**
* Shallow flat of the iterable
*/
fun <T> Iterable<out Iterable<T>>.flatten(): List<T> {
val al = ArrayList<T>()
this.forEach { it.forEach { al.add(it) } }
return al
}
/**
* Shallow flat of the array
*/
fun <T> Array<out Array<T>>.flatten(): List<T> {
val al = ArrayList<T>()
this.forEach { it.forEach { al.add(it) } }
return al
}
fun <T> Iterable<T>.sliceEvenly(slices: Int): List<List<T>> = this.toList().sliceEvenly(slices)
fun <T> List<T>.sliceEvenly(slices: Int): List<List<T>> {
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 <T> Array<T>.sliceEvenly(slices: Int): List<Array<T>> { ThreadParallel.startAllWaitForDie()
return (0 until slices).map {
this.sliceArray( return destination.flatten()
this.size.toFloat().div(slices).times(it).roundInt() until }
this.size.toFloat().div(slices).times(it + 1).roundInt()
) /**
} * Shallow flat of the array
*/
fun <T> Array<out Iterable<T>>.flatten(): List<T> {
val al = ArrayList<T>()
this.forEach { it.forEach { al.add(it) } }
return al
}
/**
* Shallow flat of the iterable
*/
fun <T> Iterable<out Iterable<T>>.flatten(): List<T> {
val al = ArrayList<T>()
this.forEach { it.forEach { al.add(it) } }
return al
}
/**
* Shallow flat of the array
*/
fun <T> Array<out Array<T>>.flatten(): List<T> {
val al = ArrayList<T>()
this.forEach { it.forEach { al.add(it) } }
return al
}
fun <T> Iterable<T>.sliceEvenly(slices: Int): List<List<T>> = this.toList().sliceEvenly(slices)
fun <T> List<T>.sliceEvenly(slices: Int): List<List<T>> {
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 IntProgression.sliceEvenly(slices: Int): List<IntProgression> { fun <T> Array<T>.sliceEvenly(slices: Int): List<Array<T>> {
if (this.step.absoluteValue != 1) throw UnsupportedOperationException("Sorry, step != +1/-1") return (0 until slices).map {
val size = (this.last - this.first).absoluteValue + (this.step.toFloat()).absoluteValue this.sliceArray(
this.size.toFloat().div(slices).times(it).roundInt() until
// println(size) this.size.toFloat().div(slices).times(it + 1).roundInt()
)
return if (this.first < this.last) (0 until slices).map {
this.first + size.div(slices).times(it).roundInt() ..
this.first + size.div(slices).times(it + 1).roundInt() - 1
}
else (0 until slices).map {
this.first - size.div(slices).times(it).roundInt() downTo
this.first - size.div(slices).times(it + 1).roundInt() + 1
}
} }
}
fun IntProgression.sliceEvenly(slices: Int): List<IntProgression> {
if (this.step.absoluteValue != 1) throw UnsupportedOperationException("Sorry, step != +1/-1")
val size = (this.last - this.first).absoluteValue + (this.step.toFloat()).absoluteValue
// println(size)
return if (this.first < this.last) (0 until slices).map {
this.first + size.div(slices).times(it).roundInt() ..
this.first + size.div(slices).times(it + 1).roundInt() - 1
}
else (0 until slices).map {
this.first - size.div(slices).times(it).roundInt() downTo
this.first - size.div(slices).times(it + 1).roundInt() + 1
}
}
private inline fun Float.roundInt(): Int = Math.round(this) private inline fun Float.roundInt(): Int = Math.round(this)
}

View File

@@ -464,6 +464,7 @@ open class GameWorld : Disposable {
override fun dispose() { override fun dispose() {
layerWall.dispose() layerWall.dispose()
layerTerrain.dispose() layerTerrain.dispose()
//nullWorldInstance?.dispose() // must be called ONLY ONCE; preferably when the app exits
} }
companion object { companion object {

View File

@@ -86,7 +86,7 @@ class EntryPoint : ModuleEntryPoint() {
println("Welcome back!") println("[Basegame.EntryPoint] Welcome back!")
} }
override fun dispose() { override fun dispose() {

View File

@@ -52,6 +52,7 @@ object WorldSimulator {
private val ingame = Terrarum.ingame!! private val ingame = Terrarum.ingame!!
private val world = ingame.world private val world = ingame.world
// TODO use R-Tree instead? https://stackoverflow.com/questions/10269179/find-rectangles-that-contain-point-efficient-algorithm#10269695
private var actorsKDTree: KDTree? = null private var actorsKDTree: KDTree? = null
fun resetForThisFrame() { fun resetForThisFrame() {

View File

@@ -20,7 +20,7 @@ import net.torvald.random.HQRNG
import net.torvald.terrarum.AppLoader import net.torvald.terrarum.AppLoader
import net.torvald.terrarum.Terrarum import net.torvald.terrarum.Terrarum
import net.torvald.terrarum.concurrent.BlockingThreadPool import net.torvald.terrarum.concurrent.BlockingThreadPool
import net.torvald.terrarum.concurrent.ParallelUtils.sliceEvenly import net.torvald.terrarum.concurrent.sliceEvenly
import net.torvald.terrarum.inUse import net.torvald.terrarum.inUse
import net.torvald.terrarum.modulebasegame.Ingame import net.torvald.terrarum.modulebasegame.Ingame
import net.torvald.terrarum.roundInt import net.torvald.terrarum.roundInt

View File

@@ -0,0 +1,164 @@
package net.torvald.terrarum.tests
import com.badlogic.gdx.ApplicationAdapter
import com.badlogic.gdx.Gdx
import com.badlogic.gdx.Input
import com.badlogic.gdx.InputAdapter
import com.badlogic.gdx.backends.lwjgl.LwjglApplication
import com.badlogic.gdx.backends.lwjgl.LwjglApplicationConfiguration
import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.graphics.OrthographicCamera
import com.badlogic.gdx.graphics.Pixmap
import com.badlogic.gdx.graphics.Texture
import com.badlogic.gdx.graphics.g2d.SpriteBatch
import com.badlogic.gdx.graphics.glutils.ShaderProgram
import com.sudoplay.joise.Joise
import com.sudoplay.joise.module.ModuleBasisFunction
import com.sudoplay.joise.module.ModuleFractal
import com.sudoplay.joise.module.ModuleScaleOffset
import net.torvald.random.HQRNG
import net.torvald.terrarum.concurrent.ThreadParallel
import net.torvald.terrarum.concurrent.ThreadableFun
import net.torvald.terrarum.concurrent.sliceEvenly
import net.torvald.terrarum.inUse
import net.torvald.terrarum.roundInt
import kotlin.math.absoluteValue
import kotlin.system.measureNanoTime
/**
* Created by minjaesong on 2019-06-17.
*/
class ThreadParallelTester : ApplicationAdapter() {
lateinit var batch: SpriteBatch
lateinit var camera: OrthographicCamera
lateinit var pixmap: Pixmap
lateinit var texture: Texture
private val IMAGE_SIZE = 1024
private val IMAGE_SIZEF = IMAGE_SIZE.toFloat()
private val IMAGE_SIZED = IMAGE_SIZE.toDouble()
private val RNG = HQRNG()
override fun create() {
Gdx.input.inputProcessor = ThreadParallelTesterController(this)
batch = SpriteBatch()
camera = OrthographicCamera(IMAGE_SIZEF, IMAGE_SIZEF)
camera.setToOrtho(true, IMAGE_SIZEF, IMAGE_SIZEF)
camera.update()
Gdx.gl20.glViewport(0, 0, IMAGE_SIZE, IMAGE_SIZE)
pixmap = Pixmap(IMAGE_SIZE, IMAGE_SIZE, Pixmap.Format.RGBA8888)
texture = Texture(1, 1, Pixmap.Format.RGBA8888)
batch.projectionMatrix = camera.combined
println("[ThreadParallelTester] Hello, world!")
}
var regenerate = true
var generateDone = true
override fun render() {
Gdx.graphics.setTitle("F: ${Gdx.graphics.framesPerSecond}")
if (regenerate) {
// fill pixmap with slow-to-calculate noises.
// create parallel job
// run parallel job, wait all of them to die
// render the pixmap
// expected result: app freezes (or nothing is drawn) until all the parallel job is done
println("Noise regenerating...")
regenerate = false
generateDone = false
val time = measureNanoTime {
setupParallelJobs()
//ThreadParallel.startAll()
ThreadParallel.startAllWaitForDie()
}
generateDone = true
println("Noise generation complete, took ${time.toDouble() / 1_000_000} ms\n")
}
// render
texture.dispose()
texture = Texture(pixmap)
batch.inUse {
batch.projectionMatrix = camera.combined
batch.color = Color.WHITE
batch.draw(texture, 0f, 0f)
}
}
private fun setupParallelJobs() {
val seed = RNG.nextLong()
for (i in 0 until ThreadParallel.threadCount) {
ThreadParallel.map(i, "NoiseGen", makeGenFun(seed, i))
}
}
private val scanlineNumbers: List<IntProgression> = (0 until IMAGE_SIZE).sliceEvenly(ThreadParallel.threadCount)
private fun makeGenFun(seed: Long, index: Int): ThreadableFun = { i ->
val module = ModuleFractal()
module.setType(ModuleFractal.FractalType.BILLOW)
module.setAllSourceBasisTypes(ModuleBasisFunction.BasisType.GRADIENT)
module.setAllSourceInterpolationTypes(ModuleBasisFunction.InterpolationType.QUINTIC)
module.setNumOctaves(10)
module.setFrequency(8.0)
module.seed = seed
val moduleScale = ModuleScaleOffset()
moduleScale.setSource(module)
moduleScale.setScale(0.5)
moduleScale.setOffset(0.0)
val noiseModule = Joise(moduleScale)
for (y in scanlineNumbers[index]) {
for (x in 0 until IMAGE_SIZE) {
val uvX = x / IMAGE_SIZED
val uvY = y / IMAGE_SIZED
val noiseValue = noiseModule.get(uvX, uvY).absoluteValue
val rgb = (noiseValue * 255.0).roundInt()
pixmap.drawPixel(x, y, (rgb shl 24) or (rgb shl 16) or (rgb shl 8) or 0xFF)
}
}
}
override fun dispose() {
pixmap.dispose()
texture.dispose()
}
}
class ThreadParallelTesterController(val host: ThreadParallelTester) : InputAdapter() {
override fun keyDown(keycode: Int): Boolean {
if (keycode == Input.Keys.SPACE && host.generateDone) {
host.regenerate = true
}
return true
}
}
fun main(args: Array<String>) {
ShaderProgram.pedantic = false
val appConfig = LwjglApplicationConfiguration()
appConfig.vSyncEnabled = false
appConfig.resizable = false
appConfig.width = 1024
appConfig.height = 1024
appConfig.backgroundFPS = 9999
appConfig.foregroundFPS = 9999
appConfig.forceExit = true
//LwjglApplication(AppLoader(appConfig, ThreadParallelTester()), appConfig)
LwjglApplication(ThreadParallelTester(), appConfig)
}

View File

@@ -20,33 +20,33 @@ class UITestPad1 : ScreenAdapter() {
val treeStr = """ val treeStr = """
- File - File
- New : Ctrl-N - New
- Open : Ctrl-O - Open
- Open Recent - Open Recent
- yaml_example.yaml - yaml_example.yaml
- Yaml.kt - Yaml.kt
- Close : Ctrl-W - Close
- Settings - Settings
- Line Separators - Line Separators
- CRLF - CRLF
- CR - CR
- LF - LF
- Edit - Edit
- Undo : Ctrl-Z - Undo
- Redo : Shift-Ctrl-Z - Redo
- Cut : Ctrl-X - Cut
- Copy : Ctrl-C - Copy
- Paste : Ctrl-V - Paste
- Find - Find
- Find : Ctrl-F - Find
- Replace : Shift-Ctrl-F - Replace
- Convert Indents - Convert Indents
- To Spaces - To Spaces
- Set Project Indentation - Set Project Indentation
- To Tabs - To Tabs
- Refactor - Refactor
- Refactor This - Refactor This
- Rename : Shift-Ctrl-R - Rename
- Extract - Extract
- Variable - Variable
- Property - Property

View File

@@ -13,7 +13,6 @@ import net.torvald.terrarum.gameworld.fmod
import net.torvald.terrarum.modulebasegame.gameworld.GameWorldExtension import net.torvald.terrarum.modulebasegame.gameworld.GameWorldExtension
import net.torvald.terrarum.modulebasegame.gameworld.WorldSimulator import net.torvald.terrarum.modulebasegame.gameworld.WorldSimulator
import net.torvald.terrarum.modulebasegame.gameworld.WorldTime import net.torvald.terrarum.modulebasegame.gameworld.WorldTime
import net.torvald.terrarum.utils.JsonWriter
import net.torvald.terrarum.worlddrawer.CreateTileAtlas.TILES_IN_X import net.torvald.terrarum.worlddrawer.CreateTileAtlas.TILES_IN_X
import net.torvald.terrarumsansbitmap.gdx.TextureRegionPack import net.torvald.terrarumsansbitmap.gdx.TextureRegionPack
import kotlin.math.roundToInt import kotlin.math.roundToInt
@@ -98,7 +97,7 @@ internal object BlocksDrawer {
printdbg(this, "Making terrain textures...") printdbg(this, "Making terrain textures...")
CreateTileAtlas() CreateTileAtlas()
JsonWriter.writeToFile(CreateTileAtlas.tags, "${AppLoader.defaultDir}/test_rendertags.json") //JsonWriter.writeToFile(CreateTileAtlas.tags, "${AppLoader.defaultDir}/test_rendertags.json")
// each takes about 60 seconds // each takes about 60 seconds
//printdbg(this, "Writing pixmap as tga: atlas.tga") //printdbg(this, "Writing pixmap as tga: atlas.tga")
//PixmapIO2.writeTGA(Gdx.files.absolute("${AppLoader.defaultDir}/atlas.tga"), CreateTileAtlas.atlas, false) //PixmapIO2.writeTGA(Gdx.files.absolute("${AppLoader.defaultDir}/atlas.tga"), CreateTileAtlas.atlas, false)

View File

@@ -11,7 +11,7 @@ import net.torvald.terrarum.AppLoader.printdbg
import net.torvald.terrarum.blockproperties.Block import net.torvald.terrarum.blockproperties.Block
import net.torvald.terrarum.blockproperties.BlockCodex import net.torvald.terrarum.blockproperties.BlockCodex
import net.torvald.terrarum.blockproperties.Fluid import net.torvald.terrarum.blockproperties.Fluid
import net.torvald.terrarum.concurrent.ParallelUtils.sliceEvenly import net.torvald.terrarum.concurrent.sliceEvenly
import net.torvald.terrarum.concurrent.ThreadParallel import net.torvald.terrarum.concurrent.ThreadParallel
import net.torvald.terrarum.gameactors.ActorWBMovable import net.torvald.terrarum.gameactors.ActorWBMovable
import net.torvald.terrarum.gameactors.ActorWithBody import net.torvald.terrarum.gameactors.ActorWithBody
@@ -400,7 +400,15 @@ object LightmapRenderer {
} }
private fun buildNoopMask() { private fun buildNoopMask() {
fun isShaded(x: Int, y: Int) = BlockCodex[world.getTileFromTerrain(x, y) ?: Block.STONE].isSolid fun isShaded(x: Int, y: Int) = try {
BlockCodex[world.getTileFromTerrain(x, y)].isSolid
}
catch (e: NullPointerException) {
System.err.println("Invalid block id ${world.getTileFromTerrain(x, y)} from coord ($x, $y)")
e.printStackTrace()
false
}
/* /*
update ordering: clockwise snake update ordering: clockwise snake