From 6118f30d5f1397e4e704e8c03dd998e4e246f4df Mon Sep 17 00:00:00 2001 From: minjaesong Date: Sat, 4 Apr 2026 17:05:00 +0900 Subject: [PATCH] async loading (hopefully?) --- .idea/csv-editor.xml | 23 ++ CLAUDE.md | 176 +++++++++++++ lib/TerrarumSansBitmap.jar | 4 +- src/net/torvald/terrarum/App.java | 8 +- .../terrarum/modulebasegame/IngameRenderer.kt | 8 +- .../terrarum/modulebasegame/TitleScreen.kt | 248 ++++++++++-------- 6 files changed, 344 insertions(+), 123 deletions(-) create mode 100644 .idea/csv-editor.xml create mode 100644 CLAUDE.md diff --git a/.idea/csv-editor.xml b/.idea/csv-editor.xml new file mode 100644 index 000000000..9c0d2513d --- /dev/null +++ b/.idea/csv-editor.xml @@ -0,0 +1,23 @@ + + + + + + \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 000000000..e25a7446d --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,176 @@ +# Terrarum + +A modular 2D side-scrolling tilemap platformer engine and game, built on LibGDX with Kotlin/Java. GPL-3.0. + +## Build & Run + +- **IDE**: IntelliJ IDEA (project files: `Terrarum_renewed.iml`, `TerrarumBuild.iml`) +- **JDK**: 21 +- **Language**: Kotlin + Java mixed; Kotlin is primary, `App.java` and `FrameBufferManager.java` are Java +- **Dependencies**: All libraries are in `lib/` (fat-jar approach, no Maven/Gradle). Includes LibGDX, GraalVM JS (modified), dyn4j (Vector2 only), custom bitmap font lib +- **Entry point**: `net.torvald.terrarum.Principii.main()` (Java) which launches `App` (the LibGDX `ApplicationListener`) +- **Distribution**: `buildapp/` scripts produce per-platform bundles with jlink'd runtimes +- **Assets**: `assets/` for development, `assets_release/` for distribution. Custom `.tevd` archive format for release assets (`AssetCache.kt`) + +## Project Structure + +``` +src/net/torvald/terrarum/ + App.java -- Main application class (LibGDX ApplicationListener). GL thread, render loop, splash screen + CommonResourcePool.kt -- Thread-safe GL resource loading with dispatch queues + ModMgr.kt -- Module manager. Scans/loads game modules, provides getGdxFile/getJavaClass + IngameInstance.kt -- Base class for game screens (show/hide/render/resize lifecycle) + Terrarum.kt -- Game-level singleton (ingame instance management, extension functions) + GameUpdateGovernor.kt -- Update/render tick governors (ConsistentUpdateRate, Anarchy) + MusicService.kt -- Music playback management + + modulebasegame/ + EntryPoint.kt -- Basegame module entry point (registers items, fixtures, blocks, weather) + TitleScreen.kt -- Title screen (demo world rendering, async loading) + TerrarumIngame.kt -- Main gameplay screen + IngameRenderer.kt -- World rendering pipeline (FBO composition, lightmap, blur, shadows) + BuildingMaker.kt -- Building editor screen + + worlddrawer/ + WorldCamera.kt -- Camera position tracking (follows player, interpolated movement) + LightmapRenderer.kt -- Per-tile RGB+UV light calculation and rasterisation + BlocksDrawer.kt -- Tile rendering + FeaturesDrawer.kt -- Wire/feature overlay rendering + + gamecontroller/ + IME.kt -- Input Method Engine (GraalVM JS keyboard layouts, loaded on daemon thread) + + weather/ + WeatherMixer.kt -- Weather state machine, skybox rendering + SkyboxModelHosek.kt -- Physically-based sky model +``` + +## Architecture: Threading & GL Dispatch + +All OpenGL calls (Texture, ShaderProgram, FrameBuffer creation) **must** happen on the GL thread. The codebase uses a dispatch mechanism to safely create GL resources from background threads. + +### CommonResourcePool (the dispatch hub) + +``` +Background Thread GL Thread (App.render) + | | + |-- addToLoadingList("name", { Texture() })| + |-- loadAll() | + | | | + | +-- if on GL thread: run directly | + | +-- if not: enqueue to | + | glDispatchQueue + latch.await() | + | CommonResourcePool.update() + | |-- poll glDispatchQueue + | | run loadfun, latch.countDown() + | |-- poll glRunnableQueue + | | run block, latch.countDown() + | |-- poll slowLoadingQueue (one per tick) + | | + +-- latch released, continues <------------+ +``` + +Key methods: +- `loadAll()` -- batch load; blocks background thread until GL thread processes +- `loadAllSlowly()` -- timesliced; one resource per tick via `update()` +- `runOnGLThread { }` -- run arbitrary block on GL thread, blocking caller +- `update()` -- called every tick from `App.render()`, processes all queues +- `getOrPut(name, loadfun, killfun)` -- thread-safe get-or-create with GL dispatch + +### App.java Boot Sequence + +``` +create() + -> postInit() [GL thread, synchronous] + |-- load shaders, fonts, audio device, IME + |-- CommonResourcePool.setGLThread(currentThread) + |-- spawn Terrarum-PostInitLoader thread: + | ModMgr.invoke() // module entry points (dispatched to GL via runOnGLThread) + | CommonResourcePool.loadAllSlowly() // timesliced resource loading + | loadingThreadDone = true + +-- return (non-blocking) + +render() loop [GL thread] + while currentScreen == null: + |-- drawSplash() + |-- CommonResourcePool.update() // process GL dispatch queues + |-- when loadingThreadDone && loaded: + | postLoadInit() // tile atlas, audio mixer, Terrarum.initialise() + | setScreen(titleScreen) // transitions to title screen +``` + +### TitleScreen Async Loading + +`TitleScreen.show()` returns immediately after starting a background thread. The splash screen continues displaying until all loading completes. + +``` +show() + |-- quick GL setup (viewport, input processor, FBO, savegame list) + |-- spawn Terrarum-TitleScreenLoader thread: + | load demo world, compute camera nodes, bogoflops, audio reset + | backgroundLoadDone = true + +-- return + +renderImpl() [GL thread, every frame] + if !loadDone: + |-- App.drawSplash() + |-- processGLLoadStep() // state machine, one step per frame: + | 0: wait for backgroundLoadDone + | 1: SkyboxModelHosek.loadlut() + | 2: IngameRenderer.setRenderedWorld + WeatherMixer init + | 3: load halfgrad texture + | 4: UIFakeGradOverlay + | 5: UIFakeBlurOverlay + | 6: UIRemoCon + | 7: MusicService.enterScene, gameUpdateGovernor.reset(), loadDone=true + else: + normal title screen rendering (world + UI via IngameRenderer) +``` + +### IME Loading + +`IME.kt` object `init {}` spawns a `Terrarum-IMELoader` daemon thread for GraalVM JS context binding and key layout/IME file scanning. Icon texture loading remains synchronous (requires GL thread). + +### ModMgr Class Initialisation + +`ModMgr.kt` object `init {}` only does metadata loading and class instantiation (fast). The heavy `invoke()` method runs entry points wrapped in `CommonResourcePool.runOnGLThread { }` to avoid GL calls on the wrong thread. This separation prevents `` lock deadlocks where a background thread holding the class init lock blocks on a GL dispatch latch while the GL thread tries to access the same class. + +## Architecture: Rendering Pipeline + +### IngameRenderer + +Singleton object. Lazily initialised on first `invoke()` call via `invokeInit()`. + +Render path (per frame): +1. `LightmapRenderer.recalculate()` -- every 3 frames, computes per-tile RGBA light values +2. `prepLightmapRGBA()` -- Kawase blur on lightmap into `lightmapFbo` +3. `BlocksDrawer.renderData()` -- prepare tile draw data +4. `drawToRGB()` -- render world tiles + actors into `fboRGB` (with shadow FBOs) +5. `drawToA()` -- render alpha/glow channel +6. Composite: sky -> world -> light multiply -> emissive blend -> vibrancy -> UI +7. Output to `App.renderFBO`, then `TerrarumPostProcessor.draw()` applies final effects + +**Critical ordering in `resize()`**: `BlocksDrawer.resize()` and `LightmapRenderer.resize()` must be called **before** `lightmapFbo` creation, because `lightmapFbo` dimensions derive from `LightmapRenderer.lightBuffer` size. + +### WorldCamera + +Follows an `ActorWithBody` (player or camera actor). Position interpolated per frame. `WorldCamera.x/y` = top-left corner of visible area. `gdxCamX/Y` = centre of visible area. `moveCameraToWorldCoord()` positions the IngameRenderer camera for world-space drawing; `setCameraPosition(0,0)` positions it for screen-space drawing. + +### FBO Extension Functions + +- `FrameBuffer.inAction(camera, batch) { }` -- bind FBO, set camera to FBO dimensions (Y-down), run block, restore camera to screen dimensions +- `FrameBuffer.inActionF(camera, batch) { }` -- same but Y-up (for flipped FBO content) + +Both save/restore `camera.position` and call `setToOrtho` with `App.scr.wf/hf` on exit. + +## Key Conventions + +- **GL thread safety**: Any code creating `Texture`, `TextureRegion`, `TextureRegionPack`, `ShaderProgram`, `FrameBuffer`, or `Pixmap` must run on the GL thread. Use `CommonResourcePool.runOnGLThread { }` when on a background thread. +- **Kotlin `object` singletons**: First access triggers ``. Keep `init {}` blocks fast (no blocking, no GL dispatch). Defer heavy work to explicit `invoke()` methods. +- **`ConcurrentHashMap` cannot store null values**. Use `HashMap` for maps that need nullable values (e.g. `poolKillFun`). +- **`@Volatile`** for cross-thread boolean flags (`loadDone`, `backgroundLoadDone`, etc.) +- **`lateinit var` guards**: Use `::prop.isInitialized` checks in `resize()` and `dispose()` for properties set during async loading steps. +- **`ConsistentUpdateRate`**: Accumulates delta time; may call `updateFunction` multiple times before `renderFunction`. Call `.reset()` before transitioning to active rendering to avoid catch-up storms. +- **Textures**: Use TGA format. Images with semitransparency must be TGA; opaque images may be PNG. +- **Coordinate system**: Y-down (camera `setToOrtho(true, ...)`). `setCameraPosition(0, 0)` places origin at screen top-left. +- **ROUNDWORLD**: World wraps horizontally. `WorldCamera.x` uses `fmod worldWidth`. Lightmap and tile drawing account for wrapping. diff --git a/lib/TerrarumSansBitmap.jar b/lib/TerrarumSansBitmap.jar index 94ed4f7c9..6190e22d7 100644 --- a/lib/TerrarumSansBitmap.jar +++ b/lib/TerrarumSansBitmap.jar @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b99d2d0129bfc309fcd4a485c5f2f29eb926c62e460d8d8260f7dc084513dee4 -size 2181259 +oid sha256:2bc080f95ecba9badcc539f2a255916994779c7e8b3ce5b8c5a3ac07f63afce4 +size 2198837 diff --git a/src/net/torvald/terrarum/App.java b/src/net/torvald/terrarum/App.java index 780acb759..e3a2e2aff 100644 --- a/src/net/torvald/terrarum/App.java +++ b/src/net/torvald/terrarum/App.java @@ -865,7 +865,7 @@ public class App implements ApplicationListener { return ditherPatterns[hash % ditherPatterns.length]; } - private void drawSplash() { + public static void drawSplash() { setCameraPosition(0f, 0f); logoBatch.setColor(Color.WHITE); @@ -874,7 +874,7 @@ public class App implements ApplicationListener { int drawWidth = Toolkit.INSTANCE.getDrawWidth(); int safetyTextLen = fontGame.getWidth(Lang.INSTANCE.get("APP_WARNING_HEALTH_AND_SAFETY", true)); - int logoPosX = (drawWidth - splashScreenLogo.getRegionWidth() - safetyTextLen) >>> 1; + int logoPosX0 = (drawWidth - splashScreenLogo.getRegionWidth() - safetyTextLen) >>> 1; int logoPosY = Math.round(scr.getHeight() / 15f); int textY = logoPosY + splashScreenLogo.getRegionHeight() - 16; @@ -900,7 +900,7 @@ public class App implements ApplicationListener { logoBatch.end(); } - + int logoPosX = (int)(logoPosX0 + Math.round(100 * Math.sin(GLOBAL_RENDER_TIMER / 50.0))); // draw logo reflection logoBatch.setShader(shaderReflect); @@ -1448,7 +1448,7 @@ public class App implements ApplicationListener { } - private void setCameraPosition(float newX, float newY) { + private static void setCameraPosition(float newX, float newY) { camera.position.set((-newX + scr.getWidth() / 2), (-newY + scr.getHeight() / 2), 0f); // deliberate integer division camera.update(); logoBatch.setProjectionMatrix(camera.combined); diff --git a/src/net/torvald/terrarum/modulebasegame/IngameRenderer.kt b/src/net/torvald/terrarum/modulebasegame/IngameRenderer.kt index 39c618fe9..f9e4709c3 100644 --- a/src/net/torvald/terrarum/modulebasegame/IngameRenderer.kt +++ b/src/net/torvald/terrarum/modulebasegame/IngameRenderer.kt @@ -1307,6 +1307,11 @@ object IngameRenderer : Disposable { //fboBlurQuarter.dispose() } + // BlocksDrawer and LightmapRenderer must be resized before lightmapFbo is created, + // because lightmapFbo dimensions are derived from LightmapRenderer.lightBuffer. + BlocksDrawer.resize(width, height) + LightmapRenderer.resize(width, height) + fboRGB = Float16FrameBuffer(width, height, false) fboRGB_lightMixed0 = Float16FrameBuffer(width, height, false) fboRGB_lightMixed = Float16FrameBuffer(width, height, false) @@ -1345,9 +1350,6 @@ object IngameRenderer : Disposable { false )*/ - BlocksDrawer.resize(width, height) - LightmapRenderer.resize(width, height) - blurWriteQuad.setVertices(floatArrayOf( 0f,0f,0f, 1f,1f,1f,1f, 0f,1f, diff --git a/src/net/torvald/terrarum/modulebasegame/TitleScreen.kt b/src/net/torvald/terrarum/modulebasegame/TitleScreen.kt index 23929e683..f8bfc6e63 100644 --- a/src/net/torvald/terrarum/modulebasegame/TitleScreen.kt +++ b/src/net/torvald/terrarum/modulebasegame/TitleScreen.kt @@ -66,7 +66,10 @@ class TitleScreen(batch: FlippingSpriteBatch) : IngameInstance(batch) { } - //private var loadDone = false // not required; draw-while-loading is implemented in the AppLoader + private var loadDone = false + @Volatile private var backgroundLoadDone = false + private var glLoadStep = 0 + private lateinit var titleLoadingThread: Thread private lateinit var demoWorld: GameWorld private lateinit var cameraNodes: FloatArray // camera Y-pos @@ -156,117 +159,129 @@ class TitleScreen(batch: FlippingSpriteBatch) : IngameInstance(batch) { gameUpdateGovernor = ConsistentUpdateRate.also { it.reset() } } - private fun loadThingsWhileIntroIsVisible() { - printdbg(this, "Intro pre-load") + private fun startBackgroundLoading() { + titleLoadingThread = Thread({ + printdbg(this, "Background loading started") - - try { - val fileHandle = ModMgr.getGdxFile("basegame", "demoworld") - val reader = fileHandle.reader("UTF-8") - //ReadWorld.readWorldAndSetNewWorld(Terrarum.ingame!! as TerrarumIngame, reader) - val world = ReadSimpleWorld(reader, fileHandle.file()) - demoWorld = world - demoWorld.worldTime.timeDelta = 30 - printdbg(this, "Demo world loaded") - } - catch (e: IOException) { - demoWorld = GameWorld(LandUtil.CHUNK_W, LandUtil.CHUNK_H, 0L, 0L) - demoWorld.worldTime.timeDelta = 30 - printdbg(this, "Demo world not found, using empty world") - } - - demoWorld.renumberTilesAfterLoad() - this.world = demoWorld - - // set initial time to summer - demoWorld.worldTime.addTime(WorldTime.DAY_LENGTH * 32) - - // construct camera nodes - val nodeCount = demoWorld.width / cameraNodeWidth - cameraNodes = kotlin.FloatArray(nodeCount) { - val tileXPos = (demoWorld.width.toFloat() * it / nodeCount).floorToInt() - var travelDownCounter = 0 - while (travelDownCounter < demoWorld.height && - !BlockCodex[demoWorld.getTileFromTerrain(tileXPos, travelDownCounter)].isSolid -// !BlockCodex[demoWorld.getTileFromWall(tileXPos, travelDownCounter)].isSolid - ) { - travelDownCounter += 2 + try { + val fileHandle = ModMgr.getGdxFile("basegame", "demoworld") + val reader = fileHandle.reader("UTF-8") + val world = ReadSimpleWorld(reader, fileHandle.file()) + demoWorld = world + demoWorld.worldTime.timeDelta = 30 + printdbg(this, "Demo world loaded") + } + catch (e: IOException) { + demoWorld = GameWorld(LandUtil.CHUNK_W, LandUtil.CHUNK_H, 0L, 0L) + demoWorld.worldTime.timeDelta = 30 + printdbg(this, "Demo world not found, using empty world") } - travelDownCounter * TILE_SIZEF - } - // apply gaussian blur to the camera nodes - for (i in cameraNodes.indices) { -// val offM2 = cameraNodes[(i-2) fmod cameraNodes.size] * 1f - val offM1 = cameraNodes[(i-1) fmod cameraNodes.size] * 1f - val off0 = cameraNodes[i] * 2f - val off1 = cameraNodes[(i+1) fmod cameraNodes.size] * 1f -// val off2 = cameraNodes[(i+2) fmod cameraNodes.size] * 1f -// cameraNodes[i] = (offM2 + offM1 + off0 + off1 + off2) / 16f - cameraNodes[i] = (offM1 + off0 + off1) / 4f - } + demoWorld.renumberTilesAfterLoad() + this.world = demoWorld + // set initial time to summer + demoWorld.worldTime.addTime(WorldTime.DAY_LENGTH * 32) + // construct camera nodes + val nodeCount = demoWorld.width / cameraNodeWidth + cameraNodes = kotlin.FloatArray(nodeCount) { + val tileXPos = (demoWorld.width.toFloat() * it / nodeCount).floorToInt() + var travelDownCounter = 0 + while (travelDownCounter < demoWorld.height && + !BlockCodex[demoWorld.getTileFromTerrain(tileXPos, travelDownCounter)].isSolid + ) { + travelDownCounter += 2 + } + travelDownCounter * TILE_SIZEF + } + // apply gaussian blur to the camera nodes + for (i in cameraNodes.indices) { + val offM1 = cameraNodes[(i-1) fmod cameraNodes.size] * 1f + val off0 = cameraNodes[i] * 2f + val off1 = cameraNodes[(i+1) fmod cameraNodes.size] * 1f + cameraNodes[i] = (offM1 + off0 + off1) / 4f + } - cameraPlayer = CameraPlayer(demoWorld, cameraAI) + cameraPlayer = CameraPlayer(demoWorld, cameraAI) + CommandDict // invoke - IngameRenderer.setRenderedWorld(demoWorld) - WeatherMixer.internalReset(this) - WeatherMixer.titleScreenInitWeather(demoWorld.weatherbox) - WeatherMixer.forceTimeAt = 23456 + // measure bogoflops here + val bogoflopsOld = App.bogoflops + App.updateBogoflops(100_000_000L) + printdbg(this, "Bogoflops old: $bogoflopsOld new: ${App.bogoflops}") + App.bogoflops = maxOf(App.bogoflops, bogoflopsOld) + App.audioMixer.ambientTracks.forEach { + it.stop() + it.currentTrack = null + it.nextTrack = null + } + App.audioMixer.reset() - // load a half-gradient texture that would be used throughout the titlescreen and its sub UIs - CommonResourcePool.addToLoadingList("title_halfgrad") { - Texture(AssetCache.getFileHandle("graphics/halfgrad.tga")).also { - it.setFilter(Texture.TextureFilter.Linear, Texture.TextureFilter.Linear) + printdbg(this, "Background loading done") + backgroundLoadDone = true + }, "Terrarum-TitleScreenLoader") + titleLoadingThread.isDaemon = true + titleLoadingThread.start() + } + + private fun processGLLoadStep() { + when (glLoadStep) { + 0 -> { + // Wait for background thread to finish world loading + CPU work + if (backgroundLoadDone) glLoadStep++ + } + 1 -> { + SkyboxModelHosek.loadlut() + glLoadStep++ + } + 2 -> { + IngameRenderer.setRenderedWorld(demoWorld) + WeatherMixer.internalReset(this) + WeatherMixer.titleScreenInitWeather(demoWorld.weatherbox) + WeatherMixer.forceTimeAt = 23456 + glLoadStep++ + } + 3 -> { + // Load half-gradient texture + CommonResourcePool.addToLoadingList("title_halfgrad") { + Texture(AssetCache.getFileHandle("graphics/halfgrad.tga")).also { + it.setFilter(Texture.TextureFilter.Linear, Texture.TextureFilter.Linear) + } + } + CommonResourcePool.loadAll() + glLoadStep++ + } + 4 -> { + // Gradient overlay UI + val uiFakeGradOverlay = UIFakeGradOverlay() + uiFakeGradOverlay.setPosition(0, 0) + uiContainer.add(uiFakeGradOverlay) + glLoadStep++ + } + 5 -> { + // Blur overlay UI + uiFakeBlurOverlay = UIFakeBlurOverlay(1f, false) + uiFakeBlurOverlay.setPosition(0, 0) + uiContainer.add(uiFakeBlurOverlay) + glLoadStep++ + } + 6 -> { + // Remote control UI + uiRemoCon = UIRemoCon(this, UITitleRemoConYaml(App.savegamePlayers.isNotEmpty())) + uiRemoCon.setPosition(0, 0) + uiRemoCon.setAsOpen() + uiContainer.add(uiRemoCon) + glLoadStep++ + } + 7 -> { + MusicService.enterScene("title") + gameUpdateGovernor.reset() + loadDone = true } } - CommonResourcePool.loadAll() - - - // fake UI for gradient overlay - val uiFakeGradOverlay = UIFakeGradOverlay() - uiFakeGradOverlay.setPosition(0, 0) - uiContainer.add(uiFakeGradOverlay) - - - // fake UI for blur - uiFakeBlurOverlay = UIFakeBlurOverlay(1f, false) - uiFakeBlurOverlay.setPosition(0,0) - uiContainer.add(uiFakeBlurOverlay) - - - uiRemoCon = UIRemoCon(this, UITitleRemoConYaml(App.savegamePlayers.isNotEmpty())) - uiRemoCon.setPosition(0, 0) - uiRemoCon.setAsOpen() - - - uiContainer.add(uiRemoCon) - - CommandDict // invoke - SkyboxModelHosek.loadlut() // invoke -// Skybox.initiate() // invoke the lengthy calculation - // TODO add console here - - - //loadDone = true - - // measure bogoflops here - val bogoflopsOld = App.bogoflops - App.updateBogoflops(100_000_000L) - printdbg(this, "Bogoflops old: $bogoflopsOld new: ${App.bogoflops}") - App.bogoflops = maxOf(App.bogoflops, bogoflopsOld) - - - App.audioMixer.ambientTracks.forEach { - it.stop() - it.currentTrack = null - it.nextTrack = null - } - App.audioMixer.reset() - } @@ -294,10 +309,8 @@ class TitleScreen(batch: FlippingSpriteBatch) : IngameInstance(batch) { App.updateListOfSavegames() UILoadGovernor.reset() - loadThingsWhileIntroIsVisible() + startBackgroundLoading() printdbg(this, "show() exit") - - MusicService.enterScene("title") } @@ -305,6 +318,12 @@ class TitleScreen(batch: FlippingSpriteBatch) : IngameInstance(batch) { private var introUncoverDeltaCounter = 0f override fun renderImpl(updateRate: Float) { + if (!loadDone) { + App.drawSplash() + processGLLoadStep() + return + } + IngameRenderer.setRenderedWorld(demoWorld) super.renderImpl(updateRate) @@ -549,23 +568,24 @@ class TitleScreen(batch: FlippingSpriteBatch) : IngameInstance(batch) { initViewPort(App.scr.width, App.scr.height) - // resize UI by re-creating it (!!) - uiRemoCon.resize(App.scr.width, App.scr.height) - // TODO I forgot what the fuck kind of hack I was talking about - //uiMenu.setPosition(0, UITitleRemoConRoot.menubarOffY) - uiRemoCon.setPosition(0, 0) // shitty hack. Could be: - // 1: Init code and resize code are different - // 2: The UI is coded shit + if (::uiRemoCon.isInitialized) { + // resize UI by re-creating it (!!) + uiRemoCon.resize(App.scr.width, App.scr.height) + // TODO I forgot what the fuck kind of hack I was talking about + //uiMenu.setPosition(0, UITitleRemoConRoot.menubarOffY) + uiRemoCon.setPosition(0, 0) // shitty hack. Could be: + // 1: Init code and resize code are different + // 2: The UI is coded shit - - IngameRenderer.resize(App.scr.width, App.scr.height) + IngameRenderer.resize(App.scr.width, App.scr.height) + } printdbg(this, "resize() exit") } override fun dispose() { - uiRemoCon.dispose() - demoWorld.dispose() + if (::uiRemoCon.isInitialized) uiRemoCon.dispose() + if (::demoWorld.isInitialized) demoWorld.dispose() warning32bitJavaIcon.texture.dispose() warningAppleRosettaIcon.texture.dispose() }