From f880544a24b701bd28aac5a3af840768ae7ae844 Mon Sep 17 00:00:00 2001 From: Ben Forge <74168521+BenCheung0422@users.noreply.github.com> Date: Fri, 16 May 2025 06:56:12 +0800 Subject: [PATCH 01/19] Draft main structure --- .idea/compiler.xml | 10 ------ .idea/copyright/copyright.xml | 6 ++++ .idea/copyright/profiles_settings.xml | 7 ++++ .idea/misc.xml | 2 +- .idea/modules.xml | 10 ------ .idea/modules/TerraModulus.iml | 12 +++++++ .idea/modules/TerraModulus.main.iml | 31 +++++++++++++++++ .idea/modules/TerraModulus.test.iml | 33 +++++++++++++++++++ .../src/client/TerraModulus.client.iml | 12 +++++++ .../src/client/TerraModulus.client.main.iml | 11 ------- .../src/client/TerraModulus.client.test.iml | 32 ++++++++++++++++++ .../src/common/TerraModulus.common.iml | 12 +++++++ .../src/common/TerraModulus.common.main.iml | 11 ------- .../src/common/TerraModulus.common.test.iml | 16 +++++++++ .../src/server/TerraModulus.server.iml | 12 +++++++ .../src/server/TerraModulus.server.main.iml | 11 ------- .../src/server/TerraModulus.server.test.iml | 17 ++++++++++ build.gradle.kts | 6 ++-- src/client/kotlin/terramodulus/client/Main.kt | 10 ------ src/client/kotlin/terramodulus/core/Main.kt | 15 +++++++++ .../kotlin/terramodulus/core/TerraModulus.kt | 12 +++++++ .../terramodulus/core/AbstractTerraModulus.kt | 10 ++++++ src/server/kotlin/terramodulus/core/Main.kt | 10 ++++++ .../kotlin/terramodulus/core/TerraModulus.kt | 12 +++++++ 24 files changed, 253 insertions(+), 67 deletions(-) delete mode 100644 .idea/compiler.xml create mode 100644 .idea/copyright/copyright.xml create mode 100644 .idea/copyright/profiles_settings.xml delete mode 100644 .idea/modules.xml create mode 100644 .idea/modules/TerraModulus.iml create mode 100644 .idea/modules/TerraModulus.main.iml create mode 100644 .idea/modules/TerraModulus.test.iml create mode 100644 .idea/modules/src/client/TerraModulus.client.iml delete mode 100644 .idea/modules/src/client/TerraModulus.client.main.iml create mode 100644 .idea/modules/src/client/TerraModulus.client.test.iml create mode 100644 .idea/modules/src/common/TerraModulus.common.iml delete mode 100644 .idea/modules/src/common/TerraModulus.common.main.iml create mode 100644 .idea/modules/src/common/TerraModulus.common.test.iml create mode 100644 .idea/modules/src/server/TerraModulus.server.iml delete mode 100644 .idea/modules/src/server/TerraModulus.server.main.iml create mode 100644 .idea/modules/src/server/TerraModulus.server.test.iml delete mode 100644 src/client/kotlin/terramodulus/client/Main.kt create mode 100644 src/client/kotlin/terramodulus/core/Main.kt create mode 100644 src/client/kotlin/terramodulus/core/TerraModulus.kt create mode 100644 src/common/kotlin/terramodulus/core/AbstractTerraModulus.kt create mode 100644 src/server/kotlin/terramodulus/core/Main.kt create mode 100644 src/server/kotlin/terramodulus/core/TerraModulus.kt diff --git a/.idea/compiler.xml b/.idea/compiler.xml deleted file mode 100644 index c2b27a04..00000000 --- a/.idea/compiler.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/.idea/copyright/copyright.xml b/.idea/copyright/copyright.xml new file mode 100644 index 00000000..a15ac165 --- /dev/null +++ b/.idea/copyright/copyright.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml new file mode 100644 index 00000000..041ca056 --- /dev/null +++ b/.idea/copyright/profiles_settings.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index 4b0bf0df..ddab4a76 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -4,5 +4,5 @@ - + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index 713c8ba9..00000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/TerraModulus.iml b/.idea/modules/TerraModulus.iml new file mode 100644 index 00000000..2e5fc3dc --- /dev/null +++ b/.idea/modules/TerraModulus.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules/TerraModulus.main.iml b/.idea/modules/TerraModulus.main.iml new file mode 100644 index 00000000..b1736c41 --- /dev/null +++ b/.idea/modules/TerraModulus.main.iml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules/TerraModulus.test.iml b/.idea/modules/TerraModulus.test.iml new file mode 100644 index 00000000..a4080e18 --- /dev/null +++ b/.idea/modules/TerraModulus.test.iml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules/src/client/TerraModulus.client.iml b/.idea/modules/src/client/TerraModulus.client.iml new file mode 100644 index 00000000..d7fc5b2d --- /dev/null +++ b/.idea/modules/src/client/TerraModulus.client.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules/src/client/TerraModulus.client.main.iml b/.idea/modules/src/client/TerraModulus.client.main.iml deleted file mode 100644 index e2a94a0e..00000000 --- a/.idea/modules/src/client/TerraModulus.client.main.iml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/src/client/TerraModulus.client.test.iml b/.idea/modules/src/client/TerraModulus.client.test.iml new file mode 100644 index 00000000..4ac0bf1a --- /dev/null +++ b/.idea/modules/src/client/TerraModulus.client.test.iml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules/src/common/TerraModulus.common.iml b/.idea/modules/src/common/TerraModulus.common.iml new file mode 100644 index 00000000..ef851b4b --- /dev/null +++ b/.idea/modules/src/common/TerraModulus.common.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules/src/common/TerraModulus.common.main.iml b/.idea/modules/src/common/TerraModulus.common.main.iml deleted file mode 100644 index 33e4aae8..00000000 --- a/.idea/modules/src/common/TerraModulus.common.main.iml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/src/common/TerraModulus.common.test.iml b/.idea/modules/src/common/TerraModulus.common.test.iml new file mode 100644 index 00000000..5a56cf3d --- /dev/null +++ b/.idea/modules/src/common/TerraModulus.common.test.iml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules/src/server/TerraModulus.server.iml b/.idea/modules/src/server/TerraModulus.server.iml new file mode 100644 index 00000000..c13d567a --- /dev/null +++ b/.idea/modules/src/server/TerraModulus.server.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules/src/server/TerraModulus.server.main.iml b/.idea/modules/src/server/TerraModulus.server.main.iml deleted file mode 100644 index 57a9f4d3..00000000 --- a/.idea/modules/src/server/TerraModulus.server.main.iml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/src/server/TerraModulus.server.test.iml b/.idea/modules/src/server/TerraModulus.server.test.iml new file mode 100644 index 00000000..bc417dd5 --- /dev/null +++ b/.idea/modules/src/server/TerraModulus.server.test.iml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 46ab9473..c282a4d7 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -35,13 +35,13 @@ subprojects { project(":common") { dependencies { - implementation("org.jetbrains:annotations:26.0.2") + api("org.jetbrains:annotations:26.0.2") } } project(":client") { dependencies { - implementation(project(":common")) + api(project(":common")) } application { @@ -51,7 +51,7 @@ project(":client") { project(":server") { dependencies { - implementation(project(":common")) + api(project(":common")) } application { diff --git a/src/client/kotlin/terramodulus/client/Main.kt b/src/client/kotlin/terramodulus/client/Main.kt deleted file mode 100644 index cf2c5455..00000000 --- a/src/client/kotlin/terramodulus/client/Main.kt +++ /dev/null @@ -1,10 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2025 Minicraft+ contributors - * SPDX-License-Identifier: LGPL-3.0-only - */ - -package terramodulus.client - -class Main { - -} diff --git a/src/client/kotlin/terramodulus/core/Main.kt b/src/client/kotlin/terramodulus/core/Main.kt new file mode 100644 index 00000000..b7317e32 --- /dev/null +++ b/src/client/kotlin/terramodulus/core/Main.kt @@ -0,0 +1,15 @@ +/* + * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package terramodulus.core + +fun main() { + val game = TerraModulus() + try { + game.run() + } catch (e: Exception) { + TODO("Not yet implemented") + } +} diff --git a/src/client/kotlin/terramodulus/core/TerraModulus.kt b/src/client/kotlin/terramodulus/core/TerraModulus.kt new file mode 100644 index 00000000..5429e13b --- /dev/null +++ b/src/client/kotlin/terramodulus/core/TerraModulus.kt @@ -0,0 +1,12 @@ +/* + * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package terramodulus.core + +class TerraModulus : AbstractTerraModulus() { + override fun run() { + TODO("Not yet implemented") + } +} diff --git a/src/common/kotlin/terramodulus/core/AbstractTerraModulus.kt b/src/common/kotlin/terramodulus/core/AbstractTerraModulus.kt new file mode 100644 index 00000000..d53f6fca --- /dev/null +++ b/src/common/kotlin/terramodulus/core/AbstractTerraModulus.kt @@ -0,0 +1,10 @@ +/* + * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package terramodulus.core + +abstract class AbstractTerraModulus { + abstract fun run() +} diff --git a/src/server/kotlin/terramodulus/core/Main.kt b/src/server/kotlin/terramodulus/core/Main.kt new file mode 100644 index 00000000..d8ac2e75 --- /dev/null +++ b/src/server/kotlin/terramodulus/core/Main.kt @@ -0,0 +1,10 @@ +/* + * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package terramodulus.core + +fun main() { + +} diff --git a/src/server/kotlin/terramodulus/core/TerraModulus.kt b/src/server/kotlin/terramodulus/core/TerraModulus.kt new file mode 100644 index 00000000..5429e13b --- /dev/null +++ b/src/server/kotlin/terramodulus/core/TerraModulus.kt @@ -0,0 +1,12 @@ +/* + * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package terramodulus.core + +class TerraModulus : AbstractTerraModulus() { + override fun run() { + TODO("Not yet implemented") + } +} From 29cdaeb9d49ac4c4ee1f6243f6e5ba02ea4ac187 Mon Sep 17 00:00:00 2001 From: Ben Forge <74168521+BenCheung0422@users.noreply.github.com> Date: Fri, 16 May 2025 08:16:06 +0800 Subject: [PATCH 02/19] Draft more files --- build.gradle.kts | 11 ++++++++++- src/client/kotlin/terramodulus/audio/AudioSystem.kt | 9 +++++++++ src/client/kotlin/terramodulus/gfx/Component.kt | 9 +++++++++ src/client/kotlin/terramodulus/gfx/Menu.kt | 9 +++++++++ src/client/kotlin/terramodulus/gfx/RenderSystem.kt | 9 +++++++++ src/client/kotlin/terramodulus/gfx/Screen.kt | 9 +++++++++ src/client/kotlin/terramodulus/gfx/ScreenManager.kt | 9 +++++++++ src/client/kotlin/terramodulus/input/InputHandler.kt | 9 +++++++++ src/client/kotlin/terramodulus/settings/Preference.kt | 9 +++++++++ src/client/kotlin/terramodulus/settings/Settings.kt | 9 +++++++++ .../kotlin/terramodulus/core/AbstractTerraModulus.kt | 2 ++ src/common/kotlin/terramodulus/core/Constants.kt | 9 +++++++++ src/common/kotlin/terramodulus/core/package-info.java | 9 +++++++++ src/common/kotlin/terramodulus/world/WorldManager.kt | 9 +++++++++ src/common/kotlin/terramodulus/world/item/Items.kt | 9 +++++++++ src/common/kotlin/terramodulus/world/tile/Tiles.kt | 9 +++++++++ 16 files changed, 138 insertions(+), 1 deletion(-) create mode 100644 src/client/kotlin/terramodulus/audio/AudioSystem.kt create mode 100644 src/client/kotlin/terramodulus/gfx/Component.kt create mode 100644 src/client/kotlin/terramodulus/gfx/Menu.kt create mode 100644 src/client/kotlin/terramodulus/gfx/RenderSystem.kt create mode 100644 src/client/kotlin/terramodulus/gfx/Screen.kt create mode 100644 src/client/kotlin/terramodulus/gfx/ScreenManager.kt create mode 100644 src/client/kotlin/terramodulus/input/InputHandler.kt create mode 100644 src/client/kotlin/terramodulus/settings/Preference.kt create mode 100644 src/client/kotlin/terramodulus/settings/Settings.kt create mode 100644 src/common/kotlin/terramodulus/core/Constants.kt create mode 100644 src/common/kotlin/terramodulus/core/package-info.java create mode 100644 src/common/kotlin/terramodulus/world/WorldManager.kt create mode 100644 src/common/kotlin/terramodulus/world/item/Items.kt create mode 100644 src/common/kotlin/terramodulus/world/tile/Tiles.kt diff --git a/build.gradle.kts b/build.gradle.kts index c282a4d7..7946c482 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,7 +1,9 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget plugins { - kotlin("jvm") version "2.1.0" + kotlin("jvm") version "2.1.20" + kotlin("plugin.serialization") version "2.1.20" + id("org.jetbrains.kotlinx.atomicfu") version "0.27.0" application } @@ -36,6 +38,13 @@ subprojects { project(":common") { dependencies { api("org.jetbrains:annotations:26.0.2") + api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.8.1") + api("org.jetbrains.kotlinx:kotlinx-io-core:0.7.0") + api("org.jetbrains.kotlinx:kotlinx-datetime:0.6.2") + api("org.jetbrains.kotlinx:multik-core:0.2.3") + api("org.jetbrains.kotlinx:multik-default:0.2.3") + api(kotlin("reflect")) + api("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2") } } diff --git a/src/client/kotlin/terramodulus/audio/AudioSystem.kt b/src/client/kotlin/terramodulus/audio/AudioSystem.kt new file mode 100644 index 00000000..3feeebc6 --- /dev/null +++ b/src/client/kotlin/terramodulus/audio/AudioSystem.kt @@ -0,0 +1,9 @@ +/* + * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package terramodulus.audio + +class AudioSystem { +} diff --git a/src/client/kotlin/terramodulus/gfx/Component.kt b/src/client/kotlin/terramodulus/gfx/Component.kt new file mode 100644 index 00000000..423d5598 --- /dev/null +++ b/src/client/kotlin/terramodulus/gfx/Component.kt @@ -0,0 +1,9 @@ +/* + * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package terramodulus.gfx + +class Component { +} diff --git a/src/client/kotlin/terramodulus/gfx/Menu.kt b/src/client/kotlin/terramodulus/gfx/Menu.kt new file mode 100644 index 00000000..27fbbdd3 --- /dev/null +++ b/src/client/kotlin/terramodulus/gfx/Menu.kt @@ -0,0 +1,9 @@ +/* + * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package terramodulus.gfx + +class Menu { +} diff --git a/src/client/kotlin/terramodulus/gfx/RenderSystem.kt b/src/client/kotlin/terramodulus/gfx/RenderSystem.kt new file mode 100644 index 00000000..3846e1b4 --- /dev/null +++ b/src/client/kotlin/terramodulus/gfx/RenderSystem.kt @@ -0,0 +1,9 @@ +/* + * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package terramodulus.gfx + +class RenderSystem { +} diff --git a/src/client/kotlin/terramodulus/gfx/Screen.kt b/src/client/kotlin/terramodulus/gfx/Screen.kt new file mode 100644 index 00000000..a822f102 --- /dev/null +++ b/src/client/kotlin/terramodulus/gfx/Screen.kt @@ -0,0 +1,9 @@ +/* + * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package terramodulus.gfx + +class Screen { +} diff --git a/src/client/kotlin/terramodulus/gfx/ScreenManager.kt b/src/client/kotlin/terramodulus/gfx/ScreenManager.kt new file mode 100644 index 00000000..02b604c9 --- /dev/null +++ b/src/client/kotlin/terramodulus/gfx/ScreenManager.kt @@ -0,0 +1,9 @@ +/* + * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package terramodulus.gfx + +class ScreenManager { +} diff --git a/src/client/kotlin/terramodulus/input/InputHandler.kt b/src/client/kotlin/terramodulus/input/InputHandler.kt new file mode 100644 index 00000000..bceeea88 --- /dev/null +++ b/src/client/kotlin/terramodulus/input/InputHandler.kt @@ -0,0 +1,9 @@ +/* + * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package terramodulus.input + +class InputHandler { +} diff --git a/src/client/kotlin/terramodulus/settings/Preference.kt b/src/client/kotlin/terramodulus/settings/Preference.kt new file mode 100644 index 00000000..a4f00778 --- /dev/null +++ b/src/client/kotlin/terramodulus/settings/Preference.kt @@ -0,0 +1,9 @@ +/* + * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package terramodulus.settings + +class Preference { +} diff --git a/src/client/kotlin/terramodulus/settings/Settings.kt b/src/client/kotlin/terramodulus/settings/Settings.kt new file mode 100644 index 00000000..3aff410f --- /dev/null +++ b/src/client/kotlin/terramodulus/settings/Settings.kt @@ -0,0 +1,9 @@ +/* + * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package terramodulus.settings + +class Settings { +} diff --git a/src/common/kotlin/terramodulus/core/AbstractTerraModulus.kt b/src/common/kotlin/terramodulus/core/AbstractTerraModulus.kt index d53f6fca..7b3d232f 100644 --- a/src/common/kotlin/terramodulus/core/AbstractTerraModulus.kt +++ b/src/common/kotlin/terramodulus/core/AbstractTerraModulus.kt @@ -6,5 +6,7 @@ package terramodulus.core abstract class AbstractTerraModulus { + abstract var tps: Int + abstract fun run() } diff --git a/src/common/kotlin/terramodulus/core/Constants.kt b/src/common/kotlin/terramodulus/core/Constants.kt new file mode 100644 index 00000000..46dcb456 --- /dev/null +++ b/src/common/kotlin/terramodulus/core/Constants.kt @@ -0,0 +1,9 @@ +/* + * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package terramodulus.core + +const val NAME = "TerraModulus" +const val VERSION = "0.1.0" // TODO placeholder diff --git a/src/common/kotlin/terramodulus/core/package-info.java b/src/common/kotlin/terramodulus/core/package-info.java new file mode 100644 index 00000000..923995bd --- /dev/null +++ b/src/common/kotlin/terramodulus/core/package-info.java @@ -0,0 +1,9 @@ +/* + * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +/** + * Critical components + */ +package terramodulus.core; diff --git a/src/common/kotlin/terramodulus/world/WorldManager.kt b/src/common/kotlin/terramodulus/world/WorldManager.kt new file mode 100644 index 00000000..97e42c9a --- /dev/null +++ b/src/common/kotlin/terramodulus/world/WorldManager.kt @@ -0,0 +1,9 @@ +/* + * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package terramodulus.world + +class WorldManager { +} diff --git a/src/common/kotlin/terramodulus/world/item/Items.kt b/src/common/kotlin/terramodulus/world/item/Items.kt new file mode 100644 index 00000000..2e87f1b6 --- /dev/null +++ b/src/common/kotlin/terramodulus/world/item/Items.kt @@ -0,0 +1,9 @@ +/* + * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package terramodulus.world.item + +class Items { +} diff --git a/src/common/kotlin/terramodulus/world/tile/Tiles.kt b/src/common/kotlin/terramodulus/world/tile/Tiles.kt new file mode 100644 index 00000000..39aacee5 --- /dev/null +++ b/src/common/kotlin/terramodulus/world/tile/Tiles.kt @@ -0,0 +1,9 @@ +/* + * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package terramodulus.world.tile + +class Tiles { +} From e2606002f0c476cb9fc4c48e22a09d2bb6131686 Mon Sep 17 00:00:00 2001 From: Ben Forge <74168521+BenCheung0422@users.noreply.github.com> Date: Thu, 22 May 2025 05:05:41 +0800 Subject: [PATCH 03/19] Add Ferricia loading part --- .gitignore | 3 ++ .gitmodules | 3 ++ .idea/compiler.xml | 6 +++ .idea/kotlinc.xml | 2 +- .idea/misc.xml | 2 +- .idea/modules/TerraModulus.iml | 12 ----- .idea/modules/TerraModulus.main.iml | 31 ------------- .idea/modules/TerraModulus.test.iml | 33 -------------- .../src/client/TerraModulus.client.iml | 12 ----- .../src/common/TerraModulus.common.iml | 12 ----- .../src/server/TerraModulus.server.iml | 12 ----- .idea/vcs.xml | 3 ++ build.gradle.kts | 45 ++++++++++++++++++- src/client/kotlin/terramodulus/core/Main.kt | 12 +++++ .../kotlin/terramodulus/core/TerraModulus.kt | 4 ++ .../terramodulus/engine/ferricia/Demo.kt | 11 +++++ .../internal/platform/Kernel32.kt | 21 +++++++++ src/server/kotlin/terramodulus/core/Main.kt | 13 +++++- .../kotlin/terramodulus/core/TerraModulus.kt | 4 ++ 19 files changed, 124 insertions(+), 117 deletions(-) create mode 100644 .gitmodules create mode 100644 .idea/compiler.xml delete mode 100644 .idea/modules/TerraModulus.iml delete mode 100644 .idea/modules/TerraModulus.main.iml delete mode 100644 .idea/modules/TerraModulus.test.iml delete mode 100644 .idea/modules/src/client/TerraModulus.client.iml delete mode 100644 .idea/modules/src/common/TerraModulus.common.iml delete mode 100644 .idea/modules/src/server/TerraModulus.server.iml create mode 100644 src/common/kotlin/terramodulus/engine/ferricia/Demo.kt create mode 100644 src/common/kotlin/terramodulus/internal/platform/Kernel32.kt diff --git a/.gitignore b/.gitignore index 1117200a..8d41cd23 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,9 @@ # Mobile Tools for Java (J2ME) .mtj.tmp/ +# Submodule +/ferricia + # Package Files # *.jar *.war diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..a8bc72c1 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "ferricia"] + path = ferricia + url = https://github.com/AnvilloyDevStudio/TerraModulus-Ferricia-Engine diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 00000000..b86273d9 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml index bb449370..131e44d7 100644 --- a/.idea/kotlinc.xml +++ b/.idea/kotlinc.xml @@ -1,6 +1,6 @@ - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index ddab4a76..4b0bf0df 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -4,5 +4,5 @@ - + \ No newline at end of file diff --git a/.idea/modules/TerraModulus.iml b/.idea/modules/TerraModulus.iml deleted file mode 100644 index 2e5fc3dc..00000000 --- a/.idea/modules/TerraModulus.iml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/TerraModulus.main.iml b/.idea/modules/TerraModulus.main.iml deleted file mode 100644 index b1736c41..00000000 --- a/.idea/modules/TerraModulus.main.iml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/TerraModulus.test.iml b/.idea/modules/TerraModulus.test.iml deleted file mode 100644 index a4080e18..00000000 --- a/.idea/modules/TerraModulus.test.iml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/src/client/TerraModulus.client.iml b/.idea/modules/src/client/TerraModulus.client.iml deleted file mode 100644 index d7fc5b2d..00000000 --- a/.idea/modules/src/client/TerraModulus.client.iml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/src/common/TerraModulus.common.iml b/.idea/modules/src/common/TerraModulus.common.iml deleted file mode 100644 index ef851b4b..00000000 --- a/.idea/modules/src/common/TerraModulus.common.iml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/src/server/TerraModulus.server.iml b/.idea/modules/src/server/TerraModulus.server.iml deleted file mode 100644 index c13d567a..00000000 --- a/.idea/modules/src/server/TerraModulus.server.iml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml index 35eb1ddf..ba9deec3 100644 --- a/.idea/vcs.xml +++ b/.idea/vcs.xml @@ -2,5 +2,8 @@ + + + \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 7946c482..d9cca56a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -45,6 +45,9 @@ project(":common") { api("org.jetbrains.kotlinx:multik-default:0.2.3") api(kotlin("reflect")) api("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2") + api("com.github.oshi:oshi-core:6.8.0") + api("net.java.dev.jna:jna:5.17.0") + api("net.java.dev.jna:jna-platform:5.17.0") } } @@ -54,7 +57,7 @@ project(":client") { } application { - mainClass = "terramodulus.client.Main" + mainClass = "terramodulus.core.MainKt" } } @@ -64,6 +67,44 @@ project(":server") { } application { - mainClass = "terramodulus.server.Main" + mainClass = "terramodulus.core.MainKt" + } +} + +enum class Target { + CLIENT, SERVER; +} + +/** Build Ferricia Engine with Cargo */ +fun Exec.configureCargoBuild(target: Target) { + workingDir = rootProject.file("ferricia") + commandLine("cargo", "build") + if (project.hasProperty("release")) args("--release") // use `-Prelease=true` + args("-F") + when (target) { + Target.CLIENT -> args("client") + Target.SERVER -> args("server") + } +} + +tasks.register("run_client") { + group = "application" + description = "Run client" + finalizedBy(":client:run") + configureCargoBuild(Target.CLIENT) +} + +tasks.register("run_server") { + group = "application" + description = "Run server" + finalizedBy(":server:run") + configureCargoBuild(Target.SERVER) +} + +configure(listOf(project(":server"), project(":client"))) { + tasks.named("run") { + jvmArgs("-Djava.library.path=${rootProject.file("ferricia/target/${ + if (project.hasProperty("release")) "release" else "debug" + }").path}") } } diff --git a/src/client/kotlin/terramodulus/core/Main.kt b/src/client/kotlin/terramodulus/core/Main.kt index b7317e32..44fb1d52 100644 --- a/src/client/kotlin/terramodulus/core/Main.kt +++ b/src/client/kotlin/terramodulus/core/Main.kt @@ -5,7 +5,19 @@ package terramodulus.core +import oshi.SystemInfo +import terramodulus.engine.ferricia.Demo +import terramodulus.internal.platform.Kernel32 +import java.util.Locale + fun main() { + println("java.library.path = ${System.getProperty("java.library.path")}") + if (SystemInfo().operatingSystem.family.lowercase(Locale.getDefault()).contains("windows")) { + Kernel32.INSTANCE.SetDllDirectoryW(System.getProperty("java.library.path")) // must use backslashes + } + System.loadLibrary("ferricia") + println(Demo.hello("Ferricia")) + println(Demo.clientOnly()) val game = TerraModulus() try { game.run() diff --git a/src/client/kotlin/terramodulus/core/TerraModulus.kt b/src/client/kotlin/terramodulus/core/TerraModulus.kt index 5429e13b..8f6ccbfd 100644 --- a/src/client/kotlin/terramodulus/core/TerraModulus.kt +++ b/src/client/kotlin/terramodulus/core/TerraModulus.kt @@ -6,6 +6,10 @@ package terramodulus.core class TerraModulus : AbstractTerraModulus() { + override var tps: Int + get() = TODO("Not yet implemented") + set(value) {} + override fun run() { TODO("Not yet implemented") } diff --git a/src/common/kotlin/terramodulus/engine/ferricia/Demo.kt b/src/common/kotlin/terramodulus/engine/ferricia/Demo.kt new file mode 100644 index 00000000..62831931 --- /dev/null +++ b/src/common/kotlin/terramodulus/engine/ferricia/Demo.kt @@ -0,0 +1,11 @@ +/* + * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package terramodulus.engine.ferricia + +object Demo { + external fun hello(name: String): String + external fun clientOnly(): Int +} diff --git a/src/common/kotlin/terramodulus/internal/platform/Kernel32.kt b/src/common/kotlin/terramodulus/internal/platform/Kernel32.kt new file mode 100644 index 00000000..95e022fe --- /dev/null +++ b/src/common/kotlin/terramodulus/internal/platform/Kernel32.kt @@ -0,0 +1,21 @@ +/* + * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package terramodulus.internal.platform + +import com.sun.jna.Native +import com.sun.jna.platform.win32.WinBase +import com.sun.jna.win32.StdCallLibrary +import com.sun.jna.win32.W32APIOptions + +@Suppress("FunctionName") +interface Kernel32 : com.sun.jna.platform.win32.Kernel32, StdCallLibrary, WinBase { + // BOOL SetDllDirectoryA(LPCSTR lpPathName); + fun SetDllDirectoryW(lpPathName: String?): Boolean + + companion object { + val INSTANCE: Kernel32 = Native.load("kernel32", Kernel32::class.java, W32APIOptions.DEFAULT_OPTIONS) + } +} diff --git a/src/server/kotlin/terramodulus/core/Main.kt b/src/server/kotlin/terramodulus/core/Main.kt index d8ac2e75..c025a7c3 100644 --- a/src/server/kotlin/terramodulus/core/Main.kt +++ b/src/server/kotlin/terramodulus/core/Main.kt @@ -5,6 +5,17 @@ package terramodulus.core -fun main() { +import oshi.SystemInfo +import terramodulus.engine.ferricia.Demo +import terramodulus.internal.platform.Kernel32 +import java.util.Locale +fun main() { + println("java.library.path = ${System.getProperty("java.library.path")}") + if (SystemInfo().operatingSystem.family.lowercase(Locale.getDefault()).contains("windows")) { + Kernel32.INSTANCE.SetDllDirectoryW(System.getProperty("java.library.path")) // must use backslashes + } + System.loadLibrary("ferricia") + println(Demo.hello("Ferricia")) + println(Demo.clientOnly()) } diff --git a/src/server/kotlin/terramodulus/core/TerraModulus.kt b/src/server/kotlin/terramodulus/core/TerraModulus.kt index 5429e13b..8f6ccbfd 100644 --- a/src/server/kotlin/terramodulus/core/TerraModulus.kt +++ b/src/server/kotlin/terramodulus/core/TerraModulus.kt @@ -6,6 +6,10 @@ package terramodulus.core class TerraModulus : AbstractTerraModulus() { + override var tps: Int + get() = TODO("Not yet implemented") + set(value) {} + override fun run() { TODO("Not yet implemented") } From 1f7c96cf87043d97b0abd9bec67e71bea16c720c Mon Sep 17 00:00:00 2001 From: Ben Forge <74168521+BenCheung0422@users.noreply.github.com> Date: Sat, 24 May 2025 02:46:39 +0800 Subject: [PATCH 04/19] Setting up logging --- .idea/misc.xml | 3 +++ build.gradle.kts | 17 +++++++++---- src/client/kotlin/terramodulus/core/Main.kt | 14 ++--------- .../terramodulus/engine/ferricia/Demo.kt | 2 +- .../terramodulus/engine/ferricia/Main.kt | 16 +++++++++++++ .../internal/platform/Kernel32.kt | 7 ++++-- .../kotlin/terramodulus/util/logging/Core.kt | 11 +++++++++ .../util/logging/FileRollerPlugin.kt | 13 ++++++++++ src/common/resources/log4j2.xml | 24 +++++++++++++++++++ src/server/kotlin/terramodulus/core/Main.kt | 12 +--------- 10 files changed, 88 insertions(+), 31 deletions(-) create mode 100644 src/common/kotlin/terramodulus/engine/ferricia/Main.kt create mode 100644 src/common/kotlin/terramodulus/util/logging/Core.kt create mode 100644 src/common/kotlin/terramodulus/util/logging/FileRollerPlugin.kt create mode 100644 src/common/resources/log4j2.xml diff --git a/.idea/misc.xml b/.idea/misc.xml index 4b0bf0df..6ed74919 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -4,5 +4,8 @@ + + + \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index d9cca56a..540ef199 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -26,12 +26,12 @@ subprojects { tasks.compileKotlin { compilerOptions { - jvmTarget.set(JvmTarget.JVM_21) + jvmTarget.set(JvmTarget.JVM_17) } } kotlin { - jvmToolchain(21) + jvmToolchain(17) } } @@ -45,9 +45,16 @@ project(":common") { api("org.jetbrains.kotlinx:multik-default:0.2.3") api(kotlin("reflect")) api("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2") - api("com.github.oshi:oshi-core:6.8.0") - api("net.java.dev.jna:jna:5.17.0") - api("net.java.dev.jna:jna-platform:5.17.0") + implementation("com.github.oshi:oshi-core:6.8.0") + implementation("net.java.dev.jna:jna:5.17.0") + implementation("net.java.dev.jna:jna-platform:5.17.0") + api("com.google.errorprone:error_prone_annotations:2.38.0") + implementation("org.apache.logging.log4j:log4j-core:2.24.3") + implementation("org.apache.logging.log4j:log4j-api:2.24.3") + implementation("org.apache.logging.log4j:log4j-slf4j2-impl:2.24.3") + implementation(platform("org.apache.logging.log4j:log4j-bom:2.24.3")) + annotationProcessor("org.apache.logging.log4j:log4j-core:2.24.3") + implementation("io.github.oshai:kotlin-logging-jvm:7.0.3") } } diff --git a/src/client/kotlin/terramodulus/core/Main.kt b/src/client/kotlin/terramodulus/core/Main.kt index 44fb1d52..6d6e245c 100644 --- a/src/client/kotlin/terramodulus/core/Main.kt +++ b/src/client/kotlin/terramodulus/core/Main.kt @@ -5,19 +5,9 @@ package terramodulus.core -import oshi.SystemInfo -import terramodulus.engine.ferricia.Demo -import terramodulus.internal.platform.Kernel32 -import java.util.Locale - -fun main() { +fun main(args: Array) { println("java.library.path = ${System.getProperty("java.library.path")}") - if (SystemInfo().operatingSystem.family.lowercase(Locale.getDefault()).contains("windows")) { - Kernel32.INSTANCE.SetDllDirectoryW(System.getProperty("java.library.path")) // must use backslashes - } - System.loadLibrary("ferricia") - println(Demo.hello("Ferricia")) - println(Demo.clientOnly()) + val game = TerraModulus() try { game.run() diff --git a/src/common/kotlin/terramodulus/engine/ferricia/Demo.kt b/src/common/kotlin/terramodulus/engine/ferricia/Demo.kt index 62831931..8d7f8de5 100644 --- a/src/common/kotlin/terramodulus/engine/ferricia/Demo.kt +++ b/src/common/kotlin/terramodulus/engine/ferricia/Demo.kt @@ -5,7 +5,7 @@ package terramodulus.engine.ferricia -object Demo { +internal object Demo { external fun hello(name: String): String external fun clientOnly(): Int } diff --git a/src/common/kotlin/terramodulus/engine/ferricia/Main.kt b/src/common/kotlin/terramodulus/engine/ferricia/Main.kt new file mode 100644 index 00000000..9bb41e1f --- /dev/null +++ b/src/common/kotlin/terramodulus/engine/ferricia/Main.kt @@ -0,0 +1,16 @@ +/* + * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package terramodulus.engine.ferricia + +import terramodulus.internal.platform.Kernel32 + +fun loadLibrary() { + if (Kernel32.INSTANCE != null) { + Kernel32.INSTANCE.SetDllDirectoryW(System.getProperty("java.library.path")) // must use backslashes + } + System.loadLibrary("ferricia") + +} diff --git a/src/common/kotlin/terramodulus/internal/platform/Kernel32.kt b/src/common/kotlin/terramodulus/internal/platform/Kernel32.kt index 95e022fe..17a0e6dc 100644 --- a/src/common/kotlin/terramodulus/internal/platform/Kernel32.kt +++ b/src/common/kotlin/terramodulus/internal/platform/Kernel32.kt @@ -6,16 +6,19 @@ package terramodulus.internal.platform import com.sun.jna.Native +import com.sun.jna.Platform import com.sun.jna.platform.win32.WinBase import com.sun.jna.win32.StdCallLibrary import com.sun.jna.win32.W32APIOptions @Suppress("FunctionName") interface Kernel32 : com.sun.jna.platform.win32.Kernel32, StdCallLibrary, WinBase { - // BOOL SetDllDirectoryA(LPCSTR lpPathName); + // BOOL SetDllDirectoryW(LPCWSTR lpPathName); fun SetDllDirectoryW(lpPathName: String?): Boolean companion object { - val INSTANCE: Kernel32 = Native.load("kernel32", Kernel32::class.java, W32APIOptions.DEFAULT_OPTIONS) + val INSTANCE: Kernel32? = + if (Platform.isWindows()) Native.load("kernel32", Kernel32::class.java, W32APIOptions.DEFAULT_OPTIONS) + else null; // without relying on classloading } } diff --git a/src/common/kotlin/terramodulus/util/logging/Core.kt b/src/common/kotlin/terramodulus/util/logging/Core.kt new file mode 100644 index 00000000..0b7b4fb9 --- /dev/null +++ b/src/common/kotlin/terramodulus/util/logging/Core.kt @@ -0,0 +1,11 @@ +/* + * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package terramodulus.util.logging + +import io.github.oshai.kotlinlogging.KLogger +import io.github.oshai.kotlinlogging.KotlinLogging + +fun logger(func: () -> Unit): KLogger = KotlinLogging.logger(func) diff --git a/src/common/kotlin/terramodulus/util/logging/FileRollerPlugin.kt b/src/common/kotlin/terramodulus/util/logging/FileRollerPlugin.kt new file mode 100644 index 00000000..0aceb1f3 --- /dev/null +++ b/src/common/kotlin/terramodulus/util/logging/FileRollerPlugin.kt @@ -0,0 +1,13 @@ +/* + * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package terramodulus.util.logging + +import org.apache.logging.log4j.core.config.Node +import org.apache.logging.log4j.core.config.plugins.Plugin + +@Plugin(name = "file-roller", category = Node.CATEGORY) +class FileRollerPlugin { +} diff --git a/src/common/resources/log4j2.xml b/src/common/resources/log4j2.xml new file mode 100644 index 00000000..5db14f31 --- /dev/null +++ b/src/common/resources/log4j2.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/server/kotlin/terramodulus/core/Main.kt b/src/server/kotlin/terramodulus/core/Main.kt index c025a7c3..358a956a 100644 --- a/src/server/kotlin/terramodulus/core/Main.kt +++ b/src/server/kotlin/terramodulus/core/Main.kt @@ -5,17 +5,7 @@ package terramodulus.core -import oshi.SystemInfo -import terramodulus.engine.ferricia.Demo -import terramodulus.internal.platform.Kernel32 -import java.util.Locale - fun main() { println("java.library.path = ${System.getProperty("java.library.path")}") - if (SystemInfo().operatingSystem.family.lowercase(Locale.getDefault()).contains("windows")) { - Kernel32.INSTANCE.SetDllDirectoryW(System.getProperty("java.library.path")) // must use backslashes - } - System.loadLibrary("ferricia") - println(Demo.hello("Ferricia")) - println(Demo.clientOnly()) + } From 412261a5a62baf36f90c2190f7a26803851f46b6 Mon Sep 17 00:00:00 2001 From: Ben Forge <74168521+BenCheung0422@users.noreply.github.com> Date: Sun, 25 May 2025 03:49:40 +0800 Subject: [PATCH 05/19] Extend and disable file logging --- .../kotlin/terramodulus/core/Constants.kt | 9 -- src/common/kotlin/terramodulus/core/Core.kt | 50 +++++++ .../engine/ferricia/{Main.kt => Core.kt} | 0 .../util/logging/DelayedFileAppender.kt | 124 ++++++++++++++++++ .../util/logging/FileRollerPlugin.kt | 13 -- .../kotlin/terramodulus/util/logging/State.kt | 16 +++ src/common/resources/log4j2.xml | 6 +- 7 files changed, 193 insertions(+), 25 deletions(-) delete mode 100644 src/common/kotlin/terramodulus/core/Constants.kt create mode 100644 src/common/kotlin/terramodulus/core/Core.kt rename src/common/kotlin/terramodulus/engine/ferricia/{Main.kt => Core.kt} (100%) create mode 100644 src/common/kotlin/terramodulus/util/logging/DelayedFileAppender.kt delete mode 100644 src/common/kotlin/terramodulus/util/logging/FileRollerPlugin.kt create mode 100644 src/common/kotlin/terramodulus/util/logging/State.kt diff --git a/src/common/kotlin/terramodulus/core/Constants.kt b/src/common/kotlin/terramodulus/core/Constants.kt deleted file mode 100644 index 46dcb456..00000000 --- a/src/common/kotlin/terramodulus/core/Constants.kt +++ /dev/null @@ -1,9 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors - * SPDX-License-Identifier: LGPL-3.0-only - */ - -package terramodulus.core - -const val NAME = "TerraModulus" -const val VERSION = "0.1.0" // TODO placeholder diff --git a/src/common/kotlin/terramodulus/core/Core.kt b/src/common/kotlin/terramodulus/core/Core.kt new file mode 100644 index 00000000..b3d04256 --- /dev/null +++ b/src/common/kotlin/terramodulus/core/Core.kt @@ -0,0 +1,50 @@ +/* + * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package terramodulus.core + +import terramodulus.util.logging.logger +import java.io.Closeable +import java.io.File +import java.io.RandomAccessFile +import java.nio.channels.FileChannel +import java.nio.channels.FileLock +import java.nio.file.Files +import java.nio.file.Path + +const val NAME = "TerraModulus" +const val VERSION = "0.1.0" // TODO placeholder + +private val logger = logger {} + +/** Checks whether this instance is the first process launched in the game data directory. */ +fun checkSingleInstance(dir: Path) { + if (dir.parent != null) Files.createDirectories(dir.parent) + val lockFile = File(dir.toFile(), "session.lock") + if (lockFile.createNewFile()) { + logger.info { "LOCK file is created." } + } else { + logger.info { "LOCK file exists." } + } + + SessionLockFile(RandomAccessFile(lockFile, "rw")) +} + +/** + * @throws java.io.IOException + */ +class SessionLockFile internal constructor(private val file: RandomAccessFile) : Closeable { + private val channel: FileChannel = file.channel + private val lock: FileLock = channel.lock() + + /** + * @throws java.io.IOException + */ + override fun close() { + lock.close() + channel.close() + file.close() + } +} diff --git a/src/common/kotlin/terramodulus/engine/ferricia/Main.kt b/src/common/kotlin/terramodulus/engine/ferricia/Core.kt similarity index 100% rename from src/common/kotlin/terramodulus/engine/ferricia/Main.kt rename to src/common/kotlin/terramodulus/engine/ferricia/Core.kt diff --git a/src/common/kotlin/terramodulus/util/logging/DelayedFileAppender.kt b/src/common/kotlin/terramodulus/util/logging/DelayedFileAppender.kt new file mode 100644 index 00000000..76a47b32 --- /dev/null +++ b/src/common/kotlin/terramodulus/util/logging/DelayedFileAppender.kt @@ -0,0 +1,124 @@ +/* + * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package terramodulus.util.logging + +import org.apache.logging.log4j.core.Appender +import org.apache.logging.log4j.core.Layout +import org.apache.logging.log4j.core.LogEvent +import org.apache.logging.log4j.core.appender.AbstractAppender +import org.apache.logging.log4j.core.appender.MemoryMappedFileAppender +import org.apache.logging.log4j.core.config.Configuration +import org.apache.logging.log4j.core.config.Node +import org.apache.logging.log4j.core.config.Property +import org.apache.logging.log4j.core.config.plugins.Plugin +import org.apache.logging.log4j.core.config.plugins.PluginAttribute +import org.apache.logging.log4j.core.config.plugins.PluginConfiguration +import org.apache.logging.log4j.core.config.plugins.PluginElement +import org.apache.logging.log4j.core.config.plugins.PluginFactory +import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required +import java.io.Serializable +import java.lang.management.ManagementFactory +import java.text.SimpleDateFormat +import java.util.Date +import java.util.concurrent.TimeUnit + +/** + * A wrapper over the underlying [MemoryMappedFileAppender] with the interoperation with single-instance checking. + * + * Note that this is not ready for generic public utility interface since some bridges are ignored + * due to the internal usage of this [Appender]. This includes features related to asynchronicity and filters. + */ +@Suppress("unused") +@Plugin(name = "DelayedFile", category = Node.CATEGORY, elementType = Appender.ELEMENT_TYPE, printObject = true) +internal class DelayedFileAppender private constructor( + name: String, + layout: Layout?, + config: Configuration, +) : AbstractAppender(name, null, layout, true, Property.EMPTY_ARRAY) { + private var session: Session = Session.Initial(DataFactory(name, layout, config)) + + init { + State.appender() + } + + /** + * With bridges of Life Cycle from + * [AbstractOutputStreamAppender][org.apache.logging.log4j.core.appender.AbstractOutputStreamAppender] + */ + private sealed interface Session { + /** @see org.apache.logging.log4j.core.appender.AbstractOutputStreamAppender.start */ + fun start() + + /** @see org.apache.logging.log4j.core.appender.AbstractOutputStreamAppender.stop */ + fun stop(timeout: Long, timeUnit: TimeUnit?): Boolean + + /** @see org.apache.logging.log4j.core.appender.AbstractOutputStreamAppender.append */ + fun append(event: LogEvent) + + /** + * The initial state of the appender, which has not been initialized for the process. + * + * Life cycle is not applicable here, so NO-OP is applied for relevant functions. + * All [LogEvent] instances passed to this appender are stored temporary until the + * appender is initialized, and all stored events would be passed immediately afterward. + */ + class Initial(val data: DataFactory) : Session { + private val events: MutableList = ArrayList() + + fun buildFinal(): Final { + val timestamp = Date(ManagementFactory.getRuntimeMXBean().startTime) + val format = SimpleDateFormat("yyyy-MM-dd-HH-mm-ss-SSS") + val final = Final(MemoryMappedFileAppender.Builder() + .setName(data.name) + .setLayout(data.layout) + .setConfiguration(data.config) + .setFileName("${format.format(timestamp)}.log") + .build()) + events.forEach(final::append) + return final + } + + override fun start() {} + + override fun stop(timeout: Long, timeUnit: TimeUnit?): Boolean = true + + override fun append(event: LogEvent) { + events.add(event) + } + } + + /** The initialized state of the appender, ready for use. */ + class Final(val fileAppender: MemoryMappedFileAppender) : Session { + override fun start() = fileAppender.start() + + override fun stop(timeout: Long, timeUnit: TimeUnit?): Boolean = fileAppender.stop(timeout, timeUnit) + + override fun append(event: LogEvent) = fileAppender.append(event) + } + } + + private class DataFactory( + val name: String, + val layout: Layout?, + val config: Configuration, + ) + + companion object { + @PluginFactory + @JvmStatic + fun createAppender( + @PluginConfiguration config: Configuration, + @PluginAttribute("name") @Required name: String, + @PluginElement("Layout") layout: Layout?, + ): Appender = DelayedFileAppender(name, layout, config) + } + + override fun start() = super.start() + + override fun stop(timeout: Long, timeUnit: TimeUnit?): Boolean = super.stop(timeout, timeUnit) + + override fun append(event: LogEvent) = session.append(event) +} diff --git a/src/common/kotlin/terramodulus/util/logging/FileRollerPlugin.kt b/src/common/kotlin/terramodulus/util/logging/FileRollerPlugin.kt deleted file mode 100644 index 0aceb1f3..00000000 --- a/src/common/kotlin/terramodulus/util/logging/FileRollerPlugin.kt +++ /dev/null @@ -1,13 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors - * SPDX-License-Identifier: LGPL-3.0-only - */ - -package terramodulus.util.logging - -import org.apache.logging.log4j.core.config.Node -import org.apache.logging.log4j.core.config.plugins.Plugin - -@Plugin(name = "file-roller", category = Node.CATEGORY) -class FileRollerPlugin { -} diff --git a/src/common/kotlin/terramodulus/util/logging/State.kt b/src/common/kotlin/terramodulus/util/logging/State.kt new file mode 100644 index 00000000..cd0daed9 --- /dev/null +++ b/src/common/kotlin/terramodulus/util/logging/State.kt @@ -0,0 +1,16 @@ +/* + * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package terramodulus.util.logging + +internal object State { + fun appender() { + logger {}.info { "Appender" } + } + + fun core() { + logger {}.info { "Core" } + } +} diff --git a/src/common/resources/log4j2.xml b/src/common/resources/log4j2.xml index 5db14f31..46386a21 100644 --- a/src/common/resources/log4j2.xml +++ b/src/common/resources/log4j2.xml @@ -11,9 +11,9 @@ - - - + + + From e5aace2326933bc5bcdede4d5e0cff5f167f762b Mon Sep 17 00:00:00 2001 From: Ben Forge <74168521+BenCheung0422@users.noreply.github.com> Date: Wed, 28 May 2025 05:26:39 +0800 Subject: [PATCH 06/19] EEMS: Add basic implementation skeleton EEMS: Exception and Error Management System Add most basic abstract classes and empty top level functions --- .../util/exception/CodeLogicFault.kt | 18 +++++++ .../terramodulus/util/exception/Core.kt | 52 +++++++++++++++++++ .../terramodulus/util/exception/Error.kt | 10 ++++ .../terramodulus/util/exception/Exception.kt | 10 ++++ .../terramodulus/util/exception/Failure.kt | 10 ++++ .../terramodulus/util/exception/Fault.kt | 10 ++++ .../util/exception/package-info.java | 10 ++++ 7 files changed, 120 insertions(+) create mode 100644 src/common/kotlin/terramodulus/util/exception/CodeLogicFault.kt create mode 100644 src/common/kotlin/terramodulus/util/exception/Core.kt create mode 100644 src/common/kotlin/terramodulus/util/exception/Error.kt create mode 100644 src/common/kotlin/terramodulus/util/exception/Exception.kt create mode 100644 src/common/kotlin/terramodulus/util/exception/Failure.kt create mode 100644 src/common/kotlin/terramodulus/util/exception/Fault.kt create mode 100644 src/common/kotlin/terramodulus/util/exception/package-info.java diff --git a/src/common/kotlin/terramodulus/util/exception/CodeLogicFault.kt b/src/common/kotlin/terramodulus/util/exception/CodeLogicFault.kt new file mode 100644 index 00000000..4a23f555 --- /dev/null +++ b/src/common/kotlin/terramodulus/util/exception/CodeLogicFault.kt @@ -0,0 +1,18 @@ +/* + * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package terramodulus.util.exception + +/** + * Code logic problems or bugs are regarded as defects in the software code and thus + * should be emitted as [Fault]s. + * + * @param cause accepts [IllegalArgumentException], [IllegalStateException], [IndexOutOfBoundsException], + * [ArithmeticException], [NumberFormatException], [NullPointerException], [AssertionError], + * [ClassCastException], [NoSuchElementException], [ConcurrentModificationException] + */ +class CodeLogicFault(override val message: String, override val cause: Throwable?) : Fault(message, cause) { + constructor(message: String) : this(message, null) +} diff --git a/src/common/kotlin/terramodulus/util/exception/Core.kt b/src/common/kotlin/terramodulus/util/exception/Core.kt new file mode 100644 index 00000000..cd58d19d --- /dev/null +++ b/src/common/kotlin/terramodulus/util/exception/Core.kt @@ -0,0 +1,52 @@ +/* + * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package terramodulus.util.exception + +import terramodulus.util.logging.logger + +private val logger = logger {} + +/** + * Records the recovered [exception] to logger. + * This logs the `exception` as a warning. + */ +fun recordException(exception: Exception) { + +} + +/** + * Pushes the recovered [exception] to application notification and records it to logger. + * This logs the `exception` as a warning. + */ +fun notifyAndRecordException(exception: Exception) { + // TODO exception notification + recordException(exception) +} + +/** + * Records the suppressed [error] to logger. + * This logs the `exception` as an error. + */ +fun recordError(error: Error) { + +} + +/** + * Pushes the suppressed [error] to application notification and records it to logger. + * This logs the `error` as an error. + */ +fun notifyAndRecordError(error: Error) { + // TODO exception notification + recordError(error) +} + +fun triggerSessionCrash() { + +} + +fun triggerGlobalCrash() { + +} diff --git a/src/common/kotlin/terramodulus/util/exception/Error.kt b/src/common/kotlin/terramodulus/util/exception/Error.kt new file mode 100644 index 00000000..8539effb --- /dev/null +++ b/src/common/kotlin/terramodulus/util/exception/Error.kt @@ -0,0 +1,10 @@ +/* + * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package terramodulus.util.exception + +abstract class Error(override val message: String, override val cause: Throwable?) : Exception(message, cause) { + constructor(message: String) : this(message, null) +} diff --git a/src/common/kotlin/terramodulus/util/exception/Exception.kt b/src/common/kotlin/terramodulus/util/exception/Exception.kt new file mode 100644 index 00000000..947c9fff --- /dev/null +++ b/src/common/kotlin/terramodulus/util/exception/Exception.kt @@ -0,0 +1,10 @@ +/* + * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package terramodulus.util.exception + +abstract class Exception(override val message: String, override val cause: Throwable?) : Throwable(message, cause) { + constructor(message: String) : this(message, null) +} diff --git a/src/common/kotlin/terramodulus/util/exception/Failure.kt b/src/common/kotlin/terramodulus/util/exception/Failure.kt new file mode 100644 index 00000000..c918f712 --- /dev/null +++ b/src/common/kotlin/terramodulus/util/exception/Failure.kt @@ -0,0 +1,10 @@ +/* + * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package terramodulus.util.exception + +abstract class Failure(override val message: String, override val cause: Throwable?) : Exception(message, cause) { + constructor(message: String) : this(message, null) +} diff --git a/src/common/kotlin/terramodulus/util/exception/Fault.kt b/src/common/kotlin/terramodulus/util/exception/Fault.kt new file mode 100644 index 00000000..a4f578cf --- /dev/null +++ b/src/common/kotlin/terramodulus/util/exception/Fault.kt @@ -0,0 +1,10 @@ +/* + * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package terramodulus.util.exception + +abstract class Fault(override val message: String, override val cause: Throwable?) : Error(message, cause) { + constructor(message: String) : this(message, null) +} diff --git a/src/common/kotlin/terramodulus/util/exception/package-info.java b/src/common/kotlin/terramodulus/util/exception/package-info.java new file mode 100644 index 00000000..2299de90 --- /dev/null +++ b/src/common/kotlin/terramodulus/util/exception/package-info.java @@ -0,0 +1,10 @@ +/* + * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +/** + * This package refers to EFP 7 + * for implementation and standardization information. + */ +package terramodulus.util.exception; From b4a5361119b9b2872eb768c97aae5eee7515cc64 Mon Sep 17 00:00:00 2001 From: Ben Forge <74168521+BenCheung0422@users.noreply.github.com> Date: Thu, 29 May 2025 01:02:54 +0800 Subject: [PATCH 07/19] EEMS: Add little more API --- src/client/kotlin/terramodulus/core/Main.kt | 6 ++++++ .../util/exception/CodeLogicFault.kt | 9 +++++---- .../util/exception/UnhandledExceptionFault.kt | 16 ++++++++++++++++ 3 files changed, 27 insertions(+), 4 deletions(-) create mode 100644 src/common/kotlin/terramodulus/util/exception/UnhandledExceptionFault.kt diff --git a/src/client/kotlin/terramodulus/core/Main.kt b/src/client/kotlin/terramodulus/core/Main.kt index 6d6e245c..a8bdb83f 100644 --- a/src/client/kotlin/terramodulus/core/Main.kt +++ b/src/client/kotlin/terramodulus/core/Main.kt @@ -5,7 +5,13 @@ package terramodulus.core +import terramodulus.util.exception.UnhandledExceptionFault + fun main(args: Array) { + Thread.setDefaultUncaughtExceptionHandler { _, e -> + // TODO find a way to add fun to commonize this part for client and server `main` + UnhandledExceptionFault.global(e) + } println("java.library.path = ${System.getProperty("java.library.path")}") val game = TerraModulus() diff --git a/src/common/kotlin/terramodulus/util/exception/CodeLogicFault.kt b/src/common/kotlin/terramodulus/util/exception/CodeLogicFault.kt index 4a23f555..ff8bdb66 100644 --- a/src/common/kotlin/terramodulus/util/exception/CodeLogicFault.kt +++ b/src/common/kotlin/terramodulus/util/exception/CodeLogicFault.kt @@ -7,11 +7,12 @@ package terramodulus.util.exception /** * Code logic problems or bugs are regarded as defects in the software code and thus - * should be emitted as [Fault]s. + * should be emitted as [Fault]s. This alerts that a made assumption or assertion was false. * - * @param cause accepts [IllegalArgumentException], [IllegalStateException], [IndexOutOfBoundsException], - * [ArithmeticException], [NumberFormatException], [NullPointerException], [AssertionError], - * [ClassCastException], [NoSuchElementException], [ConcurrentModificationException] + * @param cause generally accepts [IllegalArgumentException], [IllegalStateException], + * [IndexOutOfBoundsException], [ArithmeticException], [NumberFormatException], + * [NullPointerException], [AssertionError], [ClassCastException], [NoSuchElementException], + * [ConcurrentModificationException], [ArrayStoreException], etc. */ class CodeLogicFault(override val message: String, override val cause: Throwable?) : Fault(message, cause) { constructor(message: String) : this(message, null) diff --git a/src/common/kotlin/terramodulus/util/exception/UnhandledExceptionFault.kt b/src/common/kotlin/terramodulus/util/exception/UnhandledExceptionFault.kt new file mode 100644 index 00000000..90fcfa76 --- /dev/null +++ b/src/common/kotlin/terramodulus/util/exception/UnhandledExceptionFault.kt @@ -0,0 +1,16 @@ +/* + * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package terramodulus.util.exception + +class UnhandledExceptionFault private constructor(message: String, cause: Throwable) : Fault(message, cause) { + companion object { + fun global(cause: Throwable) = + UnhandledExceptionFault("Unknown unhandled exception", cause) + + fun scoped(scope: String, cause: Throwable) = + UnhandledExceptionFault("Unknown unhandled exception in $scope", cause) + } +} From 983b886cdf6ccbfeb31f681fd39ce25ad85d7805 Mon Sep 17 00:00:00 2001 From: Ben Forge <74168521+BenCheung0422@users.noreply.github.com> Date: Thu, 29 May 2025 01:34:48 +0800 Subject: [PATCH 08/19] Project: Split `common` to `common`, `kernel` and `internal` Specs will be exposed later --- build.gradle.kts | 23 ++++++++++++++++--- settings.gradle.kts | 2 +- src/client/kotlin/terramodulus/core/Main.kt | 7 ++---- .../kotlin/terramodulus/core/TerraModulus.kt | 2 ++ .../terramodulus/engine/ferricia/Core.kt | 0 .../terramodulus/engine/ferricia/Demo.kt | 0 .../internal/platform/Kernel32.kt | 0 .../common}/core/AbstractTerraModulus.kt | 2 +- .../kotlin/terramodulus/common/core/Core.kt | 17 ++++++++++++++ src/server/kotlin/terramodulus/core/Main.kt | 3 +++ .../kotlin/terramodulus/core/TerraModulus.kt | 2 ++ 11 files changed, 48 insertions(+), 10 deletions(-) rename src/{common => internal}/kotlin/terramodulus/engine/ferricia/Core.kt (100%) rename src/{common => internal}/kotlin/terramodulus/engine/ferricia/Demo.kt (100%) rename src/{common => internal}/kotlin/terramodulus/internal/platform/Kernel32.kt (100%) rename src/{common/kotlin/terramodulus => kernel/kotlin/terramodulus/common}/core/AbstractTerraModulus.kt (85%) create mode 100644 src/kernel/kotlin/terramodulus/common/core/Core.kt diff --git a/build.gradle.kts b/build.gradle.kts index 540ef199..6b04ac27 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -7,9 +7,12 @@ plugins { application } +configure(listOf(project(":server"), project(":client"))) { + apply(plugin = "application") +} + allprojects { apply(plugin = "org.jetbrains.kotlin.jvm") - apply(plugin = "application") version = "0.1.0" @@ -35,8 +38,22 @@ subprojects { } } +project(":kernel") { + dependencies { + implementation(project(":common")) + } +} + +project(":internal") { + dependencies { + implementation("net.java.dev.jna:jna:5.17.0") + implementation("net.java.dev.jna:jna-platform:5.17.0") + } +} + project(":common") { dependencies { + implementation(project(":internal")) api("org.jetbrains:annotations:26.0.2") api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.8.1") api("org.jetbrains.kotlinx:kotlinx-io-core:0.7.0") @@ -46,8 +63,6 @@ project(":common") { api(kotlin("reflect")) api("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2") implementation("com.github.oshi:oshi-core:6.8.0") - implementation("net.java.dev.jna:jna:5.17.0") - implementation("net.java.dev.jna:jna-platform:5.17.0") api("com.google.errorprone:error_prone_annotations:2.38.0") implementation("org.apache.logging.log4j:log4j-core:2.24.3") implementation("org.apache.logging.log4j:log4j-api:2.24.3") @@ -60,6 +75,7 @@ project(":common") { project(":client") { dependencies { + implementation(project(":kernel")) api(project(":common")) } @@ -70,6 +86,7 @@ project(":client") { project(":server") { dependencies { + implementation(project(":kernel")) api(project(":common")) } diff --git a/settings.gradle.kts b/settings.gradle.kts index 9ec2771e..2120b99a 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -11,7 +11,7 @@ plugins { } rootProject.name = "TerraModulus" -include("common", "client", "server") +include("common", "internal", "kernel", "client", "server") rootProject.children.forEach { it.projectDir = File(settingsDir, "src/${it.name}") } diff --git a/src/client/kotlin/terramodulus/core/Main.kt b/src/client/kotlin/terramodulus/core/Main.kt index a8bdb83f..04c3d84c 100644 --- a/src/client/kotlin/terramodulus/core/Main.kt +++ b/src/client/kotlin/terramodulus/core/Main.kt @@ -5,13 +5,10 @@ package terramodulus.core -import terramodulus.util.exception.UnhandledExceptionFault +import terramodulus.common.core.setupInit fun main(args: Array) { - Thread.setDefaultUncaughtExceptionHandler { _, e -> - // TODO find a way to add fun to commonize this part for client and server `main` - UnhandledExceptionFault.global(e) - } + setupInit() println("java.library.path = ${System.getProperty("java.library.path")}") val game = TerraModulus() diff --git a/src/client/kotlin/terramodulus/core/TerraModulus.kt b/src/client/kotlin/terramodulus/core/TerraModulus.kt index 8f6ccbfd..dca84520 100644 --- a/src/client/kotlin/terramodulus/core/TerraModulus.kt +++ b/src/client/kotlin/terramodulus/core/TerraModulus.kt @@ -5,6 +5,8 @@ package terramodulus.core +import terramodulus.common.core.AbstractTerraModulus + class TerraModulus : AbstractTerraModulus() { override var tps: Int get() = TODO("Not yet implemented") diff --git a/src/common/kotlin/terramodulus/engine/ferricia/Core.kt b/src/internal/kotlin/terramodulus/engine/ferricia/Core.kt similarity index 100% rename from src/common/kotlin/terramodulus/engine/ferricia/Core.kt rename to src/internal/kotlin/terramodulus/engine/ferricia/Core.kt diff --git a/src/common/kotlin/terramodulus/engine/ferricia/Demo.kt b/src/internal/kotlin/terramodulus/engine/ferricia/Demo.kt similarity index 100% rename from src/common/kotlin/terramodulus/engine/ferricia/Demo.kt rename to src/internal/kotlin/terramodulus/engine/ferricia/Demo.kt diff --git a/src/common/kotlin/terramodulus/internal/platform/Kernel32.kt b/src/internal/kotlin/terramodulus/internal/platform/Kernel32.kt similarity index 100% rename from src/common/kotlin/terramodulus/internal/platform/Kernel32.kt rename to src/internal/kotlin/terramodulus/internal/platform/Kernel32.kt diff --git a/src/common/kotlin/terramodulus/core/AbstractTerraModulus.kt b/src/kernel/kotlin/terramodulus/common/core/AbstractTerraModulus.kt similarity index 85% rename from src/common/kotlin/terramodulus/core/AbstractTerraModulus.kt rename to src/kernel/kotlin/terramodulus/common/core/AbstractTerraModulus.kt index 7b3d232f..f86bf8ab 100644 --- a/src/common/kotlin/terramodulus/core/AbstractTerraModulus.kt +++ b/src/kernel/kotlin/terramodulus/common/core/AbstractTerraModulus.kt @@ -3,7 +3,7 @@ * SPDX-License-Identifier: LGPL-3.0-only */ -package terramodulus.core +package terramodulus.common.core abstract class AbstractTerraModulus { abstract var tps: Int diff --git a/src/kernel/kotlin/terramodulus/common/core/Core.kt b/src/kernel/kotlin/terramodulus/common/core/Core.kt new file mode 100644 index 00000000..b32cab82 --- /dev/null +++ b/src/kernel/kotlin/terramodulus/common/core/Core.kt @@ -0,0 +1,17 @@ +/* + * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package terramodulus.common.core + +import terramodulus.util.exception.UnhandledExceptionFault + +/** + * This should only be used by `terramodulus.core.Main` in the beginning of `main` function. + */ +fun setupInit() { + Thread.setDefaultUncaughtExceptionHandler { _, e -> + UnhandledExceptionFault.global(e) + } +} diff --git a/src/server/kotlin/terramodulus/core/Main.kt b/src/server/kotlin/terramodulus/core/Main.kt index 358a956a..f75d7c69 100644 --- a/src/server/kotlin/terramodulus/core/Main.kt +++ b/src/server/kotlin/terramodulus/core/Main.kt @@ -5,7 +5,10 @@ package terramodulus.core +import terramodulus.common.core.setupInit + fun main() { + setupInit() println("java.library.path = ${System.getProperty("java.library.path")}") } diff --git a/src/server/kotlin/terramodulus/core/TerraModulus.kt b/src/server/kotlin/terramodulus/core/TerraModulus.kt index 8f6ccbfd..dca84520 100644 --- a/src/server/kotlin/terramodulus/core/TerraModulus.kt +++ b/src/server/kotlin/terramodulus/core/TerraModulus.kt @@ -5,6 +5,8 @@ package terramodulus.core +import terramodulus.common.core.AbstractTerraModulus + class TerraModulus : AbstractTerraModulus() { override var tps: Int get() = TODO("Not yet implemented") From c42b17ccf68459153eee6f034b5bcc431d2de4c7 Mon Sep 17 00:00:00 2001 From: Ben Forge <74168521+BenCheung0422@users.noreply.github.com> Date: Thu, 29 May 2025 07:14:57 +0800 Subject: [PATCH 09/19] MainKt: Add args parsing for client Many parts are still not yet decided, so this is still kind of partially implemented --- build.gradle.kts | 2 + src/client/kotlin/terramodulus/core/Main.kt | 112 +++++++++++++++++- src/common/kotlin/terramodulus/core/Core.kt | 41 ------- .../util/exception/CodeLogicFault.kt | 4 +- .../terramodulus/util/exception/Core.kt | 6 +- .../common/core/AbstractTerraModulus.kt | 4 +- .../kotlin/terramodulus/common/core/Core.kt | 70 ++++++++++- src/server/kotlin/terramodulus/core/Main.kt | 8 +- .../kotlin/terramodulus/core/TerraModulus.kt | 4 + 9 files changed, 195 insertions(+), 56 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 6b04ac27..a9f99453 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -77,6 +77,7 @@ project(":client") { dependencies { implementation(project(":kernel")) api(project(":common")) + implementation("net.sf.jopt-simple:jopt-simple:5.0.4") } application { @@ -88,6 +89,7 @@ project(":server") { dependencies { implementation(project(":kernel")) api(project(":common")) + implementation("net.sf.jopt-simple:jopt-simple:5.0.4") } application { diff --git a/src/client/kotlin/terramodulus/core/Main.kt b/src/client/kotlin/terramodulus/core/Main.kt index 04c3d84c..4dcb9c87 100644 --- a/src/client/kotlin/terramodulus/core/Main.kt +++ b/src/client/kotlin/terramodulus/core/Main.kt @@ -5,16 +5,116 @@ package terramodulus.core +import jdk.internal.joptsimple.OptionException +import jdk.internal.joptsimple.OptionParser +import jdk.internal.joptsimple.OptionSet +import jdk.internal.joptsimple.OptionSpec +import jdk.internal.joptsimple.ValueConversionException +import jdk.internal.joptsimple.ValueConverter +import terramodulus.common.core.ApplicationArgumentParsingError +import terramodulus.common.core.ApplicationInitializationFault +import terramodulus.common.core.run import terramodulus.common.core.setupInit +import terramodulus.util.exception.CodeLogicFault +import terramodulus.util.exception.triggerGlobalCrash +import java.awt.Dimension +import java.nio.file.InvalidPathException +import java.nio.file.Path fun main(args: Array) { setupInit() - println("java.library.path = ${System.getProperty("java.library.path")}") - - val game = TerraModulus() try { - game.run() - } catch (e: Exception) { - TODO("Not yet implemented") + parseArgs(args) + } catch (e: ApplicationArgumentParsingError) { + triggerGlobalCrash(ApplicationInitializationFault(e)) + } + run(TerraModulus()) +} + +/** + * Note that help message is not implemented here since it is not used and + * should not be accessible normally. + * Some features are then not used due to this, including but not limited to: + * - [ValueConverter.valuePattern] + * - [jdk.internal.joptsimple.HelpFormatter] + * - [OptionParser.printHelpOn] + * + * @throws ApplicationArgumentParsingError + */ +fun parseArgs(args: Array) { + val parser = OptionParser() + val options = ArgumentOptions(parser) + @Suppress("NAME_SHADOWING") + val args = try { + Arguments(options, parser.parse(*args)) + } catch (e: OptionException) { + throw ApplicationArgumentParsingError(e) + } catch (e: ClassCastException) { + triggerGlobalCrash(CodeLogicFault(e)) } } + +private object PathConverter : ValueConverter { + override fun convert(value: String): Path = try { + Path.of(value) + } catch (e: InvalidPathException) { + throw ValueConversionException("Invalid path", e) + } + + override fun valueType(): Class = Path::class.java + + override fun valuePattern(): String? = null +} + +internal class ArgumentOptions(parser: OptionParser) { + val fullscreen: OptionSpec = parser.accepts("fullscreen") + val screenSize: OptionSpec = parser.accepts("screen-size") + .withRequiredArg() + .withValuesConvertedBy(object : ValueConverter { + private val REGEX = Regex("^([1-9][0-9]*)x([1-9][0-9]*)$") + + override fun convert(value: String): Dimension { + val result = REGEX.find(value) + if (result == null) { + throw ValueConversionException("Invalid value (pattern unmatched): $value") + } else { + fun parseInt(value: String): Int { + try { + return value.toInt() + } catch (e: NumberFormatException) { + throw ValueConversionException("Invalid value: $value", e) + } + } + + try { + return Dimension(parseInt(result.groups[1]!!.value), parseInt(result.groups[2]!!.value)) + } catch (e: NullPointerException) { + triggerGlobalCrash(CodeLogicFault(e)) + } + } + } + + override fun valueType() = Dimension::class.java + + override fun valuePattern() = null + }) + val dataDir: OptionSpec = parser.accepts("data-dir") + .withRequiredArg() + .required() + .withValuesConvertedBy(PathConverter) + val resources: OptionSpec = parser.accepts("resources") + .withRequiredArg() + .required() + .withValuesConvertedBy(PathConverter) +} + +/** + * @throws OptionException + * @throws ClassCastException regarded as code logic problems + */ +class Arguments internal constructor(options: ArgumentOptions, args: OptionSet) { + val fullscreen = args.has(options.fullscreen) + val screenSize: Dimension? = args.valueOf(options.screenSize) + val dataDir: Path = args.valueOf(options.dataDir) + val resources: Path = args.valueOf(options.resources) +} diff --git a/src/common/kotlin/terramodulus/core/Core.kt b/src/common/kotlin/terramodulus/core/Core.kt index b3d04256..46dcb456 100644 --- a/src/common/kotlin/terramodulus/core/Core.kt +++ b/src/common/kotlin/terramodulus/core/Core.kt @@ -5,46 +5,5 @@ package terramodulus.core -import terramodulus.util.logging.logger -import java.io.Closeable -import java.io.File -import java.io.RandomAccessFile -import java.nio.channels.FileChannel -import java.nio.channels.FileLock -import java.nio.file.Files -import java.nio.file.Path - const val NAME = "TerraModulus" const val VERSION = "0.1.0" // TODO placeholder - -private val logger = logger {} - -/** Checks whether this instance is the first process launched in the game data directory. */ -fun checkSingleInstance(dir: Path) { - if (dir.parent != null) Files.createDirectories(dir.parent) - val lockFile = File(dir.toFile(), "session.lock") - if (lockFile.createNewFile()) { - logger.info { "LOCK file is created." } - } else { - logger.info { "LOCK file exists." } - } - - SessionLockFile(RandomAccessFile(lockFile, "rw")) -} - -/** - * @throws java.io.IOException - */ -class SessionLockFile internal constructor(private val file: RandomAccessFile) : Closeable { - private val channel: FileChannel = file.channel - private val lock: FileLock = channel.lock() - - /** - * @throws java.io.IOException - */ - override fun close() { - lock.close() - channel.close() - file.close() - } -} diff --git a/src/common/kotlin/terramodulus/util/exception/CodeLogicFault.kt b/src/common/kotlin/terramodulus/util/exception/CodeLogicFault.kt index ff8bdb66..3a1f7d25 100644 --- a/src/common/kotlin/terramodulus/util/exception/CodeLogicFault.kt +++ b/src/common/kotlin/terramodulus/util/exception/CodeLogicFault.kt @@ -14,6 +14,4 @@ package terramodulus.util.exception * [NullPointerException], [AssertionError], [ClassCastException], [NoSuchElementException], * [ConcurrentModificationException], [ArrayStoreException], etc. */ -class CodeLogicFault(override val message: String, override val cause: Throwable?) : Fault(message, cause) { - constructor(message: String) : this(message, null) -} +class CodeLogicFault(cause: Throwable) : Fault("Illogical code", cause) diff --git a/src/common/kotlin/terramodulus/util/exception/Core.kt b/src/common/kotlin/terramodulus/util/exception/Core.kt index cd58d19d..eaa9ace9 100644 --- a/src/common/kotlin/terramodulus/util/exception/Core.kt +++ b/src/common/kotlin/terramodulus/util/exception/Core.kt @@ -44,9 +44,9 @@ fun notifyAndRecordError(error: Error) { } fun triggerSessionCrash() { - + TODO() } -fun triggerGlobalCrash() { - +fun triggerGlobalCrash(error: Error): Nothing { + TODO() } diff --git a/src/kernel/kotlin/terramodulus/common/core/AbstractTerraModulus.kt b/src/kernel/kotlin/terramodulus/common/core/AbstractTerraModulus.kt index f86bf8ab..0b013a2c 100644 --- a/src/kernel/kotlin/terramodulus/common/core/AbstractTerraModulus.kt +++ b/src/kernel/kotlin/terramodulus/common/core/AbstractTerraModulus.kt @@ -5,7 +5,9 @@ package terramodulus.common.core -abstract class AbstractTerraModulus { +import java.io.Closeable + +abstract class AbstractTerraModulus : Closeable { abstract var tps: Int abstract fun run() diff --git a/src/kernel/kotlin/terramodulus/common/core/Core.kt b/src/kernel/kotlin/terramodulus/common/core/Core.kt index b32cab82..bad6671c 100644 --- a/src/kernel/kotlin/terramodulus/common/core/Core.kt +++ b/src/kernel/kotlin/terramodulus/common/core/Core.kt @@ -5,13 +5,81 @@ package terramodulus.common.core +import jdk.internal.joptsimple.OptionException +import terramodulus.util.exception.Error +import terramodulus.util.exception.Fault import terramodulus.util.exception.UnhandledExceptionFault +import terramodulus.util.exception.triggerGlobalCrash +import terramodulus.util.logging.logger +import java.io.Closeable +import java.io.File +import java.io.RandomAccessFile +import java.nio.channels.FileChannel +import java.nio.channels.FileLock +import java.nio.file.Files +import java.nio.file.Path + +private val logger = logger {} /** * This should only be used by `terramodulus.core.Main` in the beginning of `main` function. */ fun setupInit() { Thread.setDefaultUncaughtExceptionHandler { _, e -> - UnhandledExceptionFault.global(e) + triggerGlobalCrash(UnhandledExceptionFault.global(e)) + } + + logger.info { "System Properties:" } + System.getProperties().entries.forEach { + logger.info { "\t${it.key} = ${it.value}" } + } +} + +fun run(game: AbstractTerraModulus) { + try { + game.run() + } catch (e: Throwable) { + triggerGlobalCrash(UnhandledExceptionFault.scoped("game.run()", e)) + } finally { + game.close() + } +} + +/** + * This should only be used by `terramodulus.core.Main`. + */ +class ApplicationInitializationFault(cause: Throwable) : + Fault("Failed to initialize application", cause) + +class ApplicationArgumentParsingError(cause: OptionException) : + Error("Failed to parse application arguments", cause) + +/** Checks whether this instance is the first process launched in the game data directory. */ +fun checkSingleInstance(dir: Path) { + if (dir.parent != null) Files.createDirectories(dir.parent) + val lockFile = File(dir.toFile(), "session.lock") + if (lockFile.createNewFile()) { + logger.info { "LOCK file is created." } + } else { + logger.info { "LOCK file exists." } + } + + SessionLockFile(RandomAccessFile(lockFile, "rw")) +} + +/** + * @throws java.io.IOException + */ +class SessionLockFile internal constructor(private val file: RandomAccessFile) : Closeable { + private val channel: FileChannel = file.channel + private val lock: FileLock = channel.lock() + + /** + * @throws java.io.IOException + */ + override fun close() { + lock.close() + channel.close() + file.close() } } diff --git a/src/server/kotlin/terramodulus/core/Main.kt b/src/server/kotlin/terramodulus/core/Main.kt index f75d7c69..a14f1062 100644 --- a/src/server/kotlin/terramodulus/core/Main.kt +++ b/src/server/kotlin/terramodulus/core/Main.kt @@ -5,10 +5,16 @@ package terramodulus.core +import terramodulus.common.core.run import terramodulus.common.core.setupInit -fun main() { +fun main(args: Array) { setupInit() + parseArgs(args) println("java.library.path = ${System.getProperty("java.library.path")}") + run(TerraModulus()) +} + +fun parseArgs(args: Array) { } diff --git a/src/server/kotlin/terramodulus/core/TerraModulus.kt b/src/server/kotlin/terramodulus/core/TerraModulus.kt index dca84520..b8f386f1 100644 --- a/src/server/kotlin/terramodulus/core/TerraModulus.kt +++ b/src/server/kotlin/terramodulus/core/TerraModulus.kt @@ -15,4 +15,8 @@ class TerraModulus : AbstractTerraModulus() { override fun run() { TODO("Not yet implemented") } + + override fun close() { + TODO("Not yet implemented") + } } From f99a2f1df033b4baf1d69cb2a95194853a73d8d8 Mon Sep 17 00:00:00 2001 From: Ben Forge <74168521+BenCheung0422@users.noreply.github.com> Date: Fri, 30 May 2025 03:04:36 +0800 Subject: [PATCH 10/19] UI: Test SDL init in client Purely testing purpose --- .gitignore | 3 -- .idea/codeStyles/Project.xml | 8 +++++ .idea/codeStyles/codeStyleConfig.xml | 5 ++++ .idea/compiler.xml | 12 +++++++- .idea/misc.xml | 2 +- build.gradle.kts | 4 ++- ferricia | 1 + src/client/kotlin/terramodulus/core/Main.kt | 18 ++++++++---- .../kotlin/terramodulus/core/TerraModulus.kt | 4 +++ src/common/kotlin/terramodulus/engine/Core.kt | 10 +++++++ .../kotlin/terramodulus/engine/UIManager.kt | 22 ++++++++++++++ .../terramodulus/util/exception/Core.kt | 1 + .../util/exception/FerriciaEngineFault.kt | 8 +++++ src/common/resources/log4j2.xml | 2 +- .../terramodulus/engine/ferricia/Core.kt | 6 ++-- .../terramodulus/engine/ferricia/Demo.kt | 2 +- .../kotlin/terramodulus/engine/ferricia/UI.kt | 29 +++++++++++++++++++ .../kotlin/terramodulus/common/core/Core.kt | 5 +++- 18 files changed, 125 insertions(+), 17 deletions(-) create mode 100644 .idea/codeStyles/Project.xml create mode 100644 .idea/codeStyles/codeStyleConfig.xml create mode 160000 ferricia create mode 100644 src/common/kotlin/terramodulus/engine/Core.kt create mode 100644 src/common/kotlin/terramodulus/engine/UIManager.kt create mode 100644 src/common/kotlin/terramodulus/util/exception/FerriciaEngineFault.kt create mode 100644 src/internal/kotlin/terramodulus/engine/ferricia/UI.kt diff --git a/.gitignore b/.gitignore index 8d41cd23..1117200a 100644 --- a/.gitignore +++ b/.gitignore @@ -10,9 +10,6 @@ # Mobile Tools for Java (J2ME) .mtj.tmp/ -# Submodule -/ferricia - # Package Files # *.jar *.war diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 00000000..39152545 --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 00000000..79ee123c --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml index b86273d9..3af8b1ce 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -1,6 +1,16 @@ - + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index 6ed74919..743f1bcc 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -7,5 +7,5 @@ - + \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index a9f99453..e7632394 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -41,6 +41,7 @@ subprojects { project(":kernel") { dependencies { implementation(project(":common")) + implementation("net.sf.jopt-simple:jopt-simple:5.0.4") } } @@ -69,7 +70,8 @@ project(":common") { implementation("org.apache.logging.log4j:log4j-slf4j2-impl:2.24.3") implementation(platform("org.apache.logging.log4j:log4j-bom:2.24.3")) annotationProcessor("org.apache.logging.log4j:log4j-core:2.24.3") - implementation("io.github.oshai:kotlin-logging-jvm:7.0.3") + runtimeOnly("com.lmax:disruptor:4.0.0") + api("io.github.oshai:kotlin-logging-jvm:7.0.3") } } diff --git a/ferricia b/ferricia new file mode 160000 index 00000000..42a95044 --- /dev/null +++ b/ferricia @@ -0,0 +1 @@ +Subproject commit 42a950445e53cd5a990634199de74e273f6029f5 diff --git a/src/client/kotlin/terramodulus/core/Main.kt b/src/client/kotlin/terramodulus/core/Main.kt index 4dcb9c87..8502d860 100644 --- a/src/client/kotlin/terramodulus/core/Main.kt +++ b/src/client/kotlin/terramodulus/core/Main.kt @@ -5,16 +5,17 @@ package terramodulus.core -import jdk.internal.joptsimple.OptionException -import jdk.internal.joptsimple.OptionParser -import jdk.internal.joptsimple.OptionSet -import jdk.internal.joptsimple.OptionSpec -import jdk.internal.joptsimple.ValueConversionException -import jdk.internal.joptsimple.ValueConverter +import joptsimple.OptionException +import joptsimple.OptionParser +import joptsimple.OptionSet +import joptsimple.OptionSpec +import joptsimple.ValueConversionException +import joptsimple.ValueConverter import terramodulus.common.core.ApplicationArgumentParsingError import terramodulus.common.core.ApplicationInitializationFault import terramodulus.common.core.run import terramodulus.common.core.setupInit +import terramodulus.engine.UIManager import terramodulus.util.exception.CodeLogicFault import terramodulus.util.exception.triggerGlobalCrash import java.awt.Dimension @@ -23,6 +24,11 @@ import java.nio.file.Path fun main(args: Array) { setupInit() + UIManager().use { + println("Hello World!") + Thread.sleep(5000) + } + return try { parseArgs(args) } catch (e: ApplicationArgumentParsingError) { diff --git a/src/client/kotlin/terramodulus/core/TerraModulus.kt b/src/client/kotlin/terramodulus/core/TerraModulus.kt index dca84520..b8f386f1 100644 --- a/src/client/kotlin/terramodulus/core/TerraModulus.kt +++ b/src/client/kotlin/terramodulus/core/TerraModulus.kt @@ -15,4 +15,8 @@ class TerraModulus : AbstractTerraModulus() { override fun run() { TODO("Not yet implemented") } + + override fun close() { + TODO("Not yet implemented") + } } diff --git a/src/common/kotlin/terramodulus/engine/Core.kt b/src/common/kotlin/terramodulus/engine/Core.kt new file mode 100644 index 00000000..87664233 --- /dev/null +++ b/src/common/kotlin/terramodulus/engine/Core.kt @@ -0,0 +1,10 @@ +/* + * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package terramodulus.engine + +import terramodulus.engine.ferricia.loadLibrary + +fun initEngine() = loadLibrary() diff --git a/src/common/kotlin/terramodulus/engine/UIManager.kt b/src/common/kotlin/terramodulus/engine/UIManager.kt new file mode 100644 index 00000000..dd98336c --- /dev/null +++ b/src/common/kotlin/terramodulus/engine/UIManager.kt @@ -0,0 +1,22 @@ +/* + * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package terramodulus.engine + +import terramodulus.engine.ferricia.UI.dropSdlHandle +import terramodulus.engine.ferricia.UI.dropWindowHandle +import terramodulus.engine.ferricia.UI.initSdlHandle +import terramodulus.engine.ferricia.UI.initWindowHandle +import java.io.Closeable + +class UIManager : Closeable { + private val sdlHandle = initSdlHandle() + private val windowHandle = initWindowHandle(sdlHandle) + + override fun close() { + dropWindowHandle(windowHandle) + dropSdlHandle(sdlHandle) + } +} diff --git a/src/common/kotlin/terramodulus/util/exception/Core.kt b/src/common/kotlin/terramodulus/util/exception/Core.kt index eaa9ace9..99b1c628 100644 --- a/src/common/kotlin/terramodulus/util/exception/Core.kt +++ b/src/common/kotlin/terramodulus/util/exception/Core.kt @@ -48,5 +48,6 @@ fun triggerSessionCrash() { } fun triggerGlobalCrash(error: Error): Nothing { + logger.error(error) {} TODO() } diff --git a/src/common/kotlin/terramodulus/util/exception/FerriciaEngineFault.kt b/src/common/kotlin/terramodulus/util/exception/FerriciaEngineFault.kt new file mode 100644 index 00000000..a649dcf0 --- /dev/null +++ b/src/common/kotlin/terramodulus/util/exception/FerriciaEngineFault.kt @@ -0,0 +1,8 @@ +/* + * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package terramodulus.util.exception + +class FerriciaEngineFault(message: String) : Fault(message) diff --git a/src/common/resources/log4j2.xml b/src/common/resources/log4j2.xml index 46386a21..730055d7 100644 --- a/src/common/resources/log4j2.xml +++ b/src/common/resources/log4j2.xml @@ -18,7 +18,7 @@ - + diff --git a/src/internal/kotlin/terramodulus/engine/ferricia/Core.kt b/src/internal/kotlin/terramodulus/engine/ferricia/Core.kt index 9bb41e1f..78066ce7 100644 --- a/src/internal/kotlin/terramodulus/engine/ferricia/Core.kt +++ b/src/internal/kotlin/terramodulus/engine/ferricia/Core.kt @@ -7,10 +7,12 @@ package terramodulus.engine.ferricia import terramodulus.internal.platform.Kernel32 +const val NULL: Long = 0; + fun loadLibrary() { - if (Kernel32.INSTANCE != null) { + if (Kernel32.INSTANCE != null) { // For Windows Kernel32.INSTANCE.SetDllDirectoryW(System.getProperty("java.library.path")) // must use backslashes } - System.loadLibrary("ferricia") + System.loadLibrary("ferricia") } diff --git a/src/internal/kotlin/terramodulus/engine/ferricia/Demo.kt b/src/internal/kotlin/terramodulus/engine/ferricia/Demo.kt index 8d7f8de5..62831931 100644 --- a/src/internal/kotlin/terramodulus/engine/ferricia/Demo.kt +++ b/src/internal/kotlin/terramodulus/engine/ferricia/Demo.kt @@ -5,7 +5,7 @@ package terramodulus.engine.ferricia -internal object Demo { +object Demo { external fun hello(name: String): String external fun clientOnly(): Int } diff --git a/src/internal/kotlin/terramodulus/engine/ferricia/UI.kt b/src/internal/kotlin/terramodulus/engine/ferricia/UI.kt new file mode 100644 index 00000000..4199ac60 --- /dev/null +++ b/src/internal/kotlin/terramodulus/engine/ferricia/UI.kt @@ -0,0 +1,29 @@ +/* + * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package terramodulus.engine.ferricia + +object UI { + /** + * @return SDL handle pointer + */ + external fun initSdlHandle(): Long; + + /** + * @param sdlHandle SDL handle pointer + */ + external fun dropSdlHandle(sdlHandle: Long); + + /** + * @param sdlHandle SDL handle pointer + * @return window handle pointer + */ + external fun initWindowHandle(sdlHandle: Long): Long; + + /** + * @param windowHandle window handle pointer + */ + external fun dropWindowHandle(windowHandle: Long); +} diff --git a/src/kernel/kotlin/terramodulus/common/core/Core.kt b/src/kernel/kotlin/terramodulus/common/core/Core.kt index bad6671c..52e65717 100644 --- a/src/kernel/kotlin/terramodulus/common/core/Core.kt +++ b/src/kernel/kotlin/terramodulus/common/core/Core.kt @@ -5,7 +5,8 @@ package terramodulus.common.core -import jdk.internal.joptsimple.OptionException +import joptsimple.OptionException +import terramodulus.engine.initEngine import terramodulus.util.exception.Error import terramodulus.util.exception.Fault import terramodulus.util.exception.UnhandledExceptionFault @@ -33,6 +34,8 @@ fun setupInit() { System.getProperties().entries.forEach { logger.info { "\t${it.key} = ${it.value}" } } + + initEngine() // TODO remove; test only; consider other places } fun run(game: AbstractTerraModulus) { From 7fdacf4a2e9d49a56404d8b8f2491bacf6b34bdf Mon Sep 17 00:00:00 2001 From: Ben Forge <74168521+BenCheung0422@users.noreply.github.com> Date: Fri, 30 May 2025 05:39:54 +0800 Subject: [PATCH 11/19] Project: Restructure modules Split internal to common, client, server Combine kernel and common and spread to common, client, server --- build.gradle.kts | 85 +++++++++---------- settings.gradle.kts | 3 +- .../kotlin/terramodulus/engine/ferricia/UI.kt | 0 .../terramodulus/engine/ferricia/Core.kt | 0 .../terramodulus/engine/ferricia/Demo.kt | 0 .../internal/platform/Kernel32.kt | 0 .../kotlin/terramodulus/audio/AudioSystem.kt | 0 .../client/kotlin/terramodulus/core/Main.kt | 0 .../kotlin/terramodulus/core/TerraModulus.kt | 0 .../kotlin/terramodulus/engine/UIManager.kt | 0 .../kotlin/terramodulus/gfx/Component.kt | 0 .../client/kotlin/terramodulus/gfx/Menu.kt | 0 .../kotlin/terramodulus/gfx/RenderSystem.kt | 0 .../client/kotlin/terramodulus/gfx/Screen.kt | 0 .../kotlin/terramodulus/gfx/ScreenManager.kt | 0 .../kotlin/terramodulus/input/InputHandler.kt | 0 .../terramodulus/settings/Preference.kt | 0 .../kotlin/terramodulus/settings/Settings.kt | 0 .../common/core/AbstractTerraModulus.kt | 0 .../kotlin/terramodulus/common/core/Core.kt | 0 .../common/kotlin/terramodulus/core/Core.kt | 0 .../terramodulus/core/package-info.java | 0 .../common/kotlin/terramodulus/engine/Core.kt | 0 .../util/exception/CodeLogicFault.kt | 0 .../terramodulus/util/exception/Core.kt | 0 .../terramodulus/util/exception/Error.kt | 0 .../terramodulus/util/exception/Exception.kt | 0 .../terramodulus/util/exception/Failure.kt | 0 .../terramodulus/util/exception/Fault.kt | 0 .../util/exception/FerriciaEngineFault.kt | 0 .../util/exception/UnhandledExceptionFault.kt | 0 .../util/exception/package-info.java | 0 .../kotlin/terramodulus/util/logging/Core.kt | 0 .../util/logging/DelayedFileAppender.kt | 0 .../kotlin/terramodulus/util/logging/State.kt | 0 .../kotlin/terramodulus/world/WorldManager.kt | 0 .../kotlin/terramodulus/world/item/Items.kt | 0 .../kotlin/terramodulus/world/tile/Tiles.kt | 0 src/{ => kernel}/common/resources/log4j2.xml | 0 .../server/kotlin/terramodulus/core/Main.kt | 0 .../kotlin/terramodulus/core/TerraModulus.kt | 0 41 files changed, 44 insertions(+), 44 deletions(-) rename src/internal/{ => client}/kotlin/terramodulus/engine/ferricia/UI.kt (100%) rename src/internal/{ => common}/kotlin/terramodulus/engine/ferricia/Core.kt (100%) rename src/internal/{ => common}/kotlin/terramodulus/engine/ferricia/Demo.kt (100%) rename src/internal/{ => common}/kotlin/terramodulus/internal/platform/Kernel32.kt (100%) rename src/{ => kernel}/client/kotlin/terramodulus/audio/AudioSystem.kt (100%) rename src/{ => kernel}/client/kotlin/terramodulus/core/Main.kt (100%) rename src/{ => kernel}/client/kotlin/terramodulus/core/TerraModulus.kt (100%) rename src/{common => kernel/client}/kotlin/terramodulus/engine/UIManager.kt (100%) rename src/{ => kernel}/client/kotlin/terramodulus/gfx/Component.kt (100%) rename src/{ => kernel}/client/kotlin/terramodulus/gfx/Menu.kt (100%) rename src/{ => kernel}/client/kotlin/terramodulus/gfx/RenderSystem.kt (100%) rename src/{ => kernel}/client/kotlin/terramodulus/gfx/Screen.kt (100%) rename src/{ => kernel}/client/kotlin/terramodulus/gfx/ScreenManager.kt (100%) rename src/{ => kernel}/client/kotlin/terramodulus/input/InputHandler.kt (100%) rename src/{ => kernel}/client/kotlin/terramodulus/settings/Preference.kt (100%) rename src/{ => kernel}/client/kotlin/terramodulus/settings/Settings.kt (100%) rename src/kernel/{ => common}/kotlin/terramodulus/common/core/AbstractTerraModulus.kt (100%) rename src/kernel/{ => common}/kotlin/terramodulus/common/core/Core.kt (100%) rename src/{ => kernel}/common/kotlin/terramodulus/core/Core.kt (100%) rename src/{ => kernel}/common/kotlin/terramodulus/core/package-info.java (100%) rename src/{ => kernel}/common/kotlin/terramodulus/engine/Core.kt (100%) rename src/{ => kernel}/common/kotlin/terramodulus/util/exception/CodeLogicFault.kt (100%) rename src/{ => kernel}/common/kotlin/terramodulus/util/exception/Core.kt (100%) rename src/{ => kernel}/common/kotlin/terramodulus/util/exception/Error.kt (100%) rename src/{ => kernel}/common/kotlin/terramodulus/util/exception/Exception.kt (100%) rename src/{ => kernel}/common/kotlin/terramodulus/util/exception/Failure.kt (100%) rename src/{ => kernel}/common/kotlin/terramodulus/util/exception/Fault.kt (100%) rename src/{ => kernel}/common/kotlin/terramodulus/util/exception/FerriciaEngineFault.kt (100%) rename src/{ => kernel}/common/kotlin/terramodulus/util/exception/UnhandledExceptionFault.kt (100%) rename src/{ => kernel}/common/kotlin/terramodulus/util/exception/package-info.java (100%) rename src/{ => kernel}/common/kotlin/terramodulus/util/logging/Core.kt (100%) rename src/{ => kernel}/common/kotlin/terramodulus/util/logging/DelayedFileAppender.kt (100%) rename src/{ => kernel}/common/kotlin/terramodulus/util/logging/State.kt (100%) rename src/{ => kernel}/common/kotlin/terramodulus/world/WorldManager.kt (100%) rename src/{ => kernel}/common/kotlin/terramodulus/world/item/Items.kt (100%) rename src/{ => kernel}/common/kotlin/terramodulus/world/tile/Tiles.kt (100%) rename src/{ => kernel}/common/resources/log4j2.xml (100%) rename src/{ => kernel}/server/kotlin/terramodulus/core/Main.kt (100%) rename src/{ => kernel}/server/kotlin/terramodulus/core/TerraModulus.kt (100%) diff --git a/build.gradle.kts b/build.gradle.kts index e7632394..674295c9 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -7,7 +7,7 @@ plugins { application } -configure(listOf(project(":server"), project(":client"))) { +configure(listOf(project(":kernel:server"), project(":kernel:client"))) { apply(plugin = "application") } @@ -21,40 +21,43 @@ allprojects { } } -subprojects { - sourceSets.main { - kotlin.srcDir("kotlin") - resources.srcDir("resources") - } +configure(listOf(project(":kernel"), project(":internal"))) { + configure(listOf(project("common"), project("client"), project("server"))) { + sourceSets.main { + kotlin.srcDir("kotlin") + resources.srcDir("resources") + } - tasks.compileKotlin { - compilerOptions { - jvmTarget.set(JvmTarget.JVM_17) + tasks.compileKotlin { + compilerOptions { + jvmTarget.set(JvmTarget.JVM_17) + } } - } - kotlin { - jvmToolchain(17) + kotlin { + jvmToolchain(17) + } } -} -project(":kernel") { - dependencies { - implementation(project(":common")) - implementation("net.sf.jopt-simple:jopt-simple:5.0.4") + configure(listOf(project("client"), project("server"))) { + dependencies { + implementation(project("${parent!!.path}:common")) + } } } -project(":internal") { - dependencies { - implementation("net.java.dev.jna:jna:5.17.0") - implementation("net.java.dev.jna:jna-platform:5.17.0") +project(":kernel") { + arrayOf("common", "client", "server").forEach { + project(it) { + dependencies { + implementation(project(":internal:$it")) + } + } } } -project(":common") { +project(":kernel:common") { dependencies { - implementation(project(":internal")) api("org.jetbrains:annotations:26.0.2") api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.8.1") api("org.jetbrains.kotlinx:kotlinx-io-core:0.7.0") @@ -72,28 +75,24 @@ project(":common") { annotationProcessor("org.apache.logging.log4j:log4j-core:2.24.3") runtimeOnly("com.lmax:disruptor:4.0.0") api("io.github.oshai:kotlin-logging-jvm:7.0.3") + implementation("net.sf.jopt-simple:jopt-simple:5.0.4") } } -project(":client") { - dependencies { - implementation(project(":kernel")) - api(project(":common")) - implementation("net.sf.jopt-simple:jopt-simple:5.0.4") - } +project(":kernel:client").dependencies { + implementation("net.sf.jopt-simple:jopt-simple:5.0.4") +} - application { - mainClass = "terramodulus.core.MainKt" - } +project(":kernel:server").dependencies { + implementation("net.sf.jopt-simple:jopt-simple:5.0.4") } -project(":server") { - dependencies { - implementation(project(":kernel")) - api(project(":common")) - implementation("net.sf.jopt-simple:jopt-simple:5.0.4") - } +project(":internal:common").dependencies { + implementation("net.java.dev.jna:jna:5.17.0") + implementation("net.java.dev.jna:jna-platform:5.17.0") +} +configure(listOf(project(":kernel:server"), project(":kernel:client"))) { application { mainClass = "terramodulus.core.MainKt" } @@ -115,21 +114,21 @@ fun Exec.configureCargoBuild(target: Target) { } } -tasks.register("run_client") { +tasks.register("runClient") { group = "application" description = "Run client" - finalizedBy(":client:run") + finalizedBy(":kernel:client:run") configureCargoBuild(Target.CLIENT) } -tasks.register("run_server") { +tasks.register("runServer") { group = "application" description = "Run server" - finalizedBy(":server:run") + finalizedBy(":kernel:server:run") configureCargoBuild(Target.SERVER) } -configure(listOf(project(":server"), project(":client"))) { +configure(listOf(project(":kernel:server"), project(":kernel:client"))) { tasks.named("run") { jvmArgs("-Djava.library.path=${rootProject.file("ferricia/target/${ if (project.hasProperty("release")) "release" else "debug" diff --git a/settings.gradle.kts b/settings.gradle.kts index 2120b99a..48449515 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -11,7 +11,8 @@ plugins { } rootProject.name = "TerraModulus" -include("common", "internal", "kernel", "client", "server") +include("internal", "kernel") rootProject.children.forEach { it.projectDir = File(settingsDir, "src/${it.name}") + include("${it.name}:common", "${it.name}:client", "${it.name}:server") } diff --git a/src/internal/kotlin/terramodulus/engine/ferricia/UI.kt b/src/internal/client/kotlin/terramodulus/engine/ferricia/UI.kt similarity index 100% rename from src/internal/kotlin/terramodulus/engine/ferricia/UI.kt rename to src/internal/client/kotlin/terramodulus/engine/ferricia/UI.kt diff --git a/src/internal/kotlin/terramodulus/engine/ferricia/Core.kt b/src/internal/common/kotlin/terramodulus/engine/ferricia/Core.kt similarity index 100% rename from src/internal/kotlin/terramodulus/engine/ferricia/Core.kt rename to src/internal/common/kotlin/terramodulus/engine/ferricia/Core.kt diff --git a/src/internal/kotlin/terramodulus/engine/ferricia/Demo.kt b/src/internal/common/kotlin/terramodulus/engine/ferricia/Demo.kt similarity index 100% rename from src/internal/kotlin/terramodulus/engine/ferricia/Demo.kt rename to src/internal/common/kotlin/terramodulus/engine/ferricia/Demo.kt diff --git a/src/internal/kotlin/terramodulus/internal/platform/Kernel32.kt b/src/internal/common/kotlin/terramodulus/internal/platform/Kernel32.kt similarity index 100% rename from src/internal/kotlin/terramodulus/internal/platform/Kernel32.kt rename to src/internal/common/kotlin/terramodulus/internal/platform/Kernel32.kt diff --git a/src/client/kotlin/terramodulus/audio/AudioSystem.kt b/src/kernel/client/kotlin/terramodulus/audio/AudioSystem.kt similarity index 100% rename from src/client/kotlin/terramodulus/audio/AudioSystem.kt rename to src/kernel/client/kotlin/terramodulus/audio/AudioSystem.kt diff --git a/src/client/kotlin/terramodulus/core/Main.kt b/src/kernel/client/kotlin/terramodulus/core/Main.kt similarity index 100% rename from src/client/kotlin/terramodulus/core/Main.kt rename to src/kernel/client/kotlin/terramodulus/core/Main.kt diff --git a/src/client/kotlin/terramodulus/core/TerraModulus.kt b/src/kernel/client/kotlin/terramodulus/core/TerraModulus.kt similarity index 100% rename from src/client/kotlin/terramodulus/core/TerraModulus.kt rename to src/kernel/client/kotlin/terramodulus/core/TerraModulus.kt diff --git a/src/common/kotlin/terramodulus/engine/UIManager.kt b/src/kernel/client/kotlin/terramodulus/engine/UIManager.kt similarity index 100% rename from src/common/kotlin/terramodulus/engine/UIManager.kt rename to src/kernel/client/kotlin/terramodulus/engine/UIManager.kt diff --git a/src/client/kotlin/terramodulus/gfx/Component.kt b/src/kernel/client/kotlin/terramodulus/gfx/Component.kt similarity index 100% rename from src/client/kotlin/terramodulus/gfx/Component.kt rename to src/kernel/client/kotlin/terramodulus/gfx/Component.kt diff --git a/src/client/kotlin/terramodulus/gfx/Menu.kt b/src/kernel/client/kotlin/terramodulus/gfx/Menu.kt similarity index 100% rename from src/client/kotlin/terramodulus/gfx/Menu.kt rename to src/kernel/client/kotlin/terramodulus/gfx/Menu.kt diff --git a/src/client/kotlin/terramodulus/gfx/RenderSystem.kt b/src/kernel/client/kotlin/terramodulus/gfx/RenderSystem.kt similarity index 100% rename from src/client/kotlin/terramodulus/gfx/RenderSystem.kt rename to src/kernel/client/kotlin/terramodulus/gfx/RenderSystem.kt diff --git a/src/client/kotlin/terramodulus/gfx/Screen.kt b/src/kernel/client/kotlin/terramodulus/gfx/Screen.kt similarity index 100% rename from src/client/kotlin/terramodulus/gfx/Screen.kt rename to src/kernel/client/kotlin/terramodulus/gfx/Screen.kt diff --git a/src/client/kotlin/terramodulus/gfx/ScreenManager.kt b/src/kernel/client/kotlin/terramodulus/gfx/ScreenManager.kt similarity index 100% rename from src/client/kotlin/terramodulus/gfx/ScreenManager.kt rename to src/kernel/client/kotlin/terramodulus/gfx/ScreenManager.kt diff --git a/src/client/kotlin/terramodulus/input/InputHandler.kt b/src/kernel/client/kotlin/terramodulus/input/InputHandler.kt similarity index 100% rename from src/client/kotlin/terramodulus/input/InputHandler.kt rename to src/kernel/client/kotlin/terramodulus/input/InputHandler.kt diff --git a/src/client/kotlin/terramodulus/settings/Preference.kt b/src/kernel/client/kotlin/terramodulus/settings/Preference.kt similarity index 100% rename from src/client/kotlin/terramodulus/settings/Preference.kt rename to src/kernel/client/kotlin/terramodulus/settings/Preference.kt diff --git a/src/client/kotlin/terramodulus/settings/Settings.kt b/src/kernel/client/kotlin/terramodulus/settings/Settings.kt similarity index 100% rename from src/client/kotlin/terramodulus/settings/Settings.kt rename to src/kernel/client/kotlin/terramodulus/settings/Settings.kt diff --git a/src/kernel/kotlin/terramodulus/common/core/AbstractTerraModulus.kt b/src/kernel/common/kotlin/terramodulus/common/core/AbstractTerraModulus.kt similarity index 100% rename from src/kernel/kotlin/terramodulus/common/core/AbstractTerraModulus.kt rename to src/kernel/common/kotlin/terramodulus/common/core/AbstractTerraModulus.kt diff --git a/src/kernel/kotlin/terramodulus/common/core/Core.kt b/src/kernel/common/kotlin/terramodulus/common/core/Core.kt similarity index 100% rename from src/kernel/kotlin/terramodulus/common/core/Core.kt rename to src/kernel/common/kotlin/terramodulus/common/core/Core.kt diff --git a/src/common/kotlin/terramodulus/core/Core.kt b/src/kernel/common/kotlin/terramodulus/core/Core.kt similarity index 100% rename from src/common/kotlin/terramodulus/core/Core.kt rename to src/kernel/common/kotlin/terramodulus/core/Core.kt diff --git a/src/common/kotlin/terramodulus/core/package-info.java b/src/kernel/common/kotlin/terramodulus/core/package-info.java similarity index 100% rename from src/common/kotlin/terramodulus/core/package-info.java rename to src/kernel/common/kotlin/terramodulus/core/package-info.java diff --git a/src/common/kotlin/terramodulus/engine/Core.kt b/src/kernel/common/kotlin/terramodulus/engine/Core.kt similarity index 100% rename from src/common/kotlin/terramodulus/engine/Core.kt rename to src/kernel/common/kotlin/terramodulus/engine/Core.kt diff --git a/src/common/kotlin/terramodulus/util/exception/CodeLogicFault.kt b/src/kernel/common/kotlin/terramodulus/util/exception/CodeLogicFault.kt similarity index 100% rename from src/common/kotlin/terramodulus/util/exception/CodeLogicFault.kt rename to src/kernel/common/kotlin/terramodulus/util/exception/CodeLogicFault.kt diff --git a/src/common/kotlin/terramodulus/util/exception/Core.kt b/src/kernel/common/kotlin/terramodulus/util/exception/Core.kt similarity index 100% rename from src/common/kotlin/terramodulus/util/exception/Core.kt rename to src/kernel/common/kotlin/terramodulus/util/exception/Core.kt diff --git a/src/common/kotlin/terramodulus/util/exception/Error.kt b/src/kernel/common/kotlin/terramodulus/util/exception/Error.kt similarity index 100% rename from src/common/kotlin/terramodulus/util/exception/Error.kt rename to src/kernel/common/kotlin/terramodulus/util/exception/Error.kt diff --git a/src/common/kotlin/terramodulus/util/exception/Exception.kt b/src/kernel/common/kotlin/terramodulus/util/exception/Exception.kt similarity index 100% rename from src/common/kotlin/terramodulus/util/exception/Exception.kt rename to src/kernel/common/kotlin/terramodulus/util/exception/Exception.kt diff --git a/src/common/kotlin/terramodulus/util/exception/Failure.kt b/src/kernel/common/kotlin/terramodulus/util/exception/Failure.kt similarity index 100% rename from src/common/kotlin/terramodulus/util/exception/Failure.kt rename to src/kernel/common/kotlin/terramodulus/util/exception/Failure.kt diff --git a/src/common/kotlin/terramodulus/util/exception/Fault.kt b/src/kernel/common/kotlin/terramodulus/util/exception/Fault.kt similarity index 100% rename from src/common/kotlin/terramodulus/util/exception/Fault.kt rename to src/kernel/common/kotlin/terramodulus/util/exception/Fault.kt diff --git a/src/common/kotlin/terramodulus/util/exception/FerriciaEngineFault.kt b/src/kernel/common/kotlin/terramodulus/util/exception/FerriciaEngineFault.kt similarity index 100% rename from src/common/kotlin/terramodulus/util/exception/FerriciaEngineFault.kt rename to src/kernel/common/kotlin/terramodulus/util/exception/FerriciaEngineFault.kt diff --git a/src/common/kotlin/terramodulus/util/exception/UnhandledExceptionFault.kt b/src/kernel/common/kotlin/terramodulus/util/exception/UnhandledExceptionFault.kt similarity index 100% rename from src/common/kotlin/terramodulus/util/exception/UnhandledExceptionFault.kt rename to src/kernel/common/kotlin/terramodulus/util/exception/UnhandledExceptionFault.kt diff --git a/src/common/kotlin/terramodulus/util/exception/package-info.java b/src/kernel/common/kotlin/terramodulus/util/exception/package-info.java similarity index 100% rename from src/common/kotlin/terramodulus/util/exception/package-info.java rename to src/kernel/common/kotlin/terramodulus/util/exception/package-info.java diff --git a/src/common/kotlin/terramodulus/util/logging/Core.kt b/src/kernel/common/kotlin/terramodulus/util/logging/Core.kt similarity index 100% rename from src/common/kotlin/terramodulus/util/logging/Core.kt rename to src/kernel/common/kotlin/terramodulus/util/logging/Core.kt diff --git a/src/common/kotlin/terramodulus/util/logging/DelayedFileAppender.kt b/src/kernel/common/kotlin/terramodulus/util/logging/DelayedFileAppender.kt similarity index 100% rename from src/common/kotlin/terramodulus/util/logging/DelayedFileAppender.kt rename to src/kernel/common/kotlin/terramodulus/util/logging/DelayedFileAppender.kt diff --git a/src/common/kotlin/terramodulus/util/logging/State.kt b/src/kernel/common/kotlin/terramodulus/util/logging/State.kt similarity index 100% rename from src/common/kotlin/terramodulus/util/logging/State.kt rename to src/kernel/common/kotlin/terramodulus/util/logging/State.kt diff --git a/src/common/kotlin/terramodulus/world/WorldManager.kt b/src/kernel/common/kotlin/terramodulus/world/WorldManager.kt similarity index 100% rename from src/common/kotlin/terramodulus/world/WorldManager.kt rename to src/kernel/common/kotlin/terramodulus/world/WorldManager.kt diff --git a/src/common/kotlin/terramodulus/world/item/Items.kt b/src/kernel/common/kotlin/terramodulus/world/item/Items.kt similarity index 100% rename from src/common/kotlin/terramodulus/world/item/Items.kt rename to src/kernel/common/kotlin/terramodulus/world/item/Items.kt diff --git a/src/common/kotlin/terramodulus/world/tile/Tiles.kt b/src/kernel/common/kotlin/terramodulus/world/tile/Tiles.kt similarity index 100% rename from src/common/kotlin/terramodulus/world/tile/Tiles.kt rename to src/kernel/common/kotlin/terramodulus/world/tile/Tiles.kt diff --git a/src/common/resources/log4j2.xml b/src/kernel/common/resources/log4j2.xml similarity index 100% rename from src/common/resources/log4j2.xml rename to src/kernel/common/resources/log4j2.xml diff --git a/src/server/kotlin/terramodulus/core/Main.kt b/src/kernel/server/kotlin/terramodulus/core/Main.kt similarity index 100% rename from src/server/kotlin/terramodulus/core/Main.kt rename to src/kernel/server/kotlin/terramodulus/core/Main.kt diff --git a/src/server/kotlin/terramodulus/core/TerraModulus.kt b/src/kernel/server/kotlin/terramodulus/core/TerraModulus.kt similarity index 100% rename from src/server/kotlin/terramodulus/core/TerraModulus.kt rename to src/kernel/server/kotlin/terramodulus/core/TerraModulus.kt From b89c8986f05452e9278e4ecbdefed7150f71c24c Mon Sep 17 00:00:00 2001 From: Ben Forge <74168521+BenCheung0422@users.noreply.github.com> Date: Sat, 31 May 2025 07:48:45 +0800 Subject: [PATCH 12/19] MUI: Draft MUI and GMS Renamed GCS for Component --- build.gradle.kts | 1 + ferricia | 2 +- .../kotlin/terramodulus/engine/Canvas.kt | 17 ++++++++++ .../kotlin/terramodulus/engine/Window.kt} | 14 ++++++--- .../engine/ferricia/{UI.kt => MUI.kt} | 4 ++- .../common/kotlin/terramodulus/engine/Core.kt | 0 .../terramodulus/engine/ferricia/Core.kt | 2 +- .../client/kotlin/terramodulus/core/Main.kt | 31 ++++++++----------- .../{audio => mui}/AudioSystem.kt | 2 +- .../InputHandler.kt => mui/AuiManager.kt} | 5 +-- .../kotlin/terramodulus/mui/GuiManager.kt | 20 ++++++++++++ .../{gfx/Component.kt => mui/InputSystem.kt} | 4 +-- .../terramodulus/{gfx => mui}/RenderSystem.kt | 3 +- .../kotlin/terramodulus/mui/gms/Component.kt | 10 ++++++ .../terramodulus/{gfx => mui/gms}/Menu.kt | 5 +-- .../terramodulus/{gfx => mui/gms}/Screen.kt | 2 +- .../{gfx => mui/gms}/ScreenManager.kt | 2 +- .../kotlin/terramodulus/common/core/Core.kt | 9 +++--- src/kernel/common/resources/log4j2.xml | 2 +- 19 files changed, 94 insertions(+), 41 deletions(-) create mode 100644 src/internal/client/kotlin/terramodulus/engine/Canvas.kt rename src/{kernel/client/kotlin/terramodulus/engine/UIManager.kt => internal/client/kotlin/terramodulus/engine/Window.kt} (50%) rename src/internal/client/kotlin/terramodulus/engine/ferricia/{UI.kt => MUI.kt} (90%) rename src/{kernel => internal}/common/kotlin/terramodulus/engine/Core.kt (100%) rename src/kernel/client/kotlin/terramodulus/{audio => mui}/AudioSystem.kt (83%) rename src/kernel/client/kotlin/terramodulus/{input/InputHandler.kt => mui/AuiManager.kt} (60%) create mode 100644 src/kernel/client/kotlin/terramodulus/mui/GuiManager.kt rename src/kernel/client/kotlin/terramodulus/{gfx/Component.kt => mui/InputSystem.kt} (72%) rename src/kernel/client/kotlin/terramodulus/{gfx => mui}/RenderSystem.kt (84%) create mode 100644 src/kernel/client/kotlin/terramodulus/mui/gms/Component.kt rename src/kernel/client/kotlin/terramodulus/{gfx => mui/gms}/Menu.kt (61%) rename src/kernel/client/kotlin/terramodulus/{gfx => mui/gms}/Screen.kt (82%) rename src/kernel/client/kotlin/terramodulus/{gfx => mui/gms}/ScreenManager.kt (83%) diff --git a/build.gradle.kts b/build.gradle.kts index 674295c9..79e43373 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -133,5 +133,6 @@ configure(listOf(project(":kernel:server"), project(":kernel:client"))) { jvmArgs("-Djava.library.path=${rootProject.file("ferricia/target/${ if (project.hasProperty("release")) "release" else "debug" }").path}") + args("--screen-size", "800x500") } } diff --git a/ferricia b/ferricia index 42a95044..6a7f9ec6 160000 --- a/ferricia +++ b/ferricia @@ -1 +1 @@ -Subproject commit 42a950445e53cd5a990634199de74e273f6029f5 +Subproject commit 6a7f9ec6b5799888eb1ebb9ccd5c129317bc9ab1 diff --git a/src/internal/client/kotlin/terramodulus/engine/Canvas.kt b/src/internal/client/kotlin/terramodulus/engine/Canvas.kt new file mode 100644 index 00000000..46fefa4b --- /dev/null +++ b/src/internal/client/kotlin/terramodulus/engine/Canvas.kt @@ -0,0 +1,17 @@ +/* + * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package terramodulus.engine + +import terramodulus.engine.ferricia.MUI.getGLVersion + +/** + * Manages the OpenGL viewport rendering as a "canvas"; managed by the GL context. + * + * This manages GL viewport in the SDL window and rendering in the viewport. + */ +class Canvas internal constructor() { + val glVersion = getGLVersion() +} diff --git a/src/kernel/client/kotlin/terramodulus/engine/UIManager.kt b/src/internal/client/kotlin/terramodulus/engine/Window.kt similarity index 50% rename from src/kernel/client/kotlin/terramodulus/engine/UIManager.kt rename to src/internal/client/kotlin/terramodulus/engine/Window.kt index dd98336c..f8350eb6 100644 --- a/src/kernel/client/kotlin/terramodulus/engine/UIManager.kt +++ b/src/internal/client/kotlin/terramodulus/engine/Window.kt @@ -5,15 +5,19 @@ package terramodulus.engine -import terramodulus.engine.ferricia.UI.dropSdlHandle -import terramodulus.engine.ferricia.UI.dropWindowHandle -import terramodulus.engine.ferricia.UI.initSdlHandle -import terramodulus.engine.ferricia.UI.initWindowHandle +import terramodulus.engine.ferricia.MUI.dropSdlHandle +import terramodulus.engine.ferricia.MUI.dropWindowHandle +import terramodulus.engine.ferricia.MUI.initSdlHandle +import terramodulus.engine.ferricia.MUI.initWindowHandle import java.io.Closeable -class UIManager : Closeable { +/** + * Manages the SDL window instance and the underlying GL context. + */ +class Window : Closeable { private val sdlHandle = initSdlHandle() private val windowHandle = initWindowHandle(sdlHandle) + val canvas = Canvas() override fun close() { dropWindowHandle(windowHandle) diff --git a/src/internal/client/kotlin/terramodulus/engine/ferricia/UI.kt b/src/internal/client/kotlin/terramodulus/engine/ferricia/MUI.kt similarity index 90% rename from src/internal/client/kotlin/terramodulus/engine/ferricia/UI.kt rename to src/internal/client/kotlin/terramodulus/engine/ferricia/MUI.kt index 4199ac60..c13d764f 100644 --- a/src/internal/client/kotlin/terramodulus/engine/ferricia/UI.kt +++ b/src/internal/client/kotlin/terramodulus/engine/ferricia/MUI.kt @@ -5,7 +5,7 @@ package terramodulus.engine.ferricia -object UI { +internal object MUI { /** * @return SDL handle pointer */ @@ -26,4 +26,6 @@ object UI { * @param windowHandle window handle pointer */ external fun dropWindowHandle(windowHandle: Long); + + external fun getGLVersion(): String; } diff --git a/src/kernel/common/kotlin/terramodulus/engine/Core.kt b/src/internal/common/kotlin/terramodulus/engine/Core.kt similarity index 100% rename from src/kernel/common/kotlin/terramodulus/engine/Core.kt rename to src/internal/common/kotlin/terramodulus/engine/Core.kt diff --git a/src/internal/common/kotlin/terramodulus/engine/ferricia/Core.kt b/src/internal/common/kotlin/terramodulus/engine/ferricia/Core.kt index 78066ce7..cb41c29e 100644 --- a/src/internal/common/kotlin/terramodulus/engine/ferricia/Core.kt +++ b/src/internal/common/kotlin/terramodulus/engine/ferricia/Core.kt @@ -9,7 +9,7 @@ import terramodulus.internal.platform.Kernel32 const val NULL: Long = 0; -fun loadLibrary() { +internal fun loadLibrary() { if (Kernel32.INSTANCE != null) { // For Windows Kernel32.INSTANCE.SetDllDirectoryW(System.getProperty("java.library.path")) // must use backslashes } diff --git a/src/kernel/client/kotlin/terramodulus/core/Main.kt b/src/kernel/client/kotlin/terramodulus/core/Main.kt index 8502d860..137a381a 100644 --- a/src/kernel/client/kotlin/terramodulus/core/Main.kt +++ b/src/kernel/client/kotlin/terramodulus/core/Main.kt @@ -13,9 +13,8 @@ import joptsimple.ValueConversionException import joptsimple.ValueConverter import terramodulus.common.core.ApplicationArgumentParsingError import terramodulus.common.core.ApplicationInitializationFault -import terramodulus.common.core.run import terramodulus.common.core.setupInit -import terramodulus.engine.UIManager +import terramodulus.mui.GuiManager import terramodulus.util.exception.CodeLogicFault import terramodulus.util.exception.triggerGlobalCrash import java.awt.Dimension @@ -24,17 +23,13 @@ import java.nio.file.Path fun main(args: Array) { setupInit() - UIManager().use { - println("Hello World!") - Thread.sleep(5000) - } - return try { parseArgs(args) } catch (e: ApplicationArgumentParsingError) { triggerGlobalCrash(ApplicationInitializationFault(e)) } - run(TerraModulus()) + GuiManager() +// run(TerraModulus()) } /** @@ -104,14 +99,14 @@ internal class ArgumentOptions(parser: OptionParser) { override fun valuePattern() = null }) - val dataDir: OptionSpec = parser.accepts("data-dir") - .withRequiredArg() - .required() - .withValuesConvertedBy(PathConverter) - val resources: OptionSpec = parser.accepts("resources") - .withRequiredArg() - .required() - .withValuesConvertedBy(PathConverter) +// val dataDir: OptionSpec = parser.accepts("data-dir") +// .withRequiredArg() +// .required() +// .withValuesConvertedBy(PathConverter) +// val resources: OptionSpec = parser.accepts("resources") +// .withRequiredArg() +// .required() +// .withValuesConvertedBy(PathConverter) } /** @@ -121,6 +116,6 @@ internal class ArgumentOptions(parser: OptionParser) { class Arguments internal constructor(options: ArgumentOptions, args: OptionSet) { val fullscreen = args.has(options.fullscreen) val screenSize: Dimension? = args.valueOf(options.screenSize) - val dataDir: Path = args.valueOf(options.dataDir) - val resources: Path = args.valueOf(options.resources) +// val dataDir: Path = args.valueOf(options.dataDir) +// val resources: Path = args.valueOf(options.resources) } diff --git a/src/kernel/client/kotlin/terramodulus/audio/AudioSystem.kt b/src/kernel/client/kotlin/terramodulus/mui/AudioSystem.kt similarity index 83% rename from src/kernel/client/kotlin/terramodulus/audio/AudioSystem.kt rename to src/kernel/client/kotlin/terramodulus/mui/AudioSystem.kt index 3feeebc6..d6cda66a 100644 --- a/src/kernel/client/kotlin/terramodulus/audio/AudioSystem.kt +++ b/src/kernel/client/kotlin/terramodulus/mui/AudioSystem.kt @@ -3,7 +3,7 @@ * SPDX-License-Identifier: LGPL-3.0-only */ -package terramodulus.audio +package terramodulus.mui class AudioSystem { } diff --git a/src/kernel/client/kotlin/terramodulus/input/InputHandler.kt b/src/kernel/client/kotlin/terramodulus/mui/AuiManager.kt similarity index 60% rename from src/kernel/client/kotlin/terramodulus/input/InputHandler.kt rename to src/kernel/client/kotlin/terramodulus/mui/AuiManager.kt index bceeea88..60be78ee 100644 --- a/src/kernel/client/kotlin/terramodulus/input/InputHandler.kt +++ b/src/kernel/client/kotlin/terramodulus/mui/AuiManager.kt @@ -3,7 +3,8 @@ * SPDX-License-Identifier: LGPL-3.0-only */ -package terramodulus.input +package terramodulus.mui -class InputHandler { +class AuiManager { + val audioSystem = AudioSystem() } diff --git a/src/kernel/client/kotlin/terramodulus/mui/GuiManager.kt b/src/kernel/client/kotlin/terramodulus/mui/GuiManager.kt new file mode 100644 index 00000000..cb8afeef --- /dev/null +++ b/src/kernel/client/kotlin/terramodulus/mui/GuiManager.kt @@ -0,0 +1,20 @@ +/* + * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package terramodulus.mui + +import terramodulus.engine.Window +import java.io.Closeable + +class GuiManager : Closeable { + private val window = Window() + private val canvas = window.canvas + val renderSystem = RenderSystem() + val inputSystem = InputSystem() + + override fun close() { + window.close() + } +} diff --git a/src/kernel/client/kotlin/terramodulus/gfx/Component.kt b/src/kernel/client/kotlin/terramodulus/mui/InputSystem.kt similarity index 72% rename from src/kernel/client/kotlin/terramodulus/gfx/Component.kt rename to src/kernel/client/kotlin/terramodulus/mui/InputSystem.kt index 423d5598..1bf26fe5 100644 --- a/src/kernel/client/kotlin/terramodulus/gfx/Component.kt +++ b/src/kernel/client/kotlin/terramodulus/mui/InputSystem.kt @@ -3,7 +3,7 @@ * SPDX-License-Identifier: LGPL-3.0-only */ -package terramodulus.gfx +package terramodulus.mui -class Component { +class InputSystem { } diff --git a/src/kernel/client/kotlin/terramodulus/gfx/RenderSystem.kt b/src/kernel/client/kotlin/terramodulus/mui/RenderSystem.kt similarity index 84% rename from src/kernel/client/kotlin/terramodulus/gfx/RenderSystem.kt rename to src/kernel/client/kotlin/terramodulus/mui/RenderSystem.kt index 3846e1b4..a7b1b3df 100644 --- a/src/kernel/client/kotlin/terramodulus/gfx/RenderSystem.kt +++ b/src/kernel/client/kotlin/terramodulus/mui/RenderSystem.kt @@ -3,7 +3,8 @@ * SPDX-License-Identifier: LGPL-3.0-only */ -package terramodulus.gfx +package terramodulus.mui class RenderSystem { + } diff --git a/src/kernel/client/kotlin/terramodulus/mui/gms/Component.kt b/src/kernel/client/kotlin/terramodulus/mui/gms/Component.kt new file mode 100644 index 00000000..ff1e514d --- /dev/null +++ b/src/kernel/client/kotlin/terramodulus/mui/gms/Component.kt @@ -0,0 +1,10 @@ +/* + * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package terramodulus.mui.gms + +abstract class Component { + abstract fun render() +} diff --git a/src/kernel/client/kotlin/terramodulus/gfx/Menu.kt b/src/kernel/client/kotlin/terramodulus/mui/gms/Menu.kt similarity index 61% rename from src/kernel/client/kotlin/terramodulus/gfx/Menu.kt rename to src/kernel/client/kotlin/terramodulus/mui/gms/Menu.kt index 27fbbdd3..cf0d63a8 100644 --- a/src/kernel/client/kotlin/terramodulus/gfx/Menu.kt +++ b/src/kernel/client/kotlin/terramodulus/mui/gms/Menu.kt @@ -3,7 +3,8 @@ * SPDX-License-Identifier: LGPL-3.0-only */ -package terramodulus.gfx +package terramodulus.mui.gms -class Menu { +abstract class Menu { + abstract fun render() } diff --git a/src/kernel/client/kotlin/terramodulus/gfx/Screen.kt b/src/kernel/client/kotlin/terramodulus/mui/gms/Screen.kt similarity index 82% rename from src/kernel/client/kotlin/terramodulus/gfx/Screen.kt rename to src/kernel/client/kotlin/terramodulus/mui/gms/Screen.kt index a822f102..184652d2 100644 --- a/src/kernel/client/kotlin/terramodulus/gfx/Screen.kt +++ b/src/kernel/client/kotlin/terramodulus/mui/gms/Screen.kt @@ -3,7 +3,7 @@ * SPDX-License-Identifier: LGPL-3.0-only */ -package terramodulus.gfx +package terramodulus.mui.gms class Screen { } diff --git a/src/kernel/client/kotlin/terramodulus/gfx/ScreenManager.kt b/src/kernel/client/kotlin/terramodulus/mui/gms/ScreenManager.kt similarity index 83% rename from src/kernel/client/kotlin/terramodulus/gfx/ScreenManager.kt rename to src/kernel/client/kotlin/terramodulus/mui/gms/ScreenManager.kt index 02b604c9..8d5b3c7c 100644 --- a/src/kernel/client/kotlin/terramodulus/gfx/ScreenManager.kt +++ b/src/kernel/client/kotlin/terramodulus/mui/gms/ScreenManager.kt @@ -3,7 +3,7 @@ * SPDX-License-Identifier: LGPL-3.0-only */ -package terramodulus.gfx +package terramodulus.mui.gms class ScreenManager { } diff --git a/src/kernel/common/kotlin/terramodulus/common/core/Core.kt b/src/kernel/common/kotlin/terramodulus/common/core/Core.kt index 52e65717..f5e95575 100644 --- a/src/kernel/common/kotlin/terramodulus/common/core/Core.kt +++ b/src/kernel/common/kotlin/terramodulus/common/core/Core.kt @@ -30,10 +30,11 @@ fun setupInit() { triggerGlobalCrash(UnhandledExceptionFault.global(e)) } - logger.info { "System Properties:" } - System.getProperties().entries.forEach { - logger.info { "\t${it.key} = ${it.value}" } - } + logger.info {"System Properties:\n${ + System.getProperties().entries.joinToString(separator = "\n") { + "\t${it.key}=${it.value}" + } + }"} initEngine() // TODO remove; test only; consider other places } diff --git a/src/kernel/common/resources/log4j2.xml b/src/kernel/common/resources/log4j2.xml index 730055d7..7feaefe2 100644 --- a/src/kernel/common/resources/log4j2.xml +++ b/src/kernel/common/resources/log4j2.xml @@ -4,7 +4,7 @@ ~ SPDX-License-Identifier: LGPL-3.0-only --> - From 055974a654c7cfbb0ac3e26490103e2eb021fb4f Mon Sep 17 00:00:00 2001 From: Ben Forge <74168521+BenCheung0422@users.noreply.github.com> Date: Mon, 2 Jun 2025 07:52:47 +0800 Subject: [PATCH 13/19] MUI: Add SDL event polling from JNI Brief draft --- .idea/compiler.xml | 2 +- .idea/modules.xml | 8 ++ .../TerraModulus.internal.client.main.iml | 8 ++ .../kotlin/terramodulus/engine/Canvas.kt | 8 +- .../kotlin/terramodulus/engine/MuiEvent.kt | 76 +++++++++++++++++ .../kotlin/terramodulus/engine/Window.kt | 13 +-- .../engine/ferricia/{MUI.kt => Mui.kt} | 15 +++- .../engine/ferricia/package-info.java | 9 ++ .../kotlin/terramodulus/mui/AudioSystem.kt | 2 +- .../kotlin/terramodulus/mui/AuiManager.kt | 2 +- .../kotlin/terramodulus/mui/GuiManager.kt | 85 ++++++++++++++++++- .../kotlin/terramodulus/mui/InputSystem.kt | 2 +- .../kotlin/terramodulus/mui/RenderSystem.kt | 7 +- .../kotlin/terramodulus/mui/gms/Menu.kt | 2 + .../kotlin/terramodulus/mui/gms/Screen.kt | 20 ++++- .../terramodulus/mui/gms/ScreenManager.kt | 60 ++++++++++++- .../terramodulus/mui/gms/event/ScreenEvent.kt | 11 +++ .../common/core/AbstractTerraModulus.kt | 8 ++ 18 files changed, 319 insertions(+), 19 deletions(-) create mode 100644 .idea/modules.xml create mode 100644 .idea/modules/src/internal/client/TerraModulus.internal.client.main.iml create mode 100644 src/internal/client/kotlin/terramodulus/engine/MuiEvent.kt rename src/internal/client/kotlin/terramodulus/engine/ferricia/{MUI.kt => Mui.kt} (63%) create mode 100644 src/internal/client/kotlin/terramodulus/engine/ferricia/package-info.java create mode 100644 src/kernel/client/kotlin/terramodulus/mui/gms/event/ScreenEvent.kt diff --git a/.idea/compiler.xml b/.idea/compiler.xml index 3af8b1ce..e254cd93 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -8,7 +8,7 @@ - + diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 00000000..b1a50944 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/modules/src/internal/client/TerraModulus.internal.client.main.iml b/.idea/modules/src/internal/client/TerraModulus.internal.client.main.iml new file mode 100644 index 00000000..5c1a1cac --- /dev/null +++ b/.idea/modules/src/internal/client/TerraModulus.internal.client.main.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/src/internal/client/kotlin/terramodulus/engine/Canvas.kt b/src/internal/client/kotlin/terramodulus/engine/Canvas.kt index 46fefa4b..03ae61db 100644 --- a/src/internal/client/kotlin/terramodulus/engine/Canvas.kt +++ b/src/internal/client/kotlin/terramodulus/engine/Canvas.kt @@ -5,13 +5,13 @@ package terramodulus.engine -import terramodulus.engine.ferricia.MUI.getGLVersion +import terramodulus.engine.ferricia.Mui.getGLVersion /** - * Manages the OpenGL viewport rendering as a "canvas"; managed by the GL context. + * Manages the OpenGL viewport rendering as a "**canvas**"; managed by the GL context. * * This manages GL viewport in the SDL window and rendering in the viewport. */ -class Canvas internal constructor() { - val glVersion = getGLVersion() +class Canvas internal constructor(private val windowHandle: Long) { + val glVersion = getGLVersion(windowHandle) } diff --git a/src/internal/client/kotlin/terramodulus/engine/MuiEvent.kt b/src/internal/client/kotlin/terramodulus/engine/MuiEvent.kt new file mode 100644 index 00000000..6f670c53 --- /dev/null +++ b/src/internal/client/kotlin/terramodulus/engine/MuiEvent.kt @@ -0,0 +1,76 @@ +/* + * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package terramodulus.engine + +sealed interface MuiEvent { + data class DisplayAdded(val displayHandle: Long) : MuiEvent + data class DisplayRemoved(val displayHandle: Long) : MuiEvent + data class DisplayMoved(val displayHandle: Long) : MuiEvent + data object WindowShown : MuiEvent + data object WindowHidden : MuiEvent + data object WindowExposed : MuiEvent + data class WindowMoved(val x: Int, val y: Int) : MuiEvent + data class WindowResized(val width: UInt, val height: UInt) : MuiEvent + data class WindowPixelSizeChanged(val width: UInt, val height: UInt) : MuiEvent + data object WindowMetalViewResized : MuiEvent + data object WindowMinimized : MuiEvent + data object WindowMaximized : MuiEvent + data object WindowRestored : MuiEvent + data object WindowMouseEnter : MuiEvent + data object WindowMouseLeave : MuiEvent + data object WindowFocusGained : MuiEvent + data object WindowFocusLost : MuiEvent + data object WindowCloseRequested : MuiEvent + data object WindowIccProfChanged : MuiEvent + data object WindowOccluded : MuiEvent + data object WindowEnterFullscreen : MuiEvent + data object WindowLeaveFullscreen : MuiEvent + data object WindowDestroyed : MuiEvent + data object WindowHdrStateChanged : MuiEvent + data class KeyboardKeyDown(val keyboardId: UInt, val key: UInt) : MuiEvent + data class KeyboardKeyUp(val keyboardId: UInt, val key: UInt) : MuiEvent + data class TextEditing(val text: String, val start: Int, val length: UInt) : MuiEvent + data class TextInput(val text: String) : MuiEvent + data object KeymapChanged : MuiEvent + data object KeyboardAdded : MuiEvent + data object KeyboardRemoved : MuiEvent + data object TextEditingCandidates : MuiEvent + data class MouseMotion(val mouseId: UInt, val x: Float, val y: Float) : MuiEvent + data class MouseButtonDown(val mouseId: UInt, val key: UByte) : MuiEvent + data class MouseButtonUp(val mouseId: UInt, val key: UByte) : MuiEvent + data class MouseWheel(val mouseId: UInt, val x: Float, val y: Float) : MuiEvent + data object MouseAdded : MuiEvent + data object MouseRemoved : MuiEvent + data class JoystickAxisMotion(val joystickId: UInt, val axis: UByte, val value: Short) : MuiEvent + data object JoystickBallMotion : MuiEvent + data class JoystickHatMotion(val joystickId: UInt, val hat: UByte, val value: UByte) : MuiEvent + data class JoystickButtonDown(val joystickId: UInt, val button: UByte) : MuiEvent + data class JoystickButtonUp(val joystickId: UInt, val button: UByte) : MuiEvent + data class JoystickAdded(val joystickId: UInt) : MuiEvent + data class JoystickRemoved(val joystickId: UInt) : MuiEvent + data object JoystickBatteryUpdated : MuiEvent + data class GamepadAxisMotion(val joystickId: UInt, val axis: UByte, val value: Short) : MuiEvent + data class GamepadButtonDown(val joystickId: UInt, val button: UByte) : MuiEvent + data class GamepadButtonUp(val joystickId: UInt, val button: UByte) : MuiEvent + data class GamepadAdded(val joystickId: UInt) : MuiEvent + data class GamepadRemoved(val joystickId: UInt) : MuiEvent + data class GamepadRemapped(val joystickId: UInt) : MuiEvent + data class GamepadTouchpadDown(val joystickId: UInt, val touchpad: Int, val finger: Int, + val x: Float, val y: Float, val pressure: Float) : MuiEvent + data class GamepadTouchpadMotion(val joystickId: UInt, val touchpad: Int, val finger: Int, + val x: Float, val y: Float, val pressure: Float) : MuiEvent + data class GamepadTouchpadUp(val joystickId: UInt, val touchpad: Int, val finger: Int, + val x: Float, val y: Float, val pressure: Float) : MuiEvent + data object GamepadSteamHandleUpdated : MuiEvent + data class DropFile(val filename: String) : MuiEvent + data class DropText(val text: String) : MuiEvent + data object DropBegin : MuiEvent + data object DropComplete : MuiEvent + data object DropPosition : MuiEvent + data object RenderTargetsReset : MuiEvent + data object RenderDeviceReset : MuiEvent + data object RenderDeviceLost : MuiEvent +} diff --git a/src/internal/client/kotlin/terramodulus/engine/Window.kt b/src/internal/client/kotlin/terramodulus/engine/Window.kt index f8350eb6..d9363275 100644 --- a/src/internal/client/kotlin/terramodulus/engine/Window.kt +++ b/src/internal/client/kotlin/terramodulus/engine/Window.kt @@ -5,10 +5,11 @@ package terramodulus.engine -import terramodulus.engine.ferricia.MUI.dropSdlHandle -import terramodulus.engine.ferricia.MUI.dropWindowHandle -import terramodulus.engine.ferricia.MUI.initSdlHandle -import terramodulus.engine.ferricia.MUI.initWindowHandle +import terramodulus.engine.ferricia.Mui.dropSdlHandle +import terramodulus.engine.ferricia.Mui.dropWindowHandle +import terramodulus.engine.ferricia.Mui.initSdlHandle +import terramodulus.engine.ferricia.Mui.initWindowHandle +import terramodulus.engine.ferricia.Mui.sdlPoll import java.io.Closeable /** @@ -17,7 +18,9 @@ import java.io.Closeable class Window : Closeable { private val sdlHandle = initSdlHandle() private val windowHandle = initWindowHandle(sdlHandle) - val canvas = Canvas() + val canvas = Canvas(windowHandle) + + fun pollEvents() = sdlPoll(sdlHandle) override fun close() { dropWindowHandle(windowHandle) diff --git a/src/internal/client/kotlin/terramodulus/engine/ferricia/MUI.kt b/src/internal/client/kotlin/terramodulus/engine/ferricia/Mui.kt similarity index 63% rename from src/internal/client/kotlin/terramodulus/engine/ferricia/MUI.kt rename to src/internal/client/kotlin/terramodulus/engine/ferricia/Mui.kt index c13d764f..fab27da0 100644 --- a/src/internal/client/kotlin/terramodulus/engine/ferricia/MUI.kt +++ b/src/internal/client/kotlin/terramodulus/engine/ferricia/Mui.kt @@ -5,7 +5,9 @@ package terramodulus.engine.ferricia -internal object MUI { +import terramodulus.engine.MuiEvent + +internal object Mui { /** * @return SDL handle pointer */ @@ -27,5 +29,14 @@ internal object MUI { */ external fun dropWindowHandle(windowHandle: Long); - external fun getGLVersion(): String; + /** + * @param windowHandle window handle pointer + */ + external fun getGLVersion(windowHandle: Long): String; + + /** + * @param sdlHandle SDL handle pointer + * @return the list of all MUI events in this frame + */ + external fun sdlPoll(sdlHandle: Long): Array; } diff --git a/src/internal/client/kotlin/terramodulus/engine/ferricia/package-info.java b/src/internal/client/kotlin/terramodulus/engine/ferricia/package-info.java new file mode 100644 index 00000000..74afb1ef --- /dev/null +++ b/src/internal/client/kotlin/terramodulus/engine/ferricia/package-info.java @@ -0,0 +1,9 @@ +/* + * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +/** + * Contains direct bindings to the Ferricia Engine exposed JNI functions. + */ +package terramodulus.engine.ferricia; diff --git a/src/kernel/client/kotlin/terramodulus/mui/AudioSystem.kt b/src/kernel/client/kotlin/terramodulus/mui/AudioSystem.kt index d6cda66a..b598c87a 100644 --- a/src/kernel/client/kotlin/terramodulus/mui/AudioSystem.kt +++ b/src/kernel/client/kotlin/terramodulus/mui/AudioSystem.kt @@ -5,5 +5,5 @@ package terramodulus.mui -class AudioSystem { +class AudioSystem internal constructor() { } diff --git a/src/kernel/client/kotlin/terramodulus/mui/AuiManager.kt b/src/kernel/client/kotlin/terramodulus/mui/AuiManager.kt index 60be78ee..f0ea126a 100644 --- a/src/kernel/client/kotlin/terramodulus/mui/AuiManager.kt +++ b/src/kernel/client/kotlin/terramodulus/mui/AuiManager.kt @@ -5,6 +5,6 @@ package terramodulus.mui -class AuiManager { +class AuiManager internal constructor() { val audioSystem = AudioSystem() } diff --git a/src/kernel/client/kotlin/terramodulus/mui/GuiManager.kt b/src/kernel/client/kotlin/terramodulus/mui/GuiManager.kt index cb8afeef..a64cece4 100644 --- a/src/kernel/client/kotlin/terramodulus/mui/GuiManager.kt +++ b/src/kernel/client/kotlin/terramodulus/mui/GuiManager.kt @@ -5,14 +5,97 @@ package terramodulus.mui +import terramodulus.engine.MuiEvent import terramodulus.engine.Window +import terramodulus.mui.gms.ScreenManager import java.io.Closeable -class GuiManager : Closeable { +class GuiManager internal constructor() : Closeable { private val window = Window() private val canvas = window.canvas val renderSystem = RenderSystem() val inputSystem = InputSystem() + val screenManager = ScreenManager() + + /** + * Screen updating, targeting twice as *maximum FPS*. + */ + internal fun updateScreen() {} + + /** + * Canvas updating, per frame, maximally the *maximum FPS*. + * This includes input ticking and canvas rendering. + */ + internal fun updateCanvas() { + window.pollEvents().forEach { event -> + when (event) { + is MuiEvent.DisplayAdded -> TODO() + is MuiEvent.DisplayMoved -> TODO() + is MuiEvent.DisplayRemoved -> TODO() + MuiEvent.DropBegin -> TODO() + MuiEvent.DropComplete -> TODO() + is MuiEvent.DropFile -> TODO() + MuiEvent.DropPosition -> TODO() + is MuiEvent.DropText -> TODO() + is MuiEvent.GamepadAdded -> TODO() + is MuiEvent.GamepadAxisMotion -> TODO() + is MuiEvent.GamepadButtonDown -> TODO() + is MuiEvent.GamepadButtonUp -> TODO() + is MuiEvent.GamepadRemapped -> TODO() + is MuiEvent.GamepadRemoved -> TODO() + MuiEvent.GamepadSteamHandleUpdated -> TODO() + is MuiEvent.GamepadTouchpadDown -> TODO() + is MuiEvent.GamepadTouchpadMotion -> TODO() + is MuiEvent.GamepadTouchpadUp -> TODO() + is MuiEvent.JoystickAdded -> TODO() + is MuiEvent.JoystickAxisMotion -> TODO() + MuiEvent.JoystickBallMotion -> TODO() + MuiEvent.JoystickBatteryUpdated -> TODO() + is MuiEvent.JoystickButtonDown -> TODO() + is MuiEvent.JoystickButtonUp -> TODO() + is MuiEvent.JoystickHatMotion -> TODO() + is MuiEvent.JoystickRemoved -> TODO() + MuiEvent.KeyboardAdded -> TODO() + is MuiEvent.KeyboardKeyDown -> TODO() + is MuiEvent.KeyboardKeyUp -> TODO() + MuiEvent.KeyboardRemoved -> TODO() + MuiEvent.KeymapChanged -> TODO() + MuiEvent.MouseAdded -> TODO() + is MuiEvent.MouseButtonDown -> TODO() + is MuiEvent.MouseButtonUp -> TODO() + is MuiEvent.MouseMotion -> TODO() + MuiEvent.MouseRemoved -> TODO() + is MuiEvent.MouseWheel -> TODO() + MuiEvent.RenderDeviceLost -> TODO() + MuiEvent.RenderDeviceReset -> TODO() + MuiEvent.RenderTargetsReset -> TODO() + is MuiEvent.TextEditing -> TODO() + MuiEvent.TextEditingCandidates -> TODO() + is MuiEvent.TextInput -> TODO() + MuiEvent.WindowCloseRequested -> TODO() + MuiEvent.WindowDestroyed -> TODO() + MuiEvent.WindowEnterFullscreen -> TODO() + MuiEvent.WindowExposed -> TODO() + MuiEvent.WindowFocusGained -> TODO() + MuiEvent.WindowFocusLost -> TODO() + MuiEvent.WindowHdrStateChanged -> TODO() + MuiEvent.WindowHidden -> TODO() + MuiEvent.WindowIccProfChanged -> TODO() + MuiEvent.WindowLeaveFullscreen -> TODO() + MuiEvent.WindowMaximized -> TODO() + MuiEvent.WindowMetalViewResized -> TODO() + MuiEvent.WindowMinimized -> TODO() + MuiEvent.WindowMouseEnter -> TODO() + MuiEvent.WindowMouseLeave -> TODO() + is MuiEvent.WindowMoved -> TODO() + MuiEvent.WindowOccluded -> TODO() + is MuiEvent.WindowPixelSizeChanged -> TODO() + is MuiEvent.WindowResized -> TODO() + MuiEvent.WindowRestored -> TODO() + MuiEvent.WindowShown -> TODO() + } + } + } override fun close() { window.close() diff --git a/src/kernel/client/kotlin/terramodulus/mui/InputSystem.kt b/src/kernel/client/kotlin/terramodulus/mui/InputSystem.kt index 1bf26fe5..2589961b 100644 --- a/src/kernel/client/kotlin/terramodulus/mui/InputSystem.kt +++ b/src/kernel/client/kotlin/terramodulus/mui/InputSystem.kt @@ -5,5 +5,5 @@ package terramodulus.mui -class InputSystem { +class InputSystem internal constructor() { } diff --git a/src/kernel/client/kotlin/terramodulus/mui/RenderSystem.kt b/src/kernel/client/kotlin/terramodulus/mui/RenderSystem.kt index a7b1b3df..035a7496 100644 --- a/src/kernel/client/kotlin/terramodulus/mui/RenderSystem.kt +++ b/src/kernel/client/kotlin/terramodulus/mui/RenderSystem.kt @@ -5,6 +5,11 @@ package terramodulus.mui -class RenderSystem { +import terramodulus.engine.Canvas +class RenderSystem internal constructor() { + + fun render(canvas: Canvas) { + + } } diff --git a/src/kernel/client/kotlin/terramodulus/mui/gms/Menu.kt b/src/kernel/client/kotlin/terramodulus/mui/gms/Menu.kt index cf0d63a8..fc508b2d 100644 --- a/src/kernel/client/kotlin/terramodulus/mui/gms/Menu.kt +++ b/src/kernel/client/kotlin/terramodulus/mui/gms/Menu.kt @@ -6,5 +6,7 @@ package terramodulus.mui.gms abstract class Menu { + private val components = LinkedHashSet() + abstract fun render() } diff --git a/src/kernel/client/kotlin/terramodulus/mui/gms/Screen.kt b/src/kernel/client/kotlin/terramodulus/mui/gms/Screen.kt index 184652d2..4fbc132d 100644 --- a/src/kernel/client/kotlin/terramodulus/mui/gms/Screen.kt +++ b/src/kernel/client/kotlin/terramodulus/mui/gms/Screen.kt @@ -5,5 +5,23 @@ package terramodulus.mui.gms -class Screen { +import terramodulus.mui.gms.event.ScreenEvent + +abstract class Screen { + private val listeners = HashMap, LinkedHashSet<(ScreenEvent) -> Unit>>() + private val menus = LinkedHashSet() + + fun addListener(e: Class, l: (T) -> Unit) { + @Suppress("UNCHECKED_CAST") + listeners.computeIfAbsent(e) { LinkedHashSet() }.add(l as (ScreenEvent) -> Unit) + } + + fun removeListener(e: Class, l: (T) -> Unit) { + listeners[e]?.remove(l) + } + + /** + * Cleans up and closes any used resource here. + */ + abstract fun exit() } diff --git a/src/kernel/client/kotlin/terramodulus/mui/gms/ScreenManager.kt b/src/kernel/client/kotlin/terramodulus/mui/gms/ScreenManager.kt index 8d5b3c7c..00a10abb 100644 --- a/src/kernel/client/kotlin/terramodulus/mui/gms/ScreenManager.kt +++ b/src/kernel/client/kotlin/terramodulus/mui/gms/ScreenManager.kt @@ -5,5 +5,63 @@ package terramodulus.mui.gms -class ScreenManager { +class ScreenManager internal constructor() { + private val screens = ArrayDeque() + private val queue = ArrayDeque() + private val handle = Handle() + + private sealed interface ScreenOperation { + /** + * Exits `n` times + * + * @throws IllegalArgumentException when `n` < 1 + * @throws IllegalStateException when `n` >= [screens] size during operation + */ + class Exit(val n: Int) : ScreenOperation + + /** + * Opens the `screen` + */ + class Open(val screen: () -> Screen) : ScreenOperation + + /** + * Exits until reaching the `screen` then remains on the `screen` + */ + class ExitTo(val screen: Screen) : ScreenOperation + + /** + * Clears [screens] then opens the `screen` + */ + class Reset(val screen: () -> Screen) : ScreenOperation + } + + inner class Handle { + /** + * @see ScreenOperation.Exit + */ + fun exit(n: Int) { + queue.add(ScreenOperation.Exit(n)) + } + + /** + * @see ScreenOperation.Open + */ + fun open(screen: () -> Screen) { + queue.add(ScreenOperation.Open(screen)) + } + + /** + * @see ScreenOperation.ExitTo + */ + fun exitTo(screen: Screen) { + queue.add(ScreenOperation.ExitTo(screen)) + } + + /** + * @see ScreenOperation.Reset + */ + fun reset(screen: () -> Screen) { + queue.add(ScreenOperation.Reset(screen)) + } + } } diff --git a/src/kernel/client/kotlin/terramodulus/mui/gms/event/ScreenEvent.kt b/src/kernel/client/kotlin/terramodulus/mui/gms/event/ScreenEvent.kt new file mode 100644 index 00000000..402dd86e --- /dev/null +++ b/src/kernel/client/kotlin/terramodulus/mui/gms/event/ScreenEvent.kt @@ -0,0 +1,11 @@ +/* + * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package terramodulus.mui.gms.event + +sealed interface ScreenEvent { + object Open : ScreenEvent + object Close : ScreenEvent +} diff --git a/src/kernel/common/kotlin/terramodulus/common/core/AbstractTerraModulus.kt b/src/kernel/common/kotlin/terramodulus/common/core/AbstractTerraModulus.kt index 0b013a2c..256b08a9 100644 --- a/src/kernel/common/kotlin/terramodulus/common/core/AbstractTerraModulus.kt +++ b/src/kernel/common/kotlin/terramodulus/common/core/AbstractTerraModulus.kt @@ -7,8 +7,16 @@ package terramodulus.common.core import java.io.Closeable +/** + * Do not use/extend this outside TerraModulus game code. + * + * If this is Multiplatform, this could be `expect` with subclasses `actual`. + * + * @constructor Cannot be `internal` because `client` and `server` are other modules. + */ abstract class AbstractTerraModulus : Closeable { abstract var tps: Int + protected set abstract fun run() } From 1dcc8999263f22f14614332924a9726aa453e80b Mon Sep 17 00:00:00 2001 From: Ben Forge <74168521+BenCheung0422@users.noreply.github.com> Date: Tue, 3 Jun 2025 06:39:52 +0800 Subject: [PATCH 14/19] Render: Demo test rendering with GL --- .idea/modules.xml | 1 + .../TerraModulus.kernel.client.main.iml | 8 + ferricia | 2 +- .../kotlin/terramodulus/engine/Canvas.kt | 20 +- .../kotlin/terramodulus/engine/Window.kt | 9 + .../terramodulus/engine/ferricia/Mui.kt | 62 +++- .../client/kotlin/terramodulus/core/Main.kt | 7 +- .../kotlin/terramodulus/core/TerraModulus.kt | 11 +- .../kotlin/terramodulus/mui/GuiManager.kt | 272 +++++++++++++----- src/kernel/client/resources/test.fsh | 8 + src/kernel/client/resources/test.png | Bin 0 -> 6602 bytes src/kernel/client/resources/test.vsh | 11 + .../kotlin/terramodulus/common/core/Core.kt | 2 +- .../kotlin/terramodulus/core/TerraModulus.kt | 2 +- 14 files changed, 336 insertions(+), 79 deletions(-) create mode 100644 .idea/modules/src/kernel/client/TerraModulus.kernel.client.main.iml create mode 100644 src/kernel/client/resources/test.fsh create mode 100644 src/kernel/client/resources/test.png create mode 100644 src/kernel/client/resources/test.vsh diff --git a/.idea/modules.xml b/.idea/modules.xml index b1a50944..c81769a0 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -3,6 +3,7 @@ + \ No newline at end of file diff --git a/.idea/modules/src/kernel/client/TerraModulus.kernel.client.main.iml b/.idea/modules/src/kernel/client/TerraModulus.kernel.client.main.iml new file mode 100644 index 00000000..15c47c5f --- /dev/null +++ b/.idea/modules/src/kernel/client/TerraModulus.kernel.client.main.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/ferricia b/ferricia index 6a7f9ec6..10392430 160000 --- a/ferricia +++ b/ferricia @@ -1 +1 @@ -Subproject commit 6a7f9ec6b5799888eb1ebb9ccd5c129317bc9ab1 +Subproject commit 10392430c3619c5a811a5d0b82df836c333b1795 diff --git a/src/internal/client/kotlin/terramodulus/engine/Canvas.kt b/src/internal/client/kotlin/terramodulus/engine/Canvas.kt index 03ae61db..5febe1c1 100644 --- a/src/internal/client/kotlin/terramodulus/engine/Canvas.kt +++ b/src/internal/client/kotlin/terramodulus/engine/Canvas.kt @@ -5,13 +5,31 @@ package terramodulus.engine +import terramodulus.engine.ferricia.Mui.dropCanvasHandle import terramodulus.engine.ferricia.Mui.getGLVersion +import terramodulus.engine.ferricia.Mui.initCanvasHandle +import terramodulus.engine.ferricia.Mui.loadImageToCanvas +import terramodulus.engine.ferricia.Mui.renderTexture +import terramodulus.engine.ferricia.Mui.shaders +import java.awt.Image +import java.io.Closeable /** * Manages the OpenGL viewport rendering as a "**canvas**"; managed by the GL context. * * This manages GL viewport in the SDL window and rendering in the viewport. */ -class Canvas internal constructor(private val windowHandle: Long) { +class Canvas internal constructor(private val windowHandle: Long) : Closeable { val glVersion = getGLVersion(windowHandle) + val canvasHandle = initCanvasHandle() + + fun loadImage(path: String) = loadImageToCanvas(canvasHandle, path) + + fun loadShaders(vsh: String, fsh: String) = shaders(canvasHandle, vsh, fsh) + + fun renderImage(shader: UInt, texture: UInt) = renderTexture(canvasHandle, shader, texture) + + override fun close() { + dropCanvasHandle(canvasHandle) + } } diff --git a/src/internal/client/kotlin/terramodulus/engine/Window.kt b/src/internal/client/kotlin/terramodulus/engine/Window.kt index d9363275..c5e00efe 100644 --- a/src/internal/client/kotlin/terramodulus/engine/Window.kt +++ b/src/internal/client/kotlin/terramodulus/engine/Window.kt @@ -9,7 +9,10 @@ import terramodulus.engine.ferricia.Mui.dropSdlHandle import terramodulus.engine.ferricia.Mui.dropWindowHandle import terramodulus.engine.ferricia.Mui.initSdlHandle import terramodulus.engine.ferricia.Mui.initWindowHandle +import terramodulus.engine.ferricia.Mui.resizeGLViewport import terramodulus.engine.ferricia.Mui.sdlPoll +import terramodulus.engine.ferricia.Mui.showWindow +import terramodulus.engine.ferricia.Mui.swapWindow import java.io.Closeable /** @@ -20,6 +23,12 @@ class Window : Closeable { private val windowHandle = initWindowHandle(sdlHandle) val canvas = Canvas(windowHandle) + fun show() = showWindow(windowHandle) + + fun swap() = swapWindow(windowHandle) + + fun resizeGLViewport() = resizeGLViewport(windowHandle) + fun pollEvents() = sdlPoll(sdlHandle) override fun close() { diff --git a/src/internal/client/kotlin/terramodulus/engine/ferricia/Mui.kt b/src/internal/client/kotlin/terramodulus/engine/ferricia/Mui.kt index fab27da0..3af737b0 100644 --- a/src/internal/client/kotlin/terramodulus/engine/ferricia/Mui.kt +++ b/src/internal/client/kotlin/terramodulus/engine/ferricia/Mui.kt @@ -11,32 +11,82 @@ internal object Mui { /** * @return SDL handle pointer */ - external fun initSdlHandle(): Long; + external fun initSdlHandle(): Long /** * @param sdlHandle SDL handle pointer */ - external fun dropSdlHandle(sdlHandle: Long); + external fun dropSdlHandle(sdlHandle: Long) /** * @param sdlHandle SDL handle pointer * @return window handle pointer */ - external fun initWindowHandle(sdlHandle: Long): Long; + external fun initWindowHandle(sdlHandle: Long): Long /** * @param windowHandle window handle pointer */ - external fun dropWindowHandle(windowHandle: Long); + external fun dropWindowHandle(windowHandle: Long) /** * @param windowHandle window handle pointer */ - external fun getGLVersion(windowHandle: Long): String; + external fun getGLVersion(windowHandle: Long): String /** * @param sdlHandle SDL handle pointer * @return the list of all MUI events in this frame */ - external fun sdlPoll(sdlHandle: Long): Array; + external fun sdlPoll(sdlHandle: Long): Array + + /** + * @param windowHandle window handle pointer + */ + external fun resizeGLViewport(windowHandle: Long) + + /** + * @param windowHandle window handle pointer + */ + external fun showWindow(windowHandle: Long) + + /** + * @param windowHandle window handle pointer + */ + external fun swapWindow(windowHandle: Long) + + /** + * @return Canvas handle pointer + */ + external fun initCanvasHandle(): Long + + /** + * @param canvasHandle Canvas handle pointer + */ + external fun dropCanvasHandle(canvasHandle: Long) + + /** + * @param canvasHandle Canvas handle pointer + * @param path path to RGB image + * @return Texture ID + */ + @JvmName("loadImageToCanvas") + external fun loadImageToCanvas(canvasHandle: Long, path: String): UInt + + /** + * @param canvasHandle Canvas handle pointer + * @param vsh path to vector shader + * @param fsh path to fragment shader + * @return Shader program ID + */ + @JvmName("shaders") + external fun shaders(canvasHandle: Long, vsh: String, fsh: String): UInt + + /** + * @param canvasHandle Canvas handle pointer + * @param shader Shader program ID + * @param texture Texture ID + */ + @JvmName("renderTexture") + external fun renderTexture(canvasHandle: Long, shader: UInt, texture: UInt) } diff --git a/src/kernel/client/kotlin/terramodulus/core/Main.kt b/src/kernel/client/kotlin/terramodulus/core/Main.kt index 137a381a..acfc5bc7 100644 --- a/src/kernel/client/kotlin/terramodulus/core/Main.kt +++ b/src/kernel/client/kotlin/terramodulus/core/Main.kt @@ -13,6 +13,7 @@ import joptsimple.ValueConversionException import joptsimple.ValueConverter import terramodulus.common.core.ApplicationArgumentParsingError import terramodulus.common.core.ApplicationInitializationFault +import terramodulus.common.core.run import terramodulus.common.core.setupInit import terramodulus.mui.GuiManager import terramodulus.util.exception.CodeLogicFault @@ -28,8 +29,8 @@ fun main(args: Array) { } catch (e: ApplicationArgumentParsingError) { triggerGlobalCrash(ApplicationInitializationFault(e)) } - GuiManager() -// run(TerraModulus()) + + run(TerraModulus()) } /** @@ -37,7 +38,7 @@ fun main(args: Array) { * should not be accessible normally. * Some features are then not used due to this, including but not limited to: * - [ValueConverter.valuePattern] - * - [jdk.internal.joptsimple.HelpFormatter] + * - [joptsimple.HelpFormatter] * - [OptionParser.printHelpOn] * * @throws ApplicationArgumentParsingError diff --git a/src/kernel/client/kotlin/terramodulus/core/TerraModulus.kt b/src/kernel/client/kotlin/terramodulus/core/TerraModulus.kt index b8f386f1..0b618258 100644 --- a/src/kernel/client/kotlin/terramodulus/core/TerraModulus.kt +++ b/src/kernel/client/kotlin/terramodulus/core/TerraModulus.kt @@ -6,14 +6,21 @@ package terramodulus.core import terramodulus.common.core.AbstractTerraModulus +import terramodulus.mui.GuiManager + +class TerraModulus internal constructor() : AbstractTerraModulus() { + private val guiManager = GuiManager() -class TerraModulus : AbstractTerraModulus() { override var tps: Int get() = TODO("Not yet implemented") set(value) {} override fun run() { - TODO("Not yet implemented") + guiManager.showWindow() + while (true) { + guiManager.updateCanvas() + Thread.sleep(1) + } } override fun close() { diff --git a/src/kernel/client/kotlin/terramodulus/mui/GuiManager.kt b/src/kernel/client/kotlin/terramodulus/mui/GuiManager.kt index a64cece4..56ce1a66 100644 --- a/src/kernel/client/kotlin/terramodulus/mui/GuiManager.kt +++ b/src/kernel/client/kotlin/terramodulus/mui/GuiManager.kt @@ -8,7 +8,15 @@ package terramodulus.mui import terramodulus.engine.MuiEvent import terramodulus.engine.Window import terramodulus.mui.gms.ScreenManager +import terramodulus.util.logging.logger import java.io.Closeable +import java.io.File + +private val logger = logger {} + +private fun getPathOfResource(path: String): String { + return File(object {}.javaClass.getResource(path)!!.toURI()).absolutePath +} class GuiManager internal constructor() : Closeable { private val window = Window() @@ -16,6 +24,10 @@ class GuiManager internal constructor() : Closeable { val renderSystem = RenderSystem() val inputSystem = InputSystem() val screenManager = ScreenManager() + val texture = canvas.loadImage(getPathOfResource("/test.png")) + val shader = canvas.loadShaders(getPathOfResource("/test.vsh"), getPathOfResource("/test.fsh")) + + internal fun showWindow() = window.show() /** * Screen updating, targeting twice as *maximum FPS*. @@ -29,72 +41,204 @@ class GuiManager internal constructor() : Closeable { internal fun updateCanvas() { window.pollEvents().forEach { event -> when (event) { - is MuiEvent.DisplayAdded -> TODO() - is MuiEvent.DisplayMoved -> TODO() - is MuiEvent.DisplayRemoved -> TODO() - MuiEvent.DropBegin -> TODO() - MuiEvent.DropComplete -> TODO() - is MuiEvent.DropFile -> TODO() - MuiEvent.DropPosition -> TODO() - is MuiEvent.DropText -> TODO() - is MuiEvent.GamepadAdded -> TODO() - is MuiEvent.GamepadAxisMotion -> TODO() - is MuiEvent.GamepadButtonDown -> TODO() - is MuiEvent.GamepadButtonUp -> TODO() - is MuiEvent.GamepadRemapped -> TODO() - is MuiEvent.GamepadRemoved -> TODO() - MuiEvent.GamepadSteamHandleUpdated -> TODO() - is MuiEvent.GamepadTouchpadDown -> TODO() - is MuiEvent.GamepadTouchpadMotion -> TODO() - is MuiEvent.GamepadTouchpadUp -> TODO() - is MuiEvent.JoystickAdded -> TODO() - is MuiEvent.JoystickAxisMotion -> TODO() - MuiEvent.JoystickBallMotion -> TODO() - MuiEvent.JoystickBatteryUpdated -> TODO() - is MuiEvent.JoystickButtonDown -> TODO() - is MuiEvent.JoystickButtonUp -> TODO() - is MuiEvent.JoystickHatMotion -> TODO() - is MuiEvent.JoystickRemoved -> TODO() - MuiEvent.KeyboardAdded -> TODO() - is MuiEvent.KeyboardKeyDown -> TODO() - is MuiEvent.KeyboardKeyUp -> TODO() - MuiEvent.KeyboardRemoved -> TODO() - MuiEvent.KeymapChanged -> TODO() - MuiEvent.MouseAdded -> TODO() - is MuiEvent.MouseButtonDown -> TODO() - is MuiEvent.MouseButtonUp -> TODO() - is MuiEvent.MouseMotion -> TODO() - MuiEvent.MouseRemoved -> TODO() - is MuiEvent.MouseWheel -> TODO() - MuiEvent.RenderDeviceLost -> TODO() - MuiEvent.RenderDeviceReset -> TODO() - MuiEvent.RenderTargetsReset -> TODO() - is MuiEvent.TextEditing -> TODO() - MuiEvent.TextEditingCandidates -> TODO() - is MuiEvent.TextInput -> TODO() - MuiEvent.WindowCloseRequested -> TODO() - MuiEvent.WindowDestroyed -> TODO() - MuiEvent.WindowEnterFullscreen -> TODO() - MuiEvent.WindowExposed -> TODO() - MuiEvent.WindowFocusGained -> TODO() - MuiEvent.WindowFocusLost -> TODO() - MuiEvent.WindowHdrStateChanged -> TODO() - MuiEvent.WindowHidden -> TODO() - MuiEvent.WindowIccProfChanged -> TODO() - MuiEvent.WindowLeaveFullscreen -> TODO() - MuiEvent.WindowMaximized -> TODO() - MuiEvent.WindowMetalViewResized -> TODO() - MuiEvent.WindowMinimized -> TODO() - MuiEvent.WindowMouseEnter -> TODO() - MuiEvent.WindowMouseLeave -> TODO() - is MuiEvent.WindowMoved -> TODO() - MuiEvent.WindowOccluded -> TODO() - is MuiEvent.WindowPixelSizeChanged -> TODO() - is MuiEvent.WindowResized -> TODO() - MuiEvent.WindowRestored -> TODO() - MuiEvent.WindowShown -> TODO() + is MuiEvent.DisplayAdded -> { + logger.debug { "Display added." } + } + is MuiEvent.DisplayMoved -> { + logger.debug { "Display moved." } + } + is MuiEvent.DisplayRemoved -> { + logger.debug { "Display removed." } + } + MuiEvent.DropBegin -> { + logger.debug { "Drop began." } + } + MuiEvent.DropComplete -> { + logger.debug { "Drop completed." } + } + is MuiEvent.DropFile -> { + logger.debug { "Dropped file \"${event.filename}\" on window." } + } + MuiEvent.DropPosition -> { + logger.debug { "Drop position." } + } + is MuiEvent.DropText -> { + logger.debug { "Dropped text \"${event.text}\" on window." } + } + is MuiEvent.GamepadAdded -> { + logger.debug { "Gamepad (id: ${event.joystickId}) added." } + } + is MuiEvent.GamepadAxisMotion -> { + logger.debug { "Gamepad (id: ${event.joystickId}) axis (${event.axis}) motion: ${event.value}" } + } + is MuiEvent.GamepadButtonDown -> { + logger.debug { "Gamepad (id: ${event.joystickId}) button (${event.button}) down." } + } + is MuiEvent.GamepadButtonUp -> { + logger.debug { "Gamepad (id: ${event.joystickId}) button (${event.button}) up." } + } + is MuiEvent.GamepadRemapped -> { + logger.debug { "Gamepad (id: ${event.joystickId}) remapped." } + } + is MuiEvent.GamepadRemoved -> { + logger.debug { "Gamepad (id: ${event.joystickId}) removed." } + } + MuiEvent.GamepadSteamHandleUpdated -> { + logger.debug { "Gamepad steam handle updated." } + } + is MuiEvent.GamepadTouchpadDown -> { + logger.debug { "Gamepad (id: ${event.joystickId}) touchpad (${event.touchpad}) down." } + } + is MuiEvent.GamepadTouchpadMotion -> { + logger.debug { "Gamepad (id: ${event.joystickId}) touchpad (${event.touchpad}) motion." } + } + is MuiEvent.GamepadTouchpadUp -> { + logger.debug { "Gamepad (id: ${event.joystickId}) touchpad (${event.touchpad}) up." } + } + is MuiEvent.JoystickAdded -> { + logger.debug { "Joystick (id: ${event.joystickId}) added." } + } + is MuiEvent.JoystickAxisMotion -> { + logger.debug { "Joystick (id: ${event.joystickId}) axis (${event.axis}) motion: ${event.value}" } + } + MuiEvent.JoystickBallMotion -> { + logger.debug { "Joystick ball motion." } + } + MuiEvent.JoystickBatteryUpdated -> { + logger.debug { "Joystick battery updated." } + } + is MuiEvent.JoystickButtonDown -> { + logger.debug { "Joystick (id: ${event.joystickId}) button (${event.button}) down." } + } + is MuiEvent.JoystickButtonUp -> { + logger.debug { "Joystick (id: ${event.joystickId}) button (${event.button}) up." } + } + is MuiEvent.JoystickHatMotion -> { + logger.debug { "Joystick (id: ${event.joystickId}) hat (${event.hat}) motion: ${event.value}." } + } + is MuiEvent.JoystickRemoved -> { + logger.debug { "Joystick (id: ${event.joystickId}) removed." } + } + MuiEvent.KeyboardAdded -> { + logger.debug { "Keyboard added." } + } + is MuiEvent.KeyboardKeyDown -> { + logger.debug { "Keyboard (id: ${event.keyboardId}) key `${event.key}` down." } + } + is MuiEvent.KeyboardKeyUp -> { + logger.debug { "Keyboard (id: ${event.keyboardId}) key `${event.key}` up." } + } + MuiEvent.KeyboardRemoved -> { + logger.debug { "Keyboard removed." } + } + MuiEvent.KeymapChanged -> { + logger.debug { "Keyboard keymap changed." } + } + MuiEvent.MouseAdded -> { + logger.debug { "Mouse added." } + } + is MuiEvent.MouseButtonDown -> { + logger.debug { "Mouse (id: ${event.mouseId}) key `${event.key}` down." } + } + is MuiEvent.MouseButtonUp -> { + logger.debug { "Mouse (id: ${event.mouseId}) key `${event.key}` up." } + } + is MuiEvent.MouseMotion -> { + logger.debug { "Mouse (id: ${event.mouseId}) motion (${event.x}, ${event.y})." } + } + MuiEvent.MouseRemoved -> { + logger.debug { "Mouse removed." } + } + is MuiEvent.MouseWheel -> { + logger.debug { "Mouse (id: ${event.mouseId}) wheel (${event.x}, ${event.y})." } + } + MuiEvent.RenderDeviceLost -> { + logger.debug { "Render device lost." } + } + MuiEvent.RenderDeviceReset -> { + logger.debug { "Render device reset." } + } + MuiEvent.RenderTargetsReset -> { + logger.debug { "Render targets reset." } + } + is MuiEvent.TextEditing -> { + logger.debug { "Text editing at ${event.start} with length ${event.length}." } + } + MuiEvent.TextEditingCandidates -> { + logger.debug { "Text editing candidates." } + } + is MuiEvent.TextInput -> { + logger.debug { "Text input." } + } + MuiEvent.WindowCloseRequested -> { + logger.debug { "Window close requested." } + } + MuiEvent.WindowDestroyed -> { + logger.debug { "Window destroyed." } + } + MuiEvent.WindowEnterFullscreen -> { + logger.debug { "Window entered fullscreen." } + } + MuiEvent.WindowExposed -> { + logger.debug { "Window exposed." } + } + MuiEvent.WindowFocusGained -> { + logger.debug { "Window focus gained." } + } + MuiEvent.WindowFocusLost -> { + logger.debug { "Window focus lost." } + } + MuiEvent.WindowHdrStateChanged -> { + logger.debug { "Window HDR state changed." } + } + MuiEvent.WindowHidden -> { + logger.debug { "Window hidden." } + } + MuiEvent.WindowIccProfChanged -> { + logger.debug { "Window ICC profile changed." } + } + MuiEvent.WindowLeaveFullscreen -> { + logger.debug { "Window left fullscreen." } + } + MuiEvent.WindowMaximized -> { + logger.debug { "Window maximized." } + } + MuiEvent.WindowMetalViewResized -> { + logger.debug { "Window metal view resized." } + } + MuiEvent.WindowMinimized -> { + logger.debug { "Window minimized." } + } + MuiEvent.WindowMouseEnter -> { + logger.debug { "Mouse entered window." } + } + MuiEvent.WindowMouseLeave -> { + logger.debug { "Mouse left window." } + } + is MuiEvent.WindowMoved -> { + logger.debug { "Window moved to (${event.x}, ${event.y})." } + } + MuiEvent.WindowOccluded -> { + logger.debug { "Window occluded." } + } + is MuiEvent.WindowPixelSizeChanged -> { + logger.debug { "Window pixel size changed to ${event.width}x${event.height}." } + window.resizeGLViewport() + logger.debug { "Window viewport resized." } + } + is MuiEvent.WindowResized -> { + logger.debug { "Window resized to ${event.width}x${event.height}." } + } + MuiEvent.WindowRestored -> { + logger.debug { "Window restored." } + } + MuiEvent.WindowShown -> { + logger.debug { "Window shown." } + } } } + canvas.renderImage(shader, texture) + window.swap() } override fun close() { diff --git a/src/kernel/client/resources/test.fsh b/src/kernel/client/resources/test.fsh new file mode 100644 index 00000000..75c6bec8 --- /dev/null +++ b/src/kernel/client/resources/test.fsh @@ -0,0 +1,8 @@ +#version 110 + +uniform sampler2D tex; // The 2D texture +varying vec2 texCoord; // Interpolated texture coordinates + +void main() { + gl_FragColor = texture2D(tex, texCoord); +} diff --git a/src/kernel/client/resources/test.png b/src/kernel/client/resources/test.png new file mode 100644 index 0000000000000000000000000000000000000000..fd357f990ebcb87f132bb095b753ac32d372ac48 GIT binary patch literal 6602 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 Date: Tue, 10 Jun 2025 07:35:41 +0800 Subject: [PATCH 15/19] MUI: Expanding functionalities Various changes; still planning --- ferricia | 2 +- .../kotlin/terramodulus/engine/Canvas.kt | 20 +++- .../kotlin/terramodulus/engine/ColorFilter.kt | 9 ++ .../kotlin/terramodulus/engine/Drawable.kt | 9 ++ .../kotlin/terramodulus/engine/GeoDrawable.kt | 9 ++ .../terramodulus/engine/ModelTransform.kt | 9 ++ .../terramodulus/engine/SimpleLineGeom.kt | 13 +++ .../terramodulus/engine/SmartScaling.kt | 11 +++ .../kotlin/terramodulus/engine/SpriteMesh.kt | 12 +++ .../kotlin/terramodulus/engine/TexDrawable.kt | 9 ++ .../terramodulus/engine/ferricia/Mui.kt | 93 +++++++++++++++---- .../common/kotlin/terramodulus/engine/Core.kt | 6 +- .../terramodulus/engine/ferricia/Core.kt | 8 ++ .../terramodulus/engine/ferricia/Demo.kt | 11 --- .../kotlin/terramodulus/mui/AuiManager.kt | 7 +- .../kotlin/terramodulus/mui/GuiManager.kt | 18 ++-- .../kotlin/terramodulus/mui/RenderSystem.kt | 15 --- .../mui/{ => audio}/AudioSystem.kt | 2 +- .../kotlin/terramodulus/mui/gfx/Anchor.kt | 26 ++++++ .../kotlin/terramodulus/mui/gfx/Direction.kt | 75 +++++++++++++++ .../kotlin/terramodulus/mui/gfx/GuiSprite.kt | 14 +++ .../terramodulus/mui/gfx/GuiTransform.kt | 17 ++++ .../kotlin/terramodulus/mui/gfx/Rectangle.kt | 82 ++++++++++++++++ .../terramodulus/mui/gfx/RenderSystem.kt | 25 +++++ .../kotlin/terramodulus/mui/gfx/Vector2.kt | 25 +++++ .../kotlin/terramodulus/mui/gms/Component.kt | 4 + .../kotlin/terramodulus/mui/gms/Menu.kt | 40 ++++++++ .../kotlin/terramodulus/mui/gms/Screen.kt | 73 +++++++++++++++ .../terramodulus/mui/gms/ScreenManager.kt | 50 +++++++--- .../mui/gms/impl/LaunchingScreen.kt | 18 ++++ .../mui/gms/impl/ResourceLoadingScreen.kt | 14 +++ .../mui/gms/impl/SpriteComponent.kt | 17 ++++ .../mui/{ => input}/InputSystem.kt | 2 +- src/kernel/client/resources/gms_geo.fsh | 7 ++ src/kernel/client/resources/gms_geo.vsh | 14 +++ src/kernel/client/resources/gms_tex.fsh | 10 ++ src/kernel/client/resources/gms_tex.vsh | 15 +++ src/kernel/client/resources/test.fsh | 8 -- src/kernel/client/resources/test.vsh | 11 --- 39 files changed, 717 insertions(+), 93 deletions(-) create mode 100644 src/internal/client/kotlin/terramodulus/engine/ColorFilter.kt create mode 100644 src/internal/client/kotlin/terramodulus/engine/Drawable.kt create mode 100644 src/internal/client/kotlin/terramodulus/engine/GeoDrawable.kt create mode 100644 src/internal/client/kotlin/terramodulus/engine/ModelTransform.kt create mode 100644 src/internal/client/kotlin/terramodulus/engine/SimpleLineGeom.kt create mode 100644 src/internal/client/kotlin/terramodulus/engine/SmartScaling.kt create mode 100644 src/internal/client/kotlin/terramodulus/engine/SpriteMesh.kt create mode 100644 src/internal/client/kotlin/terramodulus/engine/TexDrawable.kt delete mode 100644 src/internal/common/kotlin/terramodulus/engine/ferricia/Demo.kt delete mode 100644 src/kernel/client/kotlin/terramodulus/mui/RenderSystem.kt rename src/kernel/client/kotlin/terramodulus/mui/{ => audio}/AudioSystem.kt (84%) create mode 100644 src/kernel/client/kotlin/terramodulus/mui/gfx/Anchor.kt create mode 100644 src/kernel/client/kotlin/terramodulus/mui/gfx/Direction.kt create mode 100644 src/kernel/client/kotlin/terramodulus/mui/gfx/GuiSprite.kt create mode 100644 src/kernel/client/kotlin/terramodulus/mui/gfx/GuiTransform.kt create mode 100644 src/kernel/client/kotlin/terramodulus/mui/gfx/Rectangle.kt create mode 100644 src/kernel/client/kotlin/terramodulus/mui/gfx/RenderSystem.kt create mode 100644 src/kernel/client/kotlin/terramodulus/mui/gfx/Vector2.kt create mode 100644 src/kernel/client/kotlin/terramodulus/mui/gms/impl/LaunchingScreen.kt create mode 100644 src/kernel/client/kotlin/terramodulus/mui/gms/impl/ResourceLoadingScreen.kt create mode 100644 src/kernel/client/kotlin/terramodulus/mui/gms/impl/SpriteComponent.kt rename src/kernel/client/kotlin/terramodulus/mui/{ => input}/InputSystem.kt (84%) create mode 100644 src/kernel/client/resources/gms_geo.fsh create mode 100644 src/kernel/client/resources/gms_geo.vsh create mode 100644 src/kernel/client/resources/gms_tex.fsh create mode 100644 src/kernel/client/resources/gms_tex.vsh delete mode 100644 src/kernel/client/resources/test.fsh delete mode 100644 src/kernel/client/resources/test.vsh diff --git a/ferricia b/ferricia index 10392430..62ffea6e 160000 --- a/ferricia +++ b/ferricia @@ -1 +1 @@ -Subproject commit 10392430c3619c5a811a5d0b82df836c333b1795 +Subproject commit 62ffea6eeb154019048d9a5dd6aed69f9025721a diff --git a/src/internal/client/kotlin/terramodulus/engine/Canvas.kt b/src/internal/client/kotlin/terramodulus/engine/Canvas.kt index 5febe1c1..40b4f293 100644 --- a/src/internal/client/kotlin/terramodulus/engine/Canvas.kt +++ b/src/internal/client/kotlin/terramodulus/engine/Canvas.kt @@ -5,13 +5,15 @@ package terramodulus.engine +import terramodulus.engine.ferricia.Mui.drawGuiGeo +import terramodulus.engine.ferricia.Mui.drawGuiTex import terramodulus.engine.ferricia.Mui.dropCanvasHandle +import terramodulus.engine.ferricia.Mui.geoShaders import terramodulus.engine.ferricia.Mui.getGLVersion import terramodulus.engine.ferricia.Mui.initCanvasHandle import terramodulus.engine.ferricia.Mui.loadImageToCanvas import terramodulus.engine.ferricia.Mui.renderTexture -import terramodulus.engine.ferricia.Mui.shaders -import java.awt.Image +import terramodulus.engine.ferricia.Mui.texShaders import java.io.Closeable /** @@ -19,16 +21,24 @@ import java.io.Closeable * * This manages GL viewport in the SDL window and rendering in the viewport. */ -class Canvas internal constructor(private val windowHandle: Long) : Closeable { +class Canvas internal constructor(private val windowHandle: ULong) : Closeable { + private val canvasHandle = initCanvasHandle(windowHandle) val glVersion = getGLVersion(windowHandle) - val canvasHandle = initCanvasHandle() fun loadImage(path: String) = loadImageToCanvas(canvasHandle, path) - fun loadShaders(vsh: String, fsh: String) = shaders(canvasHandle, vsh, fsh) + fun loadGeoShaders(vsh: String, fsh: String) = geoShaders(vsh, fsh) + + fun loadTexShaders(vsh: String, fsh: String) = texShaders(vsh, fsh) fun renderImage(shader: UInt, texture: UInt) = renderTexture(canvasHandle, shader, texture) + fun drawGuiGeoObj(drawable: GeoDrawable, programHandle: ULong) = + drawGuiGeo(canvasHandle, drawable.handle, programHandle) + + fun drawGuiTexObj(drawable: TexDrawable, programHandle: ULong, textureHandle: UInt) = + drawGuiTex(canvasHandle, drawable.handle, programHandle, textureHandle) + override fun close() { dropCanvasHandle(canvasHandle) } diff --git a/src/internal/client/kotlin/terramodulus/engine/ColorFilter.kt b/src/internal/client/kotlin/terramodulus/engine/ColorFilter.kt new file mode 100644 index 00000000..04e786f8 --- /dev/null +++ b/src/internal/client/kotlin/terramodulus/engine/ColorFilter.kt @@ -0,0 +1,9 @@ +/* + * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package terramodulus.engine + +sealed class ColorFilter { +} diff --git a/src/internal/client/kotlin/terramodulus/engine/Drawable.kt b/src/internal/client/kotlin/terramodulus/engine/Drawable.kt new file mode 100644 index 00000000..541729a5 --- /dev/null +++ b/src/internal/client/kotlin/terramodulus/engine/Drawable.kt @@ -0,0 +1,9 @@ +/* + * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package terramodulus.engine + +sealed class Drawable(internal val handle: ULong) { +} diff --git a/src/internal/client/kotlin/terramodulus/engine/GeoDrawable.kt b/src/internal/client/kotlin/terramodulus/engine/GeoDrawable.kt new file mode 100644 index 00000000..cb4167f9 --- /dev/null +++ b/src/internal/client/kotlin/terramodulus/engine/GeoDrawable.kt @@ -0,0 +1,9 @@ +/* + * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package terramodulus.engine + +sealed class GeoDrawable(handle: ULong) : Drawable(handle) { +} diff --git a/src/internal/client/kotlin/terramodulus/engine/ModelTransform.kt b/src/internal/client/kotlin/terramodulus/engine/ModelTransform.kt new file mode 100644 index 00000000..3386f135 --- /dev/null +++ b/src/internal/client/kotlin/terramodulus/engine/ModelTransform.kt @@ -0,0 +1,9 @@ +/* + * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package terramodulus.engine + +sealed class ModelTransform(internal val handle: ULong) { +} diff --git a/src/internal/client/kotlin/terramodulus/engine/SimpleLineGeom.kt b/src/internal/client/kotlin/terramodulus/engine/SimpleLineGeom.kt new file mode 100644 index 00000000..ce53260d --- /dev/null +++ b/src/internal/client/kotlin/terramodulus/engine/SimpleLineGeom.kt @@ -0,0 +1,13 @@ +/* + * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package terramodulus.engine + +import terramodulus.engine.ferricia.Mui.newSimpleLineGeom + +class SimpleLineGeom(x0: Int, y0: Int, x1: Int, y1: Int, r: Int, g: Int, b: Int, a: Int) : + GeoDrawable(newSimpleLineGeom(arrayOf(x0, y0, x1, y1, r, g, b, a))) { + +} diff --git a/src/internal/client/kotlin/terramodulus/engine/SmartScaling.kt b/src/internal/client/kotlin/terramodulus/engine/SmartScaling.kt new file mode 100644 index 00000000..766c8221 --- /dev/null +++ b/src/internal/client/kotlin/terramodulus/engine/SmartScaling.kt @@ -0,0 +1,11 @@ +/* + * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package terramodulus.engine + +import terramodulus.engine.ferricia.Mui.modelSmartScaling + +class SmartScaling(w: Int, h: Int) : ModelTransform(modelSmartScaling(arrayOf(w, h))) { +} diff --git a/src/internal/client/kotlin/terramodulus/engine/SpriteMesh.kt b/src/internal/client/kotlin/terramodulus/engine/SpriteMesh.kt new file mode 100644 index 00000000..e7f40c8c --- /dev/null +++ b/src/internal/client/kotlin/terramodulus/engine/SpriteMesh.kt @@ -0,0 +1,12 @@ +/* + * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package terramodulus.engine + +import terramodulus.engine.ferricia.Mui.newSpriteMesh + +class SpriteMesh(x0: Int, y0: Int, x1: Int, y1: Int) : TexDrawable(newSpriteMesh(arrayOf(x0, y0, x1, y1))) { + +} diff --git a/src/internal/client/kotlin/terramodulus/engine/TexDrawable.kt b/src/internal/client/kotlin/terramodulus/engine/TexDrawable.kt new file mode 100644 index 00000000..794d2160 --- /dev/null +++ b/src/internal/client/kotlin/terramodulus/engine/TexDrawable.kt @@ -0,0 +1,9 @@ +/* + * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package terramodulus.engine + +sealed class TexDrawable(handle: ULong) : Drawable(handle) { +} diff --git a/src/internal/client/kotlin/terramodulus/engine/ferricia/Mui.kt b/src/internal/client/kotlin/terramodulus/engine/ferricia/Mui.kt index 3af737b0..9bffa0b4 100644 --- a/src/internal/client/kotlin/terramodulus/engine/ferricia/Mui.kt +++ b/src/internal/client/kotlin/terramodulus/engine/ferricia/Mui.kt @@ -11,59 +11,60 @@ internal object Mui { /** * @return SDL handle pointer */ - external fun initSdlHandle(): Long + external fun initSdlHandle(): ULong /** * @param sdlHandle SDL handle pointer */ - external fun dropSdlHandle(sdlHandle: Long) + external fun dropSdlHandle(sdlHandle: ULong) /** * @param sdlHandle SDL handle pointer * @return window handle pointer */ - external fun initWindowHandle(sdlHandle: Long): Long + external fun initWindowHandle(sdlHandle: ULong): ULong /** * @param windowHandle window handle pointer */ - external fun dropWindowHandle(windowHandle: Long) + external fun dropWindowHandle(windowHandle: ULong) /** * @param windowHandle window handle pointer */ - external fun getGLVersion(windowHandle: Long): String + external fun getGLVersion(windowHandle: ULong): String /** * @param sdlHandle SDL handle pointer * @return the list of all MUI events in this frame */ - external fun sdlPoll(sdlHandle: Long): Array + external fun sdlPoll(sdlHandle: ULong): Array /** * @param windowHandle window handle pointer */ - external fun resizeGLViewport(windowHandle: Long) + external fun resizeGLViewport(windowHandle: ULong) /** * @param windowHandle window handle pointer */ - external fun showWindow(windowHandle: Long) + external fun showWindow(windowHandle: ULong) /** * @param windowHandle window handle pointer */ - external fun swapWindow(windowHandle: Long) + external fun swapWindow(windowHandle: ULong) /** + * @param windowHandle window handle pointer * @return Canvas handle pointer */ - external fun initCanvasHandle(): Long + external fun initCanvasHandle(windowHandle: ULong): ULong /** * @param canvasHandle Canvas handle pointer */ - external fun dropCanvasHandle(canvasHandle: Long) + external fun dropCanvasHandle(canvasHandle: ULong) /** * @param canvasHandle Canvas handle pointer @@ -71,16 +72,23 @@ internal object Mui { * @return Texture ID */ @JvmName("loadImageToCanvas") - external fun loadImageToCanvas(canvasHandle: Long, path: String): UInt + external fun loadImageToCanvas(canvasHandle: ULong, path: String): UInt + + /** + * @param vsh path to vector shader + * @param fsh path to fragment shader + * @return Geo Shader Program handle pointer + */ + @JvmName("geoShaders") + external fun geoShaders(vsh: String, fsh: String): ULong /** - * @param canvasHandle Canvas handle pointer * @param vsh path to vector shader * @param fsh path to fragment shader - * @return Shader program ID + * @return Tex Shader Program handle pointer */ - @JvmName("shaders") - external fun shaders(canvasHandle: Long, vsh: String, fsh: String): UInt + @JvmName("texShaders") + external fun texShaders(vsh: String, fsh: String): ULong /** * @param canvasHandle Canvas handle pointer @@ -88,5 +96,56 @@ internal object Mui { * @param texture Texture ID */ @JvmName("renderTexture") - external fun renderTexture(canvasHandle: Long, shader: UInt, texture: UInt) + external fun renderTexture(canvasHandle: ULong, shader: UInt, texture: UInt) + + /** + * @param data `[x0, y0, x1, y1, r, g, b, a]` + * @return SimpleLineGeom handle pointer + */ + @JvmName("newSimpleLineGeom") + external fun newSimpleLineGeom(data: Array): ULong + + /** + * @param data `[x0, y0, x1, y1]` + * @return SpriteMesh as DrawableSet handle pointer + */ + @JvmName("newSpriteMesh") + external fun newSpriteMesh(data: Array): ULong + + /** + * @param data `[w, h]` + * @return SmartScaling handle pointer + */ + @JvmName("modelSmartScaling") + external fun modelSmartScaling(data: Array): ULong + + /** + * @param drawableHandle DrawableSet handle pointer + * @param modelHandle Model Transform handle pointer + */ + @JvmName("addSmartScaling") + external fun addSmartScaling(drawableHandle: ULong, modelHandle: ULong) + + /** + * @param drawableHandle DrawableSet handle pointer + * @param modelHandle Model Transform handle pointer + */ + @JvmName("removeSmartScaling") + external fun removeSmartScaling(drawableHandle: ULong, modelHandle: ULong) + + /** + * @param canvasHandle Canvas handle pointer + * @param drawableHandle DrawableSet handle pointer + * @param programHandle Geo Shader Program handle pointer + */ + @JvmName("drawGuiGeo") + external fun drawGuiGeo(canvasHandle: ULong, drawableHandle: ULong, programHandle: ULong) + + /** + * @param canvasHandle Canvas handle pointer + * @param drawableHandle DrawableSet handle pointer + * @param programHandle Tex Shader Program handle pointer + */ + @JvmName("drawGuiTex") + external fun drawGuiTex(canvasHandle: ULong, drawableHandle: ULong, programHandle: ULong, textureHandle: UInt) } diff --git a/src/internal/common/kotlin/terramodulus/engine/Core.kt b/src/internal/common/kotlin/terramodulus/engine/Core.kt index 87664233..fbd31b6f 100644 --- a/src/internal/common/kotlin/terramodulus/engine/Core.kt +++ b/src/internal/common/kotlin/terramodulus/engine/Core.kt @@ -5,6 +5,10 @@ package terramodulus.engine +import terramodulus.engine.ferricia.Core import terramodulus.engine.ferricia.loadLibrary -fun initEngine() = loadLibrary() +fun initEngine() { + loadLibrary() + Core.init() +} diff --git a/src/internal/common/kotlin/terramodulus/engine/ferricia/Core.kt b/src/internal/common/kotlin/terramodulus/engine/ferricia/Core.kt index cb41c29e..62d5967e 100644 --- a/src/internal/common/kotlin/terramodulus/engine/ferricia/Core.kt +++ b/src/internal/common/kotlin/terramodulus/engine/ferricia/Core.kt @@ -16,3 +16,11 @@ internal fun loadLibrary() { System.loadLibrary("ferricia") } + +internal object Core { + /** + * Initializes the Engine handle. + */ + @JvmName("init") + external fun init() +} diff --git a/src/internal/common/kotlin/terramodulus/engine/ferricia/Demo.kt b/src/internal/common/kotlin/terramodulus/engine/ferricia/Demo.kt deleted file mode 100644 index 62831931..00000000 --- a/src/internal/common/kotlin/terramodulus/engine/ferricia/Demo.kt +++ /dev/null @@ -1,11 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors - * SPDX-License-Identifier: LGPL-3.0-only - */ - -package terramodulus.engine.ferricia - -object Demo { - external fun hello(name: String): String - external fun clientOnly(): Int -} diff --git a/src/kernel/client/kotlin/terramodulus/mui/AuiManager.kt b/src/kernel/client/kotlin/terramodulus/mui/AuiManager.kt index f0ea126a..3e3ae08d 100644 --- a/src/kernel/client/kotlin/terramodulus/mui/AuiManager.kt +++ b/src/kernel/client/kotlin/terramodulus/mui/AuiManager.kt @@ -5,6 +5,11 @@ package terramodulus.mui -class AuiManager internal constructor() { +import terramodulus.mui.audio.AudioSystem + +/** + * Audio User Interface (AUI) Manager + */ +internal class AuiManager internal constructor() { val audioSystem = AudioSystem() } diff --git a/src/kernel/client/kotlin/terramodulus/mui/GuiManager.kt b/src/kernel/client/kotlin/terramodulus/mui/GuiManager.kt index 56ce1a66..2dd9180d 100644 --- a/src/kernel/client/kotlin/terramodulus/mui/GuiManager.kt +++ b/src/kernel/client/kotlin/terramodulus/mui/GuiManager.kt @@ -7,25 +7,23 @@ package terramodulus.mui import terramodulus.engine.MuiEvent import terramodulus.engine.Window +import terramodulus.mui.gfx.RenderSystem import terramodulus.mui.gms.ScreenManager +import terramodulus.mui.input.InputSystem import terramodulus.util.logging.logger import java.io.Closeable import java.io.File private val logger = logger {} -private fun getPathOfResource(path: String): String { - return File(object {}.javaClass.getResource(path)!!.toURI()).absolutePath -} - -class GuiManager internal constructor() : Closeable { +/** + * Graphical User Interface (GUI) Manager + */ +internal class GuiManager internal constructor() : Closeable { private val window = Window() - private val canvas = window.canvas - val renderSystem = RenderSystem() + val renderSystem = RenderSystem(window.canvas) val inputSystem = InputSystem() val screenManager = ScreenManager() - val texture = canvas.loadImage(getPathOfResource("/test.png")) - val shader = canvas.loadShaders(getPathOfResource("/test.vsh"), getPathOfResource("/test.fsh")) internal fun showWindow() = window.show() @@ -237,7 +235,7 @@ class GuiManager internal constructor() : Closeable { } } } - canvas.renderImage(shader, texture) + renderSystem.render() window.swap() } diff --git a/src/kernel/client/kotlin/terramodulus/mui/RenderSystem.kt b/src/kernel/client/kotlin/terramodulus/mui/RenderSystem.kt deleted file mode 100644 index 035a7496..00000000 --- a/src/kernel/client/kotlin/terramodulus/mui/RenderSystem.kt +++ /dev/null @@ -1,15 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors - * SPDX-License-Identifier: LGPL-3.0-only - */ - -package terramodulus.mui - -import terramodulus.engine.Canvas - -class RenderSystem internal constructor() { - - fun render(canvas: Canvas) { - - } -} diff --git a/src/kernel/client/kotlin/terramodulus/mui/AudioSystem.kt b/src/kernel/client/kotlin/terramodulus/mui/audio/AudioSystem.kt similarity index 84% rename from src/kernel/client/kotlin/terramodulus/mui/AudioSystem.kt rename to src/kernel/client/kotlin/terramodulus/mui/audio/AudioSystem.kt index b598c87a..0c05c0f3 100644 --- a/src/kernel/client/kotlin/terramodulus/mui/AudioSystem.kt +++ b/src/kernel/client/kotlin/terramodulus/mui/audio/AudioSystem.kt @@ -3,7 +3,7 @@ * SPDX-License-Identifier: LGPL-3.0-only */ -package terramodulus.mui +package terramodulus.mui.audio class AudioSystem internal constructor() { } diff --git a/src/kernel/client/kotlin/terramodulus/mui/gfx/Anchor.kt b/src/kernel/client/kotlin/terramodulus/mui/gfx/Anchor.kt new file mode 100644 index 00000000..6065fbd6 --- /dev/null +++ b/src/kernel/client/kotlin/terramodulus/mui/gfx/Anchor.kt @@ -0,0 +1,26 @@ +/* + * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package terramodulus.mui.gfx + +/* + * Every set of anchor position directions has different meanings in different context, + * They are kept separated for context and literal clarity and organization. + * Those should be used with care since they are not already interconvertible. + */ + +/** + * Set of 4 anchor position directions + */ +enum class Anchor4 { + TopLeft, TopRight, BottomLeft, BottomRight; +} + +/** + * Set of 5 anchor position directions + */ +enum class Anchor5 { + TopLeft, TopRight, BottomLeft, BottomRight, Center; +} diff --git a/src/kernel/client/kotlin/terramodulus/mui/gfx/Direction.kt b/src/kernel/client/kotlin/terramodulus/mui/gfx/Direction.kt new file mode 100644 index 00000000..867394e8 --- /dev/null +++ b/src/kernel/client/kotlin/terramodulus/mui/gfx/Direction.kt @@ -0,0 +1,75 @@ +/* + * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package terramodulus.mui.gfx + +/* + * Every set of directions has different meanings in different context, + * They are kept separated for context and literal clarity and organization. + * Those should be used with care since they are not already interconvertible. + */ + +/** + * Set of 4 compass directions. + */ +enum class Direction4C { + North, South, West, East; +} + +/** + * Set of 4 vertical directions. + */ +enum class Direction4V { + Left, Right, Up, Down; +} + +/** + * Set of 4 horizontal directions. + */ +enum class Direction4H { + Front, Back, Left, Right; +} + +/** + * Set of 4 axial directions. + */ +enum class Direction4A { + XPos, XNeg, YPos, YNeg; +} + +/** + * Set of 8 compass directions. + */ +enum class Direction8C { + North, South, West, East, NorthWest, NorthEast, SouthWest, SouthEast; +} + +/** + * Set of 8 vertical directions. + */ +enum class Direction8V { + Left, Right, Up, Down, UpLeft, UpRight, DownLeft, DownRight; +} + +/** + * Set of 8 horizontal directions. + */ +enum class Direction8H { + Front, Back, Left, Right, FrontLeft, FrontRight, BackLeft, BackRight; +} + +/** + * Set of 6 3D-relative directions. + */ +enum class Direction6R { + Up, Down, Left, Right, Front, Back; +} + +/** + * Set of 6 3D-compass directions. + */ +enum class Direction6C { + North, South, West, East, Up, Down; +} diff --git a/src/kernel/client/kotlin/terramodulus/mui/gfx/GuiSprite.kt b/src/kernel/client/kotlin/terramodulus/mui/gfx/GuiSprite.kt new file mode 100644 index 00000000..f52f6c13 --- /dev/null +++ b/src/kernel/client/kotlin/terramodulus/mui/gfx/GuiSprite.kt @@ -0,0 +1,14 @@ +/* + * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package terramodulus.mui.gfx + +import terramodulus.engine.SpriteMesh + +class GuiSprite(private val rect: RectangleI) { + private val mesh = SpriteMesh(rect.x, rect.y, rect.x + rect.width, rect.y + rect.height) + + +} diff --git a/src/kernel/client/kotlin/terramodulus/mui/gfx/GuiTransform.kt b/src/kernel/client/kotlin/terramodulus/mui/gfx/GuiTransform.kt new file mode 100644 index 00000000..2ece03dd --- /dev/null +++ b/src/kernel/client/kotlin/terramodulus/mui/gfx/GuiTransform.kt @@ -0,0 +1,17 @@ +/* + * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package terramodulus.mui.gfx + +import terramodulus.engine.ModelTransform +import terramodulus.engine.SmartScaling + +class GuiTransform private constructor(private val handle: ModelTransform) { + companion object { + fun smartScaling(w: Int, h: Int) = GuiTransform(SmartScaling(w, h)) + } + + fun add +} diff --git a/src/kernel/client/kotlin/terramodulus/mui/gfx/Rectangle.kt b/src/kernel/client/kotlin/terramodulus/mui/gfx/Rectangle.kt new file mode 100644 index 00000000..e5cf9d21 --- /dev/null +++ b/src/kernel/client/kotlin/terramodulus/mui/gfx/Rectangle.kt @@ -0,0 +1,82 @@ +/* + * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package terramodulus.mui.gfx + +/** + * Rectangle in a coordinate system with (0, 0) on the bottom left. + * The anchor of the rectangle is the bottom-left corner. + */ +sealed interface Rectangle { + val x: Int + val y: Int + val width: Int + val height: Int + + fun anchor(pos: Anchor5): Vector2 + + fun translateTo(pos: Vector2): Rectangle + + fun translateToX(x: T): Rectangle + + fun translateToY(y: T): Rectangle + + fun translateByX(x: T): Rectangle + + fun translateByY(y: T): Rectangle + + fun translateBy(pos: Vector2): Rectangle +} + +data class RectangleI( + val x: Int, + val y: Int, + val width: Int, + val height: Int +) { + companion object { + fun withPoints(x0: Int, y0: Int, x1: Int, y1: Int): RectangleI { + val minX: Int?; + val maxX: Int?; + if (x0 < x1) { + minX = x0; + maxX = x1; + } else { + maxX = x0; + minX = x1; + } + val minY: Int?; + val maxY: Int?; + if (y0 < y1) { + minY = y0; + maxY = y1; + } else { + maxY = y0; + minY = y1; + } + return RectangleI(minX, maxX, minY, maxY) + } + } + + fun anchor(pos: Anchor5) = when (pos) { + Anchor5.TopLeft -> Vector2I(x, y + width) + Anchor5.TopRight -> Vector2I(x + width, y + height) + Anchor5.BottomLeft -> Vector2I(x, y) + Anchor5.BottomRight -> Vector2I(x + width, y) + Anchor5.Center -> Vector2I(x + width / 2, y + height / 2) + } + + fun translateBy(pos: Vector2I) = RectangleI(x, y, width, height) + + fun translateByY(y: Int) = RectangleI(x, this.y + y, width, height) + + fun translateByX(x: Int) = RectangleI(this.x + x, y, width, height) + + fun translateToY(y: Int) = RectangleI(x, y, width, height) + + fun translateToX(x: Int) = RectangleI(x, y, width, height) + + fun translateTo(pos: Vector2I) = RectangleI(pos.x, pos.y, width, height) +} diff --git a/src/kernel/client/kotlin/terramodulus/mui/gfx/RenderSystem.kt b/src/kernel/client/kotlin/terramodulus/mui/gfx/RenderSystem.kt new file mode 100644 index 00000000..39ebf161 --- /dev/null +++ b/src/kernel/client/kotlin/terramodulus/mui/gfx/RenderSystem.kt @@ -0,0 +1,25 @@ +/* + * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package terramodulus.mui.gfx + +import terramodulus.engine.Canvas +import java.io.File + +private fun getPathOfResource(path: String): String { + return File(object {}.javaClass.getResource(path)!!.toURI()).absolutePath +} + +class RenderSystem internal constructor(private val canvas: Canvas) { + private val texture = canvas.loadImage(getPathOfResource("/test.png")) + private val shader = canvas.loadShaders( + getPathOfResource("/gms_tex.vsh"), + getPathOfResource("/gms_tex.fsh") + ) + + internal fun render() { + + } +} diff --git a/src/kernel/client/kotlin/terramodulus/mui/gfx/Vector2.kt b/src/kernel/client/kotlin/terramodulus/mui/gfx/Vector2.kt new file mode 100644 index 00000000..c4b678c3 --- /dev/null +++ b/src/kernel/client/kotlin/terramodulus/mui/gfx/Vector2.kt @@ -0,0 +1,25 @@ +/* + * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package terramodulus.mui.gfx + +sealed interface Vector2 { + val x: T + val y: T + + operator fun plus(other: Vector2): Vector2 +} + +data class Vector2I(val x: Int, val y: Int) { + fun plus(other: Vector2I) = Vector2I(x + other.x, y + other.y) +} + +data class Vector2D(val x: Double, val y: Double) { + fun plus(other: Vector2D) = Vector2D(x + other.x, y + other.y) +} + +data class Vector2F(val x: Float, val y: Float) { + fun plus(other: Vector2F) = Vector2F(x + other.x, y + other.y) +} diff --git a/src/kernel/client/kotlin/terramodulus/mui/gms/Component.kt b/src/kernel/client/kotlin/terramodulus/mui/gms/Component.kt index ff1e514d..f5d2e4ee 100644 --- a/src/kernel/client/kotlin/terramodulus/mui/gms/Component.kt +++ b/src/kernel/client/kotlin/terramodulus/mui/gms/Component.kt @@ -5,6 +5,10 @@ package terramodulus.mui.gms +import java.awt.Rectangle + abstract class Component { + abstract var rect: Rectangle + abstract fun render() } diff --git a/src/kernel/client/kotlin/terramodulus/mui/gms/Menu.kt b/src/kernel/client/kotlin/terramodulus/mui/gms/Menu.kt index fc508b2d..b4704109 100644 --- a/src/kernel/client/kotlin/terramodulus/mui/gms/Menu.kt +++ b/src/kernel/client/kotlin/terramodulus/mui/gms/Menu.kt @@ -5,8 +5,48 @@ package terramodulus.mui.gms +import java.util.ArrayDeque + abstract class Menu { private val components = LinkedHashSet() + private val componentQueue = ArrayDeque() + val handle: Handle = HandleImpl() + + private sealed interface ComponentOperation { + class Add(val component: () -> Component) : ComponentOperation + + class Remove(val component: Component) : ComponentOperation + } + + /** + * It is strongly suggested only using this function during initialization. + */ + protected fun addComponent(component: Component) { + components.add(component) + } + + /** + * It is strongly suggested only using this function during initialization. + */ + protected fun removeComponent(component: Component) { + components.remove(component) + } + + sealed interface Handle { + fun addComponent(component: () -> Component) + + fun removeComponent(component: Component) + } + + private inner class HandleImpl : Handle { + override fun addComponent(component: () -> Component) { + componentQueue.add(ComponentOperation.Add(component)) + } + + override fun removeComponent(component: Component) { + componentQueue.add(ComponentOperation.Remove(component)) + } + } abstract fun render() } diff --git a/src/kernel/client/kotlin/terramodulus/mui/gms/Screen.kt b/src/kernel/client/kotlin/terramodulus/mui/gms/Screen.kt index 4fbc132d..8ee46d92 100644 --- a/src/kernel/client/kotlin/terramodulus/mui/gms/Screen.kt +++ b/src/kernel/client/kotlin/terramodulus/mui/gms/Screen.kt @@ -6,10 +6,55 @@ package terramodulus.mui.gms import terramodulus.mui.gms.event.ScreenEvent +import java.util.ArrayDeque abstract class Screen { private val listeners = HashMap, LinkedHashSet<(ScreenEvent) -> Unit>>() private val menus = LinkedHashSet() + private val components = LinkedHashSet() + private val componentQueue = ArrayDeque() + private val menuQueue = ArrayDeque() + val handle: Handle = HandleImpl() + + private sealed interface ComponentOperation { + class Add(val component: () -> Component) : ComponentOperation + + class Remove(val component: Component) : ComponentOperation + } + + private sealed interface MenuOperation { + class Add(val menu: () -> Menu) : MenuOperation + + class Remove(val menu: Menu) : MenuOperation + } + + /** + * It is strongly suggested only using this function during initialization. + */ + protected fun addComponent(component: Component) { + components.add(component) + } + + /** + * It is strongly suggested only using this function during initialization. + */ + protected fun removeComponent(component: Component) { + components.remove(component) + } + + /** + * It is strongly suggested only using this function during initialization. + */ + protected fun addMenu(menu: Menu) { + menus.add(menu) + } + + /** + * It is strongly suggested only using this function during initialization. + */ + protected fun removeMenu(menu: Menu) { + menus.remove(menu) + } fun addListener(e: Class, l: (T) -> Unit) { @Suppress("UNCHECKED_CAST") @@ -20,6 +65,34 @@ abstract class Screen { listeners[e]?.remove(l) } + sealed interface Handle { + fun addComponent(component: () -> Component) + + fun removeComponent(component: Component) + + fun addMenu(menu: () -> Menu) + + fun removeMenu(menu: Menu) + } + + private inner class HandleImpl : Handle { + override fun addComponent(component: () -> Component) { + componentQueue.add(ComponentOperation.Add(component)) + } + + override fun removeComponent(component: Component) { + componentQueue.add(ComponentOperation.Remove(component)) + } + + override fun addMenu(menu: () -> Menu) { + menuQueue.add(MenuOperation.Add(menu)) + } + + override fun removeMenu(menu: Menu) { + menuQueue.add(MenuOperation.Remove(menu)) + } + } + /** * Cleans up and closes any used resource here. */ diff --git a/src/kernel/client/kotlin/terramodulus/mui/gms/ScreenManager.kt b/src/kernel/client/kotlin/terramodulus/mui/gms/ScreenManager.kt index 00a10abb..f439591e 100644 --- a/src/kernel/client/kotlin/terramodulus/mui/gms/ScreenManager.kt +++ b/src/kernel/client/kotlin/terramodulus/mui/gms/ScreenManager.kt @@ -5,10 +5,16 @@ package terramodulus.mui.gms +import terramodulus.mui.gms.impl.LaunchingScreen + class ScreenManager internal constructor() { private val screens = ArrayDeque() - private val queue = ArrayDeque() - private val handle = Handle() + private val screenQueue = ArrayDeque() + private val handle: Handle = HandleImpl() + + init { + screens.add(LaunchingScreen()) + } private sealed interface ScreenOperation { /** @@ -35,33 +41,55 @@ class ScreenManager internal constructor() { class Reset(val screen: () -> Screen) : ScreenOperation } - inner class Handle { + sealed interface Handle { + /** + * @see ScreenOperation.Exit + */ + fun exit(n: Int) + + /** + * @see ScreenOperation.Open + */ + fun open(screen: () -> Screen) + + /** + * @see ScreenOperation.ExitTo + */ + fun exitTo(screen: Screen) + + /** + * @see ScreenOperation.Reset + */ + fun reset(screen: () -> Screen) + } + + private inner class HandleImpl : Handle { /** * @see ScreenOperation.Exit */ - fun exit(n: Int) { - queue.add(ScreenOperation.Exit(n)) + override fun exit(n: Int) { + screenQueue.add(ScreenOperation.Exit(n)) } /** * @see ScreenOperation.Open */ - fun open(screen: () -> Screen) { - queue.add(ScreenOperation.Open(screen)) + override fun open(screen: () -> Screen) { + screenQueue.add(ScreenOperation.Open(screen)) } /** * @see ScreenOperation.ExitTo */ - fun exitTo(screen: Screen) { - queue.add(ScreenOperation.ExitTo(screen)) + override fun exitTo(screen: Screen) { + screenQueue.add(ScreenOperation.ExitTo(screen)) } /** * @see ScreenOperation.Reset */ - fun reset(screen: () -> Screen) { - queue.add(ScreenOperation.Reset(screen)) + override fun reset(screen: () -> Screen) { + screenQueue.add(ScreenOperation.Reset(screen)) } } } diff --git a/src/kernel/client/kotlin/terramodulus/mui/gms/impl/LaunchingScreen.kt b/src/kernel/client/kotlin/terramodulus/mui/gms/impl/LaunchingScreen.kt new file mode 100644 index 00000000..667e20a3 --- /dev/null +++ b/src/kernel/client/kotlin/terramodulus/mui/gms/impl/LaunchingScreen.kt @@ -0,0 +1,18 @@ +/* + * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package terramodulus.mui.gms.impl + +import terramodulus.mui.gms.Screen + +internal class LaunchingScreen : Screen() { + init { + addComponent(SpriteComponent()) + } + + override fun exit() { + TODO("Not yet implemented") + } +} diff --git a/src/kernel/client/kotlin/terramodulus/mui/gms/impl/ResourceLoadingScreen.kt b/src/kernel/client/kotlin/terramodulus/mui/gms/impl/ResourceLoadingScreen.kt new file mode 100644 index 00000000..a48a0446 --- /dev/null +++ b/src/kernel/client/kotlin/terramodulus/mui/gms/impl/ResourceLoadingScreen.kt @@ -0,0 +1,14 @@ +/* + * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package terramodulus.mui.gms.impl + +import terramodulus.mui.gms.Screen + +class ResourceLoadingScreen : Screen() { + override fun exit() { + TODO("Not yet implemented") + } +} diff --git a/src/kernel/client/kotlin/terramodulus/mui/gms/impl/SpriteComponent.kt b/src/kernel/client/kotlin/terramodulus/mui/gms/impl/SpriteComponent.kt new file mode 100644 index 00000000..89eea265 --- /dev/null +++ b/src/kernel/client/kotlin/terramodulus/mui/gms/impl/SpriteComponent.kt @@ -0,0 +1,17 @@ +/* + * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package terramodulus.mui.gms.impl + +import terramodulus.mui.gfx.GuiSprite +import terramodulus.mui.gms.Component + +class SpriteComponent : Component() { + private val sprite = GuiSprite() + + override fun render() { + TODO("Not yet implemented") + } +} diff --git a/src/kernel/client/kotlin/terramodulus/mui/InputSystem.kt b/src/kernel/client/kotlin/terramodulus/mui/input/InputSystem.kt similarity index 84% rename from src/kernel/client/kotlin/terramodulus/mui/InputSystem.kt rename to src/kernel/client/kotlin/terramodulus/mui/input/InputSystem.kt index 2589961b..fe1b1b39 100644 --- a/src/kernel/client/kotlin/terramodulus/mui/InputSystem.kt +++ b/src/kernel/client/kotlin/terramodulus/mui/input/InputSystem.kt @@ -3,7 +3,7 @@ * SPDX-License-Identifier: LGPL-3.0-only */ -package terramodulus.mui +package terramodulus.mui.input class InputSystem internal constructor() { } diff --git a/src/kernel/client/resources/gms_geo.fsh b/src/kernel/client/resources/gms_geo.fsh new file mode 100644 index 00000000..c5672186 --- /dev/null +++ b/src/kernel/client/resources/gms_geo.fsh @@ -0,0 +1,7 @@ +#version 110 + +varying vec4 texColor; + +void main() { + gl_FragColor = texColor; +} diff --git a/src/kernel/client/resources/gms_geo.vsh b/src/kernel/client/resources/gms_geo.vsh new file mode 100644 index 00000000..94d46f5b --- /dev/null +++ b/src/kernel/client/resources/gms_geo.vsh @@ -0,0 +1,14 @@ +#version 110 + +attribute vec2 pos; +attribute vec4 color; + +varying vec4 texColor; + +uniform mat4 model; +uniform mat4 projection; + +void main() { + gl_Position = projection * model * vec4(pos, 0.0, 1.0); + texColor = color; +} diff --git a/src/kernel/client/resources/gms_tex.fsh b/src/kernel/client/resources/gms_tex.fsh new file mode 100644 index 00000000..d561ac67 --- /dev/null +++ b/src/kernel/client/resources/gms_tex.fsh @@ -0,0 +1,10 @@ +#version 110 + +varying vec2 texCoord; + +uniform sampler2D tex; +uniform mat4 filter; + +void main() { + gl_FragColor = filter * texture2D(tex, texCoord); +} diff --git a/src/kernel/client/resources/gms_tex.vsh b/src/kernel/client/resources/gms_tex.vsh new file mode 100644 index 00000000..64b368f8 --- /dev/null +++ b/src/kernel/client/resources/gms_tex.vsh @@ -0,0 +1,15 @@ +#version 110 + +attribute vec2 pos; +attribute vec2 coord; + +varying vec2 texCoord; +varying mat4 texFilter; + +uniform mat4 model; +uniform mat4 projection; + +void main() { + gl_Position = projection * model * vec4(pos, 0.0, 1.0); + texCoord = coord; +} diff --git a/src/kernel/client/resources/test.fsh b/src/kernel/client/resources/test.fsh deleted file mode 100644 index 75c6bec8..00000000 --- a/src/kernel/client/resources/test.fsh +++ /dev/null @@ -1,8 +0,0 @@ -#version 110 - -uniform sampler2D tex; // The 2D texture -varying vec2 texCoord; // Interpolated texture coordinates - -void main() { - gl_FragColor = texture2D(tex, texCoord); -} diff --git a/src/kernel/client/resources/test.vsh b/src/kernel/client/resources/test.vsh deleted file mode 100644 index b9ffa6ee..00000000 --- a/src/kernel/client/resources/test.vsh +++ /dev/null @@ -1,11 +0,0 @@ -#version 110 - -attribute vec3 aPos; -attribute vec2 aTexCoord; - -varying vec2 texCoord; - -void main() { - gl_Position = vec4(aPos, 1.0); - texCoord = aTexCoord; -} From dae52048b3ae69787e774ca3ed864768bfcf77e5 Mon Sep 17 00:00:00 2001 From: Ben Forge <74168521+BenCheung0422@users.noreply.github.com> Date: Wed, 11 Jun 2025 08:28:34 +0800 Subject: [PATCH 16/19] MUI: Expanding functionalities Now it can render well --- ferricia | 2 +- .../kotlin/terramodulus/engine/AlphaFilter.kt | 18 ++++ .../kotlin/terramodulus/engine/Canvas.kt | 16 +++- .../kotlin/terramodulus/engine/ColorFilter.kt | 5 +- .../kotlin/terramodulus/engine/Drawable.kt | 6 ++ .../terramodulus/engine/ModelTransform.kt | 5 +- .../terramodulus/engine/SimpleLineGeom.kt | 2 +- .../terramodulus/engine/SmartScaling.kt | 14 ++- .../kotlin/terramodulus/engine/SpriteMesh.kt | 2 +- .../kotlin/terramodulus/engine/Window.kt | 2 - .../terramodulus/engine/ferricia/Mui.kt | 78 +++++++++++---- .../kotlin/terramodulus/mui/GuiManager.kt | 7 +- .../terramodulus/mui/gfx/ColorFilter.kt | 10 ++ .../kotlin/terramodulus/mui/gfx/GuiSprite.kt | 12 ++- .../terramodulus/mui/gfx/GuiTransform.kt | 17 ---- .../terramodulus/mui/gfx/ModelTransform.kt | 10 ++ .../kotlin/terramodulus/mui/gfx/Rectangle.kt | 96 ++++++++++++++----- .../terramodulus/mui/gfx/RenderSystem.kt | 22 ++++- .../kotlin/terramodulus/mui/gfx/Vector2.kt | 7 -- .../kotlin/terramodulus/mui/gms/Component.kt | 7 +- .../kotlin/terramodulus/mui/gms/Screen.kt | 5 + .../terramodulus/mui/gms/ScreenManager.kt | 21 ++-- .../mui/gms/impl/LaunchingScreen.kt | 6 +- .../mui/gms/impl/SpriteComponent.kt | 19 +++- src/kernel/client/resources/gms_tex.vsh | 1 - 25 files changed, 283 insertions(+), 107 deletions(-) create mode 100644 src/internal/client/kotlin/terramodulus/engine/AlphaFilter.kt create mode 100644 src/kernel/client/kotlin/terramodulus/mui/gfx/ColorFilter.kt delete mode 100644 src/kernel/client/kotlin/terramodulus/mui/gfx/GuiTransform.kt create mode 100644 src/kernel/client/kotlin/terramodulus/mui/gfx/ModelTransform.kt diff --git a/ferricia b/ferricia index 62ffea6e..9a963375 160000 --- a/ferricia +++ b/ferricia @@ -1 +1 @@ -Subproject commit 62ffea6eeb154019048d9a5dd6aed69f9025721a +Subproject commit 9a96337548f58c8469e7d7e11464758cf60875e1 diff --git a/src/internal/client/kotlin/terramodulus/engine/AlphaFilter.kt b/src/internal/client/kotlin/terramodulus/engine/AlphaFilter.kt new file mode 100644 index 00000000..281e6fb2 --- /dev/null +++ b/src/internal/client/kotlin/terramodulus/engine/AlphaFilter.kt @@ -0,0 +1,18 @@ +/* + * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package terramodulus.engine + +import terramodulus.engine.ferricia.Mui.editAlphaFilter +import terramodulus.engine.ferricia.Mui.filterAlphaFilter + +@OptIn(ExperimentalUnsignedTypes::class) +class AlphaFilter(alpha: Float) : ColorFilter(filterAlphaFilter(alpha)) { + var alpha = alpha + set(value) { + editAlphaFilter(handle, value) + field = value + } +} diff --git a/src/internal/client/kotlin/terramodulus/engine/Canvas.kt b/src/internal/client/kotlin/terramodulus/engine/Canvas.kt index 40b4f293..184c70f5 100644 --- a/src/internal/client/kotlin/terramodulus/engine/Canvas.kt +++ b/src/internal/client/kotlin/terramodulus/engine/Canvas.kt @@ -5,6 +5,8 @@ package terramodulus.engine +import terramodulus.engine.ferricia.Mui +import terramodulus.engine.ferricia.Mui.clearCanvas import terramodulus.engine.ferricia.Mui.drawGuiGeo import terramodulus.engine.ferricia.Mui.drawGuiTex import terramodulus.engine.ferricia.Mui.dropCanvasHandle @@ -12,7 +14,7 @@ import terramodulus.engine.ferricia.Mui.geoShaders import terramodulus.engine.ferricia.Mui.getGLVersion import terramodulus.engine.ferricia.Mui.initCanvasHandle import terramodulus.engine.ferricia.Mui.loadImageToCanvas -import terramodulus.engine.ferricia.Mui.renderTexture +import terramodulus.engine.ferricia.Mui.setCanvasClearColor import terramodulus.engine.ferricia.Mui.texShaders import java.io.Closeable @@ -25,18 +27,22 @@ class Canvas internal constructor(private val windowHandle: ULong) : Closeable { private val canvasHandle = initCanvasHandle(windowHandle) val glVersion = getGLVersion(windowHandle) + fun clear() = clearCanvas() + + fun setClearColor(r: Float, g: Float, b: Float, a: Float) = setCanvasClearColor(r, g, b, a) + + fun resizeGLViewport() = Mui.resizeGLViewport(windowHandle, canvasHandle) + fun loadImage(path: String) = loadImageToCanvas(canvasHandle, path) fun loadGeoShaders(vsh: String, fsh: String) = geoShaders(vsh, fsh) fun loadTexShaders(vsh: String, fsh: String) = texShaders(vsh, fsh) - fun renderImage(shader: UInt, texture: UInt) = renderTexture(canvasHandle, shader, texture) - - fun drawGuiGeoObj(drawable: GeoDrawable, programHandle: ULong) = + fun renderGuiGeo(drawable: GeoDrawable, programHandle: ULong) = drawGuiGeo(canvasHandle, drawable.handle, programHandle) - fun drawGuiTexObj(drawable: TexDrawable, programHandle: ULong, textureHandle: UInt) = + fun renderGuiTex(drawable: TexDrawable, programHandle: ULong, textureHandle: UInt) = drawGuiTex(canvasHandle, drawable.handle, programHandle, textureHandle) override fun close() { diff --git a/src/internal/client/kotlin/terramodulus/engine/ColorFilter.kt b/src/internal/client/kotlin/terramodulus/engine/ColorFilter.kt index 04e786f8..e6815ccd 100644 --- a/src/internal/client/kotlin/terramodulus/engine/ColorFilter.kt +++ b/src/internal/client/kotlin/terramodulus/engine/ColorFilter.kt @@ -5,5 +5,8 @@ package terramodulus.engine -sealed class ColorFilter { +@OptIn(ExperimentalUnsignedTypes::class) +sealed class ColorFilter(handles: ULongArray) { + internal val handle: ULong = handles[0] + internal val wideHandle: ULong = handles[1] } diff --git a/src/internal/client/kotlin/terramodulus/engine/Drawable.kt b/src/internal/client/kotlin/terramodulus/engine/Drawable.kt index 541729a5..870000dd 100644 --- a/src/internal/client/kotlin/terramodulus/engine/Drawable.kt +++ b/src/internal/client/kotlin/terramodulus/engine/Drawable.kt @@ -5,5 +5,11 @@ package terramodulus.engine +import terramodulus.engine.ferricia.Mui.addColorFilter +import terramodulus.engine.ferricia.Mui.addModelTransform + sealed class Drawable(internal val handle: ULong) { + fun add(model: ModelTransform) = addModelTransform(handle, model.wideHandle) + + fun add(filter: ColorFilter) = addColorFilter(handle, filter.wideHandle) } diff --git a/src/internal/client/kotlin/terramodulus/engine/ModelTransform.kt b/src/internal/client/kotlin/terramodulus/engine/ModelTransform.kt index 3386f135..e75f67a7 100644 --- a/src/internal/client/kotlin/terramodulus/engine/ModelTransform.kt +++ b/src/internal/client/kotlin/terramodulus/engine/ModelTransform.kt @@ -5,5 +5,8 @@ package terramodulus.engine -sealed class ModelTransform(internal val handle: ULong) { +@OptIn(ExperimentalUnsignedTypes::class) +sealed class ModelTransform(handles: ULongArray) { + internal val handle: ULong = handles[0] + internal val wideHandle: ULong = handles[1] } diff --git a/src/internal/client/kotlin/terramodulus/engine/SimpleLineGeom.kt b/src/internal/client/kotlin/terramodulus/engine/SimpleLineGeom.kt index ce53260d..a41f4cc5 100644 --- a/src/internal/client/kotlin/terramodulus/engine/SimpleLineGeom.kt +++ b/src/internal/client/kotlin/terramodulus/engine/SimpleLineGeom.kt @@ -8,6 +8,6 @@ package terramodulus.engine import terramodulus.engine.ferricia.Mui.newSimpleLineGeom class SimpleLineGeom(x0: Int, y0: Int, x1: Int, y1: Int, r: Int, g: Int, b: Int, a: Int) : - GeoDrawable(newSimpleLineGeom(arrayOf(x0, y0, x1, y1, r, g, b, a))) { + GeoDrawable(newSimpleLineGeom(intArrayOf(x0, y0, x1, y1, r, g, b, a))) { } diff --git a/src/internal/client/kotlin/terramodulus/engine/SmartScaling.kt b/src/internal/client/kotlin/terramodulus/engine/SmartScaling.kt index 766c8221..263bf8bc 100644 --- a/src/internal/client/kotlin/terramodulus/engine/SmartScaling.kt +++ b/src/internal/client/kotlin/terramodulus/engine/SmartScaling.kt @@ -7,5 +7,17 @@ package terramodulus.engine import terramodulus.engine.ferricia.Mui.modelSmartScaling -class SmartScaling(w: Int, h: Int) : ModelTransform(modelSmartScaling(arrayOf(w, h))) { +@OptIn(ExperimentalUnsignedTypes::class) +class SmartScaling private constructor(vararg args: Int) : + ModelTransform(modelSmartScaling(args)) { + + companion object { + fun none(w: Int, h: Int) = SmartScaling(w, h, 0) + + fun x(w: Int, h: Int, ww: Int, hh: Int) = SmartScaling(w, h, 1, ww, hh) + + fun y(w: Int, h: Int, ww: Int, hh: Int) = SmartScaling(w, h, 2, ww, hh) + + fun both(w: Int, h: Int, ww: Int, hh: Int) = SmartScaling(w, h, 3, ww, hh) + } } diff --git a/src/internal/client/kotlin/terramodulus/engine/SpriteMesh.kt b/src/internal/client/kotlin/terramodulus/engine/SpriteMesh.kt index e7f40c8c..36bb233c 100644 --- a/src/internal/client/kotlin/terramodulus/engine/SpriteMesh.kt +++ b/src/internal/client/kotlin/terramodulus/engine/SpriteMesh.kt @@ -7,6 +7,6 @@ package terramodulus.engine import terramodulus.engine.ferricia.Mui.newSpriteMesh -class SpriteMesh(x0: Int, y0: Int, x1: Int, y1: Int) : TexDrawable(newSpriteMesh(arrayOf(x0, y0, x1, y1))) { +class SpriteMesh(x0: Int, y0: Int, x1: Int, y1: Int) : TexDrawable(newSpriteMesh(intArrayOf(x0, y0, x1, y1))) { } diff --git a/src/internal/client/kotlin/terramodulus/engine/Window.kt b/src/internal/client/kotlin/terramodulus/engine/Window.kt index c5e00efe..969eb47b 100644 --- a/src/internal/client/kotlin/terramodulus/engine/Window.kt +++ b/src/internal/client/kotlin/terramodulus/engine/Window.kt @@ -27,8 +27,6 @@ class Window : Closeable { fun swap() = swapWindow(windowHandle) - fun resizeGLViewport() = resizeGLViewport(windowHandle) - fun pollEvents() = sdlPoll(sdlHandle) override fun close() { diff --git a/src/internal/client/kotlin/terramodulus/engine/ferricia/Mui.kt b/src/internal/client/kotlin/terramodulus/engine/ferricia/Mui.kt index 9bffa0b4..0754cee3 100644 --- a/src/internal/client/kotlin/terramodulus/engine/ferricia/Mui.kt +++ b/src/internal/client/kotlin/terramodulus/engine/ferricia/Mui.kt @@ -7,63 +7,75 @@ package terramodulus.engine.ferricia import terramodulus.engine.MuiEvent +@OptIn(ExperimentalUnsignedTypes::class) internal object Mui { /** * @return SDL handle pointer */ + @JvmName("initSdlHandle") external fun initSdlHandle(): ULong /** * @param sdlHandle SDL handle pointer */ + @JvmName("dropSdlHandle") external fun dropSdlHandle(sdlHandle: ULong) /** * @param sdlHandle SDL handle pointer * @return window handle pointer */ + @JvmName("initWindowHandle") external fun initWindowHandle(sdlHandle: ULong): ULong /** * @param windowHandle window handle pointer */ + @JvmName("dropWindowHandle") external fun dropWindowHandle(windowHandle: ULong) /** * @param windowHandle window handle pointer */ + @JvmName("getGLVersion") external fun getGLVersion(windowHandle: ULong): String /** * @param sdlHandle SDL handle pointer * @return the list of all MUI events in this frame */ + @JvmName("sdlPoll") external fun sdlPoll(sdlHandle: ULong): Array /** * @param windowHandle window handle pointer */ - external fun resizeGLViewport(windowHandle: ULong) + @JvmName("resizeGLViewport") + external fun resizeGLViewport(windowHandle: ULong, canvasHandle: ULong) /** * @param windowHandle window handle pointer */ + @JvmName("showWindow") external fun showWindow(windowHandle: ULong) /** * @param windowHandle window handle pointer */ + @JvmName("swapWindow") external fun swapWindow(windowHandle: ULong) /** * @param windowHandle window handle pointer * @return Canvas handle pointer */ + @JvmName("initCanvasHandle") external fun initCanvasHandle(windowHandle: ULong): ULong /** * @param canvasHandle Canvas handle pointer */ + @JvmName("dropCanvasHandle") external fun dropCanvasHandle(canvasHandle: ULong) /** @@ -74,6 +86,12 @@ internal object Mui { @JvmName("loadImageToCanvas") external fun loadImageToCanvas(canvasHandle: ULong, path: String): UInt + @JvmName("clearCanvas") + external fun clearCanvas() + + @JvmName("setCanvasClearColor") + external fun setCanvasClearColor(r: Float, g: Float, b: Float, a: Float) + /** * @param vsh path to vector shader * @param fsh path to fragment shader @@ -90,48 +108,68 @@ internal object Mui { @JvmName("texShaders") external fun texShaders(vsh: String, fsh: String): ULong - /** - * @param canvasHandle Canvas handle pointer - * @param shader Shader program ID - * @param texture Texture ID - */ - @JvmName("renderTexture") - external fun renderTexture(canvasHandle: ULong, shader: UInt, texture: UInt) - /** * @param data `[x0, y0, x1, y1, r, g, b, a]` * @return SimpleLineGeom handle pointer */ @JvmName("newSimpleLineGeom") - external fun newSimpleLineGeom(data: Array): ULong + external fun newSimpleLineGeom(data: IntArray): ULong /** * @param data `[x0, y0, x1, y1]` * @return SpriteMesh as DrawableSet handle pointer */ @JvmName("newSpriteMesh") - external fun newSpriteMesh(data: Array): ULong + external fun newSpriteMesh(data: IntArray): ULong /** - * @param data `[w, h]` - * @return SmartScaling handle pointer + * @param data `[w, h, param, w, h]` + * @return SmartScaling handle pointers */ @JvmName("modelSmartScaling") - external fun modelSmartScaling(data: Array): ULong + external fun modelSmartScaling(data: IntArray): ULongArray + + /** + * @param data alpha + * @return AlphaFilter handle pointers + */ + @JvmName("filterAlphaFilter") + external fun filterAlphaFilter(data: Float): ULongArray + + /** + * @param filter AlphaFilter handle pointer + * @param data alpha + */ + @JvmName("editAlphaFilter") + external fun editAlphaFilter(filter: ULong, data: Float) + + /** + * @param drawableHandle DrawableSet handle pointer + * @param modelHandle Model Transform wide handle pointer + */ + @JvmName("addModelTransform") + external fun addModelTransform(drawableHandle: ULong, modelHandle: ULong) + + /** + * @param drawableHandle DrawableSet handle pointer + * @param modelHandle Model Transform wide handle pointer + */ + @JvmName("removeModelTransform") + external fun removeModelTransform(drawableHandle: ULong, modelHandle: ULong) /** * @param drawableHandle DrawableSet handle pointer - * @param modelHandle Model Transform handle pointer + * @param filterHandle Color Filter wide handle pointer */ - @JvmName("addSmartScaling") - external fun addSmartScaling(drawableHandle: ULong, modelHandle: ULong) + @JvmName("addColorFilter") + external fun addColorFilter(drawableHandle: ULong, filterHandle: ULong) /** * @param drawableHandle DrawableSet handle pointer - * @param modelHandle Model Transform handle pointer + * @param filterHandle Color Filter wide handle pointer */ - @JvmName("removeSmartScaling") - external fun removeSmartScaling(drawableHandle: ULong, modelHandle: ULong) + @JvmName("removeColorFilter") + external fun removeColorFilter(drawableHandle: ULong, filterHandle: ULong) /** * @param canvasHandle Canvas handle pointer diff --git a/src/kernel/client/kotlin/terramodulus/mui/GuiManager.kt b/src/kernel/client/kotlin/terramodulus/mui/GuiManager.kt index 2dd9180d..a8affbf6 100644 --- a/src/kernel/client/kotlin/terramodulus/mui/GuiManager.kt +++ b/src/kernel/client/kotlin/terramodulus/mui/GuiManager.kt @@ -23,7 +23,7 @@ internal class GuiManager internal constructor() : Closeable { private val window = Window() val renderSystem = RenderSystem(window.canvas) val inputSystem = InputSystem() - val screenManager = ScreenManager() + val screenManager = ScreenManager(renderSystem.handle) internal fun showWindow() = window.show() @@ -221,7 +221,7 @@ internal class GuiManager internal constructor() : Closeable { } is MuiEvent.WindowPixelSizeChanged -> { logger.debug { "Window pixel size changed to ${event.width}x${event.height}." } - window.resizeGLViewport() + window.canvas.resizeGLViewport() logger.debug { "Window viewport resized." } } is MuiEvent.WindowResized -> { @@ -235,7 +235,8 @@ internal class GuiManager internal constructor() : Closeable { } } } - renderSystem.render() + window.canvas.clear() + screenManager.render(renderSystem) window.swap() } diff --git a/src/kernel/client/kotlin/terramodulus/mui/gfx/ColorFilter.kt b/src/kernel/client/kotlin/terramodulus/mui/gfx/ColorFilter.kt new file mode 100644 index 00000000..9ed99fb1 --- /dev/null +++ b/src/kernel/client/kotlin/terramodulus/mui/gfx/ColorFilter.kt @@ -0,0 +1,10 @@ +/* + * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package terramodulus.mui.gfx + +typealias ColorFilter = terramodulus.engine.ColorFilter + +typealias AlphaFilter = terramodulus.engine.AlphaFilter diff --git a/src/kernel/client/kotlin/terramodulus/mui/gfx/GuiSprite.kt b/src/kernel/client/kotlin/terramodulus/mui/gfx/GuiSprite.kt index f52f6c13..53408837 100644 --- a/src/kernel/client/kotlin/terramodulus/mui/gfx/GuiSprite.kt +++ b/src/kernel/client/kotlin/terramodulus/mui/gfx/GuiSprite.kt @@ -7,8 +7,18 @@ package terramodulus.mui.gfx import terramodulus.engine.SpriteMesh -class GuiSprite(private val rect: RectangleI) { +class GuiSprite(private val rect: RectangleI, private val texture: UInt) { private val mesh = SpriteMesh(rect.x, rect.y, rect.x + rect.width, rect.y + rect.height) + fun smartScaling(referX: Int, referY: Int) = SmartScaling.both(referX, referY, rect.width, rect.height) + fun alpha(alpha: Float) = AlphaFilter(alpha) + + fun add(model: ModelTransform) = mesh.add(model) + + fun add(filter: ColorFilter) = mesh.add(filter) + + fun render(renderSystem: RenderSystem) { + renderSystem.renderGuiTex(mesh, texture) + } } diff --git a/src/kernel/client/kotlin/terramodulus/mui/gfx/GuiTransform.kt b/src/kernel/client/kotlin/terramodulus/mui/gfx/GuiTransform.kt deleted file mode 100644 index 2ece03dd..00000000 --- a/src/kernel/client/kotlin/terramodulus/mui/gfx/GuiTransform.kt +++ /dev/null @@ -1,17 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors - * SPDX-License-Identifier: LGPL-3.0-only - */ - -package terramodulus.mui.gfx - -import terramodulus.engine.ModelTransform -import terramodulus.engine.SmartScaling - -class GuiTransform private constructor(private val handle: ModelTransform) { - companion object { - fun smartScaling(w: Int, h: Int) = GuiTransform(SmartScaling(w, h)) - } - - fun add -} diff --git a/src/kernel/client/kotlin/terramodulus/mui/gfx/ModelTransform.kt b/src/kernel/client/kotlin/terramodulus/mui/gfx/ModelTransform.kt new file mode 100644 index 00000000..ad1ab544 --- /dev/null +++ b/src/kernel/client/kotlin/terramodulus/mui/gfx/ModelTransform.kt @@ -0,0 +1,10 @@ +/* + * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package terramodulus.mui.gfx + +typealias ModelTransform = terramodulus.engine.ModelTransform + +typealias SmartScaling = terramodulus.engine.SmartScaling diff --git a/src/kernel/client/kotlin/terramodulus/mui/gfx/Rectangle.kt b/src/kernel/client/kotlin/terramodulus/mui/gfx/Rectangle.kt index e5cf9d21..9dbc2d14 100644 --- a/src/kernel/client/kotlin/terramodulus/mui/gfx/Rectangle.kt +++ b/src/kernel/client/kotlin/terramodulus/mui/gfx/Rectangle.kt @@ -9,27 +9,6 @@ package terramodulus.mui.gfx * Rectangle in a coordinate system with (0, 0) on the bottom left. * The anchor of the rectangle is the bottom-left corner. */ -sealed interface Rectangle { - val x: Int - val y: Int - val width: Int - val height: Int - - fun anchor(pos: Anchor5): Vector2 - - fun translateTo(pos: Vector2): Rectangle - - fun translateToX(x: T): Rectangle - - fun translateToY(y: T): Rectangle - - fun translateByX(x: T): Rectangle - - fun translateByY(y: T): Rectangle - - fun translateBy(pos: Vector2): Rectangle -} - data class RectangleI( val x: Int, val y: Int, @@ -38,8 +17,8 @@ data class RectangleI( ) { companion object { fun withPoints(x0: Int, y0: Int, x1: Int, y1: Int): RectangleI { - val minX: Int?; - val maxX: Int?; + val minX: Int; + val maxX: Int; if (x0 < x1) { minX = x0; maxX = x1; @@ -47,8 +26,8 @@ data class RectangleI( maxX = x0; minX = x1; } - val minY: Int?; - val maxY: Int?; + val minY: Int; + val maxY: Int; if (y0 < y1) { minY = y0; maxY = y1; @@ -68,7 +47,9 @@ data class RectangleI( Anchor5.Center -> Vector2I(x + width / 2, y + height / 2) } - fun translateBy(pos: Vector2I) = RectangleI(x, y, width, height) + fun translateBy(pos: Vector2I) = RectangleI(x + pos.x, y + pos.y, width, height) + + fun translateBy(x: Int, y: Int) = RectangleI(this.x + x, this.y + y, width, height) fun translateByY(y: Int) = RectangleI(x, this.y + y, width, height) @@ -79,4 +60,67 @@ data class RectangleI( fun translateToX(x: Int) = RectangleI(x, y, width, height) fun translateTo(pos: Vector2I) = RectangleI(pos.x, pos.y, width, height) + + fun translateTo(x: Int, y: Int) = RectangleI(x, y, width, height) + + fun toFloat() = RectangleF(x.toFloat(), y.toFloat(), width.toFloat(), height.toFloat()) +} + +/** + * Rectangle in a coordinate system with (0, 0) on the bottom left. + * The anchor of the rectangle is the bottom-left corner. + */ +data class RectangleF( + val x: Float, + val y: Float, + val width: Float, + val height: Float +) { + companion object { + fun withPoints(x0: Float, y0: Float, x1: Float, y1: Float): RectangleF { + val minX: Float; + val maxX: Float; + if (x0 < x1) { + minX = x0; + maxX = x1; + } else { + maxX = x0; + minX = x1; + } + val minY: Float; + val maxY: Float; + if (y0 < y1) { + minY = y0; + maxY = y1; + } else { + maxY = y0; + minY = y1; + } + return RectangleF(minX, maxX, minY, maxY) + } + } + + fun anchor(pos: Anchor5) = when (pos) { + Anchor5.TopLeft -> Vector2F(x, y + width) + Anchor5.TopRight -> Vector2F(x + width, y + height) + Anchor5.BottomLeft -> Vector2F(x, y) + Anchor5.BottomRight -> Vector2F(x + width, y) + Anchor5.Center -> Vector2F(x + width / 2, y + height / 2) + } + + fun translateBy(pos: Vector2F) = RectangleF(x + pos.x, y + pos.y, width, height) + + fun translateBy(x: Float, y: Float) = RectangleF(this.x + x, this.y + y, width, height) + + fun translateByY(y: Float) = RectangleF(x, this.y + y, width, height) + + fun translateByX(x: Float) = RectangleF(this.x + x, y, width, height) + + fun translateToY(y: Float) = RectangleF(x, y, width, height) + + fun translateToX(x: Float) = RectangleF(x, y, width, height) + + fun translateTo(pos: Vector2F) = RectangleF(pos.x, pos.y, width, height) + + fun translateTo(x: Float, y: Float) = RectangleF(x, y, width, height) } diff --git a/src/kernel/client/kotlin/terramodulus/mui/gfx/RenderSystem.kt b/src/kernel/client/kotlin/terramodulus/mui/gfx/RenderSystem.kt index 39ebf161..e63078bd 100644 --- a/src/kernel/client/kotlin/terramodulus/mui/gfx/RenderSystem.kt +++ b/src/kernel/client/kotlin/terramodulus/mui/gfx/RenderSystem.kt @@ -6,6 +6,8 @@ package terramodulus.mui.gfx import terramodulus.engine.Canvas +import terramodulus.engine.GeoDrawable +import terramodulus.engine.TexDrawable import java.io.File private fun getPathOfResource(path: String): String { @@ -13,11 +15,27 @@ private fun getPathOfResource(path: String): String { } class RenderSystem internal constructor(private val canvas: Canvas) { - private val texture = canvas.loadImage(getPathOfResource("/test.png")) - private val shader = canvas.loadShaders( + internal val handle: Handle = HandleImpl() + private val texShaders = canvas.loadTexShaders( getPathOfResource("/gms_tex.vsh"), getPathOfResource("/gms_tex.fsh") ) + private val geoShaders = canvas.loadGeoShaders( + getPathOfResource("/gms_geo.vsh"), + getPathOfResource("/gms_geo.fsh") + ) + + sealed interface Handle { + fun loadTexture(path: String): UInt + } + + private inner class HandleImpl : Handle { + override fun loadTexture(path: String) = canvas.loadImage(getPathOfResource(path)) + } + + internal fun renderGuiTex(drawable: TexDrawable, texture: UInt) = canvas.renderGuiTex(drawable, texShaders, texture) + + internal fun renderGuiGeo(drawable: GeoDrawable) = canvas.renderGuiGeo(drawable, texShaders) internal fun render() { diff --git a/src/kernel/client/kotlin/terramodulus/mui/gfx/Vector2.kt b/src/kernel/client/kotlin/terramodulus/mui/gfx/Vector2.kt index c4b678c3..d14a4ad9 100644 --- a/src/kernel/client/kotlin/terramodulus/mui/gfx/Vector2.kt +++ b/src/kernel/client/kotlin/terramodulus/mui/gfx/Vector2.kt @@ -5,13 +5,6 @@ package terramodulus.mui.gfx -sealed interface Vector2 { - val x: T - val y: T - - operator fun plus(other: Vector2): Vector2 -} - data class Vector2I(val x: Int, val y: Int) { fun plus(other: Vector2I) = Vector2I(x + other.x, y + other.y) } diff --git a/src/kernel/client/kotlin/terramodulus/mui/gms/Component.kt b/src/kernel/client/kotlin/terramodulus/mui/gms/Component.kt index f5d2e4ee..4da3f8fc 100644 --- a/src/kernel/client/kotlin/terramodulus/mui/gms/Component.kt +++ b/src/kernel/client/kotlin/terramodulus/mui/gms/Component.kt @@ -5,10 +5,11 @@ package terramodulus.mui.gms -import java.awt.Rectangle +import terramodulus.mui.gfx.RectangleI +import terramodulus.mui.gfx.RenderSystem abstract class Component { - abstract var rect: Rectangle + abstract var rect: RectangleI - abstract fun render() + abstract fun render(renderSystem: RenderSystem) } diff --git a/src/kernel/client/kotlin/terramodulus/mui/gms/Screen.kt b/src/kernel/client/kotlin/terramodulus/mui/gms/Screen.kt index 8ee46d92..fa1375bb 100644 --- a/src/kernel/client/kotlin/terramodulus/mui/gms/Screen.kt +++ b/src/kernel/client/kotlin/terramodulus/mui/gms/Screen.kt @@ -5,6 +5,7 @@ package terramodulus.mui.gms +import terramodulus.mui.gfx.RenderSystem import terramodulus.mui.gms.event.ScreenEvent import java.util.ArrayDeque @@ -93,6 +94,10 @@ abstract class Screen { } } + internal fun render(renderSystem: RenderSystem) { + components.forEach { it.render(renderSystem) } + } + /** * Cleans up and closes any used resource here. */ diff --git a/src/kernel/client/kotlin/terramodulus/mui/gms/ScreenManager.kt b/src/kernel/client/kotlin/terramodulus/mui/gms/ScreenManager.kt index f439591e..97e74f38 100644 --- a/src/kernel/client/kotlin/terramodulus/mui/gms/ScreenManager.kt +++ b/src/kernel/client/kotlin/terramodulus/mui/gms/ScreenManager.kt @@ -5,15 +5,16 @@ package terramodulus.mui.gms +import terramodulus.mui.gfx.RenderSystem import terramodulus.mui.gms.impl.LaunchingScreen -class ScreenManager internal constructor() { +class ScreenManager internal constructor(private val renderSystemHandle: RenderSystem.Handle) { private val screens = ArrayDeque() private val screenQueue = ArrayDeque() private val handle: Handle = HandleImpl() init { - screens.add(LaunchingScreen()) + screens.add(LaunchingScreen(renderSystemHandle)) } private sealed interface ScreenOperation { @@ -28,7 +29,7 @@ class ScreenManager internal constructor() { /** * Opens the `screen` */ - class Open(val screen: () -> Screen) : ScreenOperation + class Open(val screen: (RenderSystem.Handle) -> Screen) : ScreenOperation /** * Exits until reaching the `screen` then remains on the `screen` @@ -38,7 +39,7 @@ class ScreenManager internal constructor() { /** * Clears [screens] then opens the `screen` */ - class Reset(val screen: () -> Screen) : ScreenOperation + class Reset(val screen: (RenderSystem.Handle) -> Screen) : ScreenOperation } sealed interface Handle { @@ -50,7 +51,7 @@ class ScreenManager internal constructor() { /** * @see ScreenOperation.Open */ - fun open(screen: () -> Screen) + fun open(screen: (RenderSystem.Handle) -> Screen) /** * @see ScreenOperation.ExitTo @@ -60,7 +61,7 @@ class ScreenManager internal constructor() { /** * @see ScreenOperation.Reset */ - fun reset(screen: () -> Screen) + fun reset(screen: (RenderSystem.Handle) -> Screen) } private inner class HandleImpl : Handle { @@ -74,7 +75,7 @@ class ScreenManager internal constructor() { /** * @see ScreenOperation.Open */ - override fun open(screen: () -> Screen) { + override fun open(screen: (RenderSystem.Handle) -> Screen) { screenQueue.add(ScreenOperation.Open(screen)) } @@ -88,8 +89,12 @@ class ScreenManager internal constructor() { /** * @see ScreenOperation.Reset */ - override fun reset(screen: () -> Screen) { + override fun reset(screen: (RenderSystem.Handle) -> Screen) { screenQueue.add(ScreenOperation.Reset(screen)) } } + + internal fun render(renderSystem: RenderSystem) { + screens.forEach { it.render(renderSystem) } + } } diff --git a/src/kernel/client/kotlin/terramodulus/mui/gms/impl/LaunchingScreen.kt b/src/kernel/client/kotlin/terramodulus/mui/gms/impl/LaunchingScreen.kt index 667e20a3..2c01eb24 100644 --- a/src/kernel/client/kotlin/terramodulus/mui/gms/impl/LaunchingScreen.kt +++ b/src/kernel/client/kotlin/terramodulus/mui/gms/impl/LaunchingScreen.kt @@ -5,11 +5,13 @@ package terramodulus.mui.gms.impl +import terramodulus.mui.gfx.RectangleI +import terramodulus.mui.gfx.RenderSystem import terramodulus.mui.gms.Screen -internal class LaunchingScreen : Screen() { +internal class LaunchingScreen(renderSystemHandle: RenderSystem.Handle) : Screen() { init { - addComponent(SpriteComponent()) + addComponent(SpriteComponent(RectangleI(0, 0, 400, 100), renderSystemHandle.loadTexture("/test.png"))) } override fun exit() { diff --git a/src/kernel/client/kotlin/terramodulus/mui/gms/impl/SpriteComponent.kt b/src/kernel/client/kotlin/terramodulus/mui/gms/impl/SpriteComponent.kt index 89eea265..2d0b91bc 100644 --- a/src/kernel/client/kotlin/terramodulus/mui/gms/impl/SpriteComponent.kt +++ b/src/kernel/client/kotlin/terramodulus/mui/gms/impl/SpriteComponent.kt @@ -6,12 +6,23 @@ package terramodulus.mui.gms.impl import terramodulus.mui.gfx.GuiSprite +import terramodulus.mui.gfx.RectangleI +import terramodulus.mui.gfx.RenderSystem import terramodulus.mui.gms.Component -class SpriteComponent : Component() { - private val sprite = GuiSprite() +class SpriteComponent(override var rect: RectangleI, texture: UInt) : Component() { + private val sprite = GuiSprite(rect, texture) + private var tick = 0; + private val alpha = sprite.alpha(0F); - override fun render() { - TODO("Not yet implemented") + init { + sprite.add(sprite.smartScaling(800, 480)) + sprite.add(alpha) + } + + override fun render(renderSystem: RenderSystem) { + sprite.render(renderSystem) + tick = (tick + 1) % 50 + alpha.alpha = tick / 50F } } diff --git a/src/kernel/client/resources/gms_tex.vsh b/src/kernel/client/resources/gms_tex.vsh index 64b368f8..2881ea5a 100644 --- a/src/kernel/client/resources/gms_tex.vsh +++ b/src/kernel/client/resources/gms_tex.vsh @@ -4,7 +4,6 @@ attribute vec2 pos; attribute vec2 coord; varying vec2 texCoord; -varying mat4 texFilter; uniform mat4 model; uniform mat4 projection; From ec113fe2214b20d809f53503be5c1c0d9d3e2601 Mon Sep 17 00:00:00 2001 From: Ben Forge <74168521+BenCheung0422@users.noreply.github.com> Date: Thu, 12 Jun 2025 07:48:53 +0800 Subject: [PATCH 17/19] MUI: Expanding functionalities Merged several classes into files Tweaked status logging Update threading still planning --- ferricia | 2 +- .../kotlin/terramodulus/engine/AlphaFilter.kt | 18 ----- .../kotlin/terramodulus/engine/Canvas.kt | 4 +- .../kotlin/terramodulus/engine/ColorFilter.kt | 12 ++++ .../kotlin/terramodulus/engine/Drawable.kt | 6 +- .../kotlin/terramodulus/engine/GeoDrawable.kt | 9 --- .../terramodulus/engine/GeomDrawable.kt | 18 +++++ .../engine/{SpriteMesh.kt => MeshDrawable.kt} | 5 +- .../terramodulus/engine/ModelTransform.kt | 21 ++++++ .../terramodulus/engine/SimpleLineGeom.kt | 13 ---- .../terramodulus/engine/SmartScaling.kt | 23 ------ .../kotlin/terramodulus/engine/TexDrawable.kt | 9 --- .../terramodulus/engine/ferricia/Mui.kt | 21 ++++++ .../kotlin/terramodulus/core/TerraModulus.kt | 1 + .../kotlin/terramodulus/mui/GuiManager.kt | 10 +-- .../kotlin/terramodulus/mui/gfx/Dimension.kt | 14 ++++ .../terramodulus/mui/gfx/GuiGeometry.kt | 24 +++++++ .../kotlin/terramodulus/mui/gfx/GuiSprite.kt | 8 +-- .../terramodulus/mui/gfx/ModelTransform.kt | 4 ++ .../kotlin/terramodulus/mui/gfx/Rectangle.kt | 4 ++ .../terramodulus/mui/gfx/RenderSystem.kt | 17 +++-- .../kotlin/terramodulus/mui/gfx/Vector.kt | 54 ++++++++++++++ .../kotlin/terramodulus/mui/gfx/Vector2.kt | 18 ----- .../kotlin/terramodulus/mui/gms/Screen.kt | 37 ++++++++-- .../terramodulus/mui/gms/ScreenManager.kt | 60 ++++++++++++++-- .../mui/gms/impl/GeomComponent.kt | 17 +++++ .../mui/gms/impl/LaunchingScreen.kt | 66 +++++++++++++++++- .../mui/gms/impl/ResourceLoadingScreen.kt | 8 ++- .../mui/gms/impl/SpriteComponent.kt | 11 +-- src/kernel/client/resources/gms_geo.fsh | 4 +- src/kernel/client/resources/logo.png | Bin 0 -> 20376 bytes .../kotlin/terramodulus/common/core/Core.kt | 6 +- .../kotlin/terramodulus/util/logging/Core.kt | 31 ++++++++ src/kernel/common/resources/log4j2.xml | 2 +- 34 files changed, 418 insertions(+), 139 deletions(-) delete mode 100644 src/internal/client/kotlin/terramodulus/engine/AlphaFilter.kt delete mode 100644 src/internal/client/kotlin/terramodulus/engine/GeoDrawable.kt create mode 100644 src/internal/client/kotlin/terramodulus/engine/GeomDrawable.kt rename src/internal/client/kotlin/terramodulus/engine/{SpriteMesh.kt => MeshDrawable.kt} (53%) delete mode 100644 src/internal/client/kotlin/terramodulus/engine/SimpleLineGeom.kt delete mode 100644 src/internal/client/kotlin/terramodulus/engine/SmartScaling.kt delete mode 100644 src/internal/client/kotlin/terramodulus/engine/TexDrawable.kt create mode 100644 src/kernel/client/kotlin/terramodulus/mui/gfx/Dimension.kt create mode 100644 src/kernel/client/kotlin/terramodulus/mui/gfx/GuiGeometry.kt create mode 100644 src/kernel/client/kotlin/terramodulus/mui/gfx/Vector.kt delete mode 100644 src/kernel/client/kotlin/terramodulus/mui/gfx/Vector2.kt create mode 100644 src/kernel/client/kotlin/terramodulus/mui/gms/impl/GeomComponent.kt create mode 100644 src/kernel/client/resources/logo.png diff --git a/ferricia b/ferricia index 9a963375..29903d15 160000 --- a/ferricia +++ b/ferricia @@ -1 +1 @@ -Subproject commit 9a96337548f58c8469e7d7e11464758cf60875e1 +Subproject commit 29903d15ea2b046541e1af32a6673981222c399b diff --git a/src/internal/client/kotlin/terramodulus/engine/AlphaFilter.kt b/src/internal/client/kotlin/terramodulus/engine/AlphaFilter.kt deleted file mode 100644 index 281e6fb2..00000000 --- a/src/internal/client/kotlin/terramodulus/engine/AlphaFilter.kt +++ /dev/null @@ -1,18 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors - * SPDX-License-Identifier: LGPL-3.0-only - */ - -package terramodulus.engine - -import terramodulus.engine.ferricia.Mui.editAlphaFilter -import terramodulus.engine.ferricia.Mui.filterAlphaFilter - -@OptIn(ExperimentalUnsignedTypes::class) -class AlphaFilter(alpha: Float) : ColorFilter(filterAlphaFilter(alpha)) { - var alpha = alpha - set(value) { - editAlphaFilter(handle, value) - field = value - } -} diff --git a/src/internal/client/kotlin/terramodulus/engine/Canvas.kt b/src/internal/client/kotlin/terramodulus/engine/Canvas.kt index 184c70f5..7b1d6a6d 100644 --- a/src/internal/client/kotlin/terramodulus/engine/Canvas.kt +++ b/src/internal/client/kotlin/terramodulus/engine/Canvas.kt @@ -39,10 +39,10 @@ class Canvas internal constructor(private val windowHandle: ULong) : Closeable { fun loadTexShaders(vsh: String, fsh: String) = texShaders(vsh, fsh) - fun renderGuiGeo(drawable: GeoDrawable, programHandle: ULong) = + fun renderGuiGeo(drawable: GeomDrawable, programHandle: ULong) = drawGuiGeo(canvasHandle, drawable.handle, programHandle) - fun renderGuiTex(drawable: TexDrawable, programHandle: ULong, textureHandle: UInt) = + fun renderGuiTex(drawable: MeshDrawable, programHandle: ULong, textureHandle: UInt) = drawGuiTex(canvasHandle, drawable.handle, programHandle, textureHandle) override fun close() { diff --git a/src/internal/client/kotlin/terramodulus/engine/ColorFilter.kt b/src/internal/client/kotlin/terramodulus/engine/ColorFilter.kt index e6815ccd..06b8bef7 100644 --- a/src/internal/client/kotlin/terramodulus/engine/ColorFilter.kt +++ b/src/internal/client/kotlin/terramodulus/engine/ColorFilter.kt @@ -5,8 +5,20 @@ package terramodulus.engine +import terramodulus.engine.ferricia.Mui.editAlphaFilter +import terramodulus.engine.ferricia.Mui.filterAlphaFilter + @OptIn(ExperimentalUnsignedTypes::class) sealed class ColorFilter(handles: ULongArray) { internal val handle: ULong = handles[0] internal val wideHandle: ULong = handles[1] } + +@OptIn(ExperimentalUnsignedTypes::class) +class AlphaFilter(alpha: Float) : ColorFilter(filterAlphaFilter(alpha)) { + var alpha = alpha + set(value) { + editAlphaFilter(handle, value) + field = value + } +} diff --git a/src/internal/client/kotlin/terramodulus/engine/Drawable.kt b/src/internal/client/kotlin/terramodulus/engine/Drawable.kt index 870000dd..157f010a 100644 --- a/src/internal/client/kotlin/terramodulus/engine/Drawable.kt +++ b/src/internal/client/kotlin/terramodulus/engine/Drawable.kt @@ -11,5 +11,9 @@ import terramodulus.engine.ferricia.Mui.addModelTransform sealed class Drawable(internal val handle: ULong) { fun add(model: ModelTransform) = addModelTransform(handle, model.wideHandle) - fun add(filter: ColorFilter) = addColorFilter(handle, filter.wideHandle) + fun add(filter: ColorFilter) { + println(this) + println(filter) + addColorFilter(handle, filter.wideHandle) + } } diff --git a/src/internal/client/kotlin/terramodulus/engine/GeoDrawable.kt b/src/internal/client/kotlin/terramodulus/engine/GeoDrawable.kt deleted file mode 100644 index cb4167f9..00000000 --- a/src/internal/client/kotlin/terramodulus/engine/GeoDrawable.kt +++ /dev/null @@ -1,9 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors - * SPDX-License-Identifier: LGPL-3.0-only - */ - -package terramodulus.engine - -sealed class GeoDrawable(handle: ULong) : Drawable(handle) { -} diff --git a/src/internal/client/kotlin/terramodulus/engine/GeomDrawable.kt b/src/internal/client/kotlin/terramodulus/engine/GeomDrawable.kt new file mode 100644 index 00000000..b4da6865 --- /dev/null +++ b/src/internal/client/kotlin/terramodulus/engine/GeomDrawable.kt @@ -0,0 +1,18 @@ +/* + * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package terramodulus.engine + +import terramodulus.engine.ferricia.Mui.newSimpleLineGeom +import terramodulus.engine.ferricia.Mui.newSimpleRectGeom + +sealed class GeomDrawable(handle: ULong) : Drawable(handle) { +} + +class SimpleLineGeom(x0: Int, y0: Int, x1: Int, y1: Int, r: Int, g: Int, b: Int, a: Int) : + GeomDrawable(newSimpleLineGeom(intArrayOf(x0, y0, x1, y1, r, g, b, a))) + +class SimpleRectGeom(x0: Int, y0: Int, x1: Int, y1: Int, r: Int, g: Int, b: Int, a: Int) : + GeomDrawable(newSimpleRectGeom(intArrayOf(x0, y0, x1, y1, r, g, b, a))) diff --git a/src/internal/client/kotlin/terramodulus/engine/SpriteMesh.kt b/src/internal/client/kotlin/terramodulus/engine/MeshDrawable.kt similarity index 53% rename from src/internal/client/kotlin/terramodulus/engine/SpriteMesh.kt rename to src/internal/client/kotlin/terramodulus/engine/MeshDrawable.kt index 36bb233c..b60c82e4 100644 --- a/src/internal/client/kotlin/terramodulus/engine/SpriteMesh.kt +++ b/src/internal/client/kotlin/terramodulus/engine/MeshDrawable.kt @@ -7,6 +7,9 @@ package terramodulus.engine import terramodulus.engine.ferricia.Mui.newSpriteMesh -class SpriteMesh(x0: Int, y0: Int, x1: Int, y1: Int) : TexDrawable(newSpriteMesh(intArrayOf(x0, y0, x1, y1))) { +sealed class MeshDrawable(handle: ULong) : Drawable(handle) { +} + +class SpriteMesh(x0: Int, y0: Int, x1: Int, y1: Int) : MeshDrawable(newSpriteMesh(intArrayOf(x0, y0, x1, y1))) { } diff --git a/src/internal/client/kotlin/terramodulus/engine/ModelTransform.kt b/src/internal/client/kotlin/terramodulus/engine/ModelTransform.kt index e75f67a7..c7e5645b 100644 --- a/src/internal/client/kotlin/terramodulus/engine/ModelTransform.kt +++ b/src/internal/client/kotlin/terramodulus/engine/ModelTransform.kt @@ -5,8 +5,29 @@ package terramodulus.engine +import terramodulus.engine.ferricia.Mui.modelFullScaling +import terramodulus.engine.ferricia.Mui.modelSmartScaling + @OptIn(ExperimentalUnsignedTypes::class) sealed class ModelTransform(handles: ULongArray) { internal val handle: ULong = handles[0] internal val wideHandle: ULong = handles[1] } + +@OptIn(ExperimentalUnsignedTypes::class) +class SmartScaling private constructor(vararg args: Int) : + ModelTransform(modelSmartScaling(args)) { + + companion object { + fun none(w: Int, h: Int) = SmartScaling(w, h, 0) + + fun x(w: Int, h: Int, ww: Int, hh: Int) = SmartScaling(w, h, 1, ww, hh) + + fun y(w: Int, h: Int, ww: Int, hh: Int) = SmartScaling(w, h, 2, ww, hh) + + fun both(w: Int, h: Int, ww: Int, hh: Int) = SmartScaling(w, h, 3, ww, hh) + } +} + +@OptIn(ExperimentalUnsignedTypes::class) +class FullScaling(w: Int, h: Int) : ModelTransform(modelFullScaling(intArrayOf(w, h))) diff --git a/src/internal/client/kotlin/terramodulus/engine/SimpleLineGeom.kt b/src/internal/client/kotlin/terramodulus/engine/SimpleLineGeom.kt deleted file mode 100644 index a41f4cc5..00000000 --- a/src/internal/client/kotlin/terramodulus/engine/SimpleLineGeom.kt +++ /dev/null @@ -1,13 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors - * SPDX-License-Identifier: LGPL-3.0-only - */ - -package terramodulus.engine - -import terramodulus.engine.ferricia.Mui.newSimpleLineGeom - -class SimpleLineGeom(x0: Int, y0: Int, x1: Int, y1: Int, r: Int, g: Int, b: Int, a: Int) : - GeoDrawable(newSimpleLineGeom(intArrayOf(x0, y0, x1, y1, r, g, b, a))) { - -} diff --git a/src/internal/client/kotlin/terramodulus/engine/SmartScaling.kt b/src/internal/client/kotlin/terramodulus/engine/SmartScaling.kt deleted file mode 100644 index 263bf8bc..00000000 --- a/src/internal/client/kotlin/terramodulus/engine/SmartScaling.kt +++ /dev/null @@ -1,23 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors - * SPDX-License-Identifier: LGPL-3.0-only - */ - -package terramodulus.engine - -import terramodulus.engine.ferricia.Mui.modelSmartScaling - -@OptIn(ExperimentalUnsignedTypes::class) -class SmartScaling private constructor(vararg args: Int) : - ModelTransform(modelSmartScaling(args)) { - - companion object { - fun none(w: Int, h: Int) = SmartScaling(w, h, 0) - - fun x(w: Int, h: Int, ww: Int, hh: Int) = SmartScaling(w, h, 1, ww, hh) - - fun y(w: Int, h: Int, ww: Int, hh: Int) = SmartScaling(w, h, 2, ww, hh) - - fun both(w: Int, h: Int, ww: Int, hh: Int) = SmartScaling(w, h, 3, ww, hh) - } -} diff --git a/src/internal/client/kotlin/terramodulus/engine/TexDrawable.kt b/src/internal/client/kotlin/terramodulus/engine/TexDrawable.kt deleted file mode 100644 index 794d2160..00000000 --- a/src/internal/client/kotlin/terramodulus/engine/TexDrawable.kt +++ /dev/null @@ -1,9 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors - * SPDX-License-Identifier: LGPL-3.0-only - */ - -package terramodulus.engine - -sealed class TexDrawable(handle: ULong) : Drawable(handle) { -} diff --git a/src/internal/client/kotlin/terramodulus/engine/ferricia/Mui.kt b/src/internal/client/kotlin/terramodulus/engine/ferricia/Mui.kt index 0754cee3..bae53762 100644 --- a/src/internal/client/kotlin/terramodulus/engine/ferricia/Mui.kt +++ b/src/internal/client/kotlin/terramodulus/engine/ferricia/Mui.kt @@ -115,6 +115,13 @@ internal object Mui { @JvmName("newSimpleLineGeom") external fun newSimpleLineGeom(data: IntArray): ULong + /** + * @param data `[x0, y0, x1, y1, r, g, b, a]` + * @return SimpleRectGeom handle pointer + */ + @JvmName("newSimpleRectGeom") + external fun newSimpleRectGeom(data: IntArray): ULong + /** * @param data `[x0, y0, x1, y1]` * @return SpriteMesh as DrawableSet handle pointer @@ -129,6 +136,20 @@ internal object Mui { @JvmName("modelSmartScaling") external fun modelSmartScaling(data: IntArray): ULongArray + /** + * @param data `[w, h]` + * @return FullScaling handle pointers + */ + @JvmName("modelFullScaling") + external fun modelFullScaling(data: IntArray): ULongArray + + /** + * @param data `[x, y]` + * @return SimpleTranslation handle pointers + */ + @JvmName("modelSimpleTranslation") + external fun modelSimpleTranslation(data: FloatArray): ULongArray + /** * @param data alpha * @return AlphaFilter handle pointers diff --git a/src/kernel/client/kotlin/terramodulus/core/TerraModulus.kt b/src/kernel/client/kotlin/terramodulus/core/TerraModulus.kt index 0b618258..1ed220b1 100644 --- a/src/kernel/client/kotlin/terramodulus/core/TerraModulus.kt +++ b/src/kernel/client/kotlin/terramodulus/core/TerraModulus.kt @@ -19,6 +19,7 @@ class TerraModulus internal constructor() : AbstractTerraModulus() { guiManager.showWindow() while (true) { guiManager.updateCanvas() +// guiManager.updateScreens() Thread.sleep(1) } } diff --git a/src/kernel/client/kotlin/terramodulus/mui/GuiManager.kt b/src/kernel/client/kotlin/terramodulus/mui/GuiManager.kt index a8affbf6..04f8ac3e 100644 --- a/src/kernel/client/kotlin/terramodulus/mui/GuiManager.kt +++ b/src/kernel/client/kotlin/terramodulus/mui/GuiManager.kt @@ -27,10 +27,12 @@ internal class GuiManager internal constructor() : Closeable { internal fun showWindow() = window.show() - /** - * Screen updating, targeting twice as *maximum FPS*. - */ - internal fun updateScreen() {} +// /** +// * Screen updating, targeting as the same as *maximum FPS*, +// * but the numbers of ticks are not supposed to be compensated when missed, +// * so it is up to the callers to compensate missed activities. +// */ +// internal fun updateScreens() {} /** * Canvas updating, per frame, maximally the *maximum FPS*. diff --git a/src/kernel/client/kotlin/terramodulus/mui/gfx/Dimension.kt b/src/kernel/client/kotlin/terramodulus/mui/gfx/Dimension.kt new file mode 100644 index 00000000..df844d8d --- /dev/null +++ b/src/kernel/client/kotlin/terramodulus/mui/gfx/Dimension.kt @@ -0,0 +1,14 @@ +/* + * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package terramodulus.mui.gfx + +data class Dimension2I(val width: Int, val height: Int) + +data class Dimension2F(val width: Float, val height: Float) + +data class Dimension3I(val width: Int, val height: Int, val length: Int) + +data class Dimension3F(val width: Float, val height: Float, val length: Float) diff --git a/src/kernel/client/kotlin/terramodulus/mui/gfx/GuiGeometry.kt b/src/kernel/client/kotlin/terramodulus/mui/gfx/GuiGeometry.kt new file mode 100644 index 00000000..60413c2d --- /dev/null +++ b/src/kernel/client/kotlin/terramodulus/mui/gfx/GuiGeometry.kt @@ -0,0 +1,24 @@ +/* + * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package terramodulus.mui.gfx + +import terramodulus.engine.GeomDrawable +import terramodulus.engine.SimpleLineGeom +import terramodulus.engine.SimpleRectGeom + +sealed class GuiGeometry(protected val geom: GeomDrawable) { + fun add(model: ModelTransform) = geom.add(model) + + fun add(filter: ColorFilter) = geom.add(filter) + + fun render(renderSystem: RenderSystem) = renderSystem.renderGuiGeo(geom) +} + +class GuiLine(x0: Int, y0: Int, x1: Int, y1: Int, r: Int, g: Int, b: Int, a: Int) : + GuiGeometry(SimpleLineGeom(x0, y0, x1, y1, r, g, b, a)) + +class GuiRect(x0: Int, y0: Int, x1: Int, y1: Int, r: Int, g: Int, b: Int, a: Int) : + GuiGeometry(SimpleRectGeom(x0, y0, x1, y1, r, g, b, a)) diff --git a/src/kernel/client/kotlin/terramodulus/mui/gfx/GuiSprite.kt b/src/kernel/client/kotlin/terramodulus/mui/gfx/GuiSprite.kt index 53408837..42935a81 100644 --- a/src/kernel/client/kotlin/terramodulus/mui/gfx/GuiSprite.kt +++ b/src/kernel/client/kotlin/terramodulus/mui/gfx/GuiSprite.kt @@ -10,15 +10,9 @@ import terramodulus.engine.SpriteMesh class GuiSprite(private val rect: RectangleI, private val texture: UInt) { private val mesh = SpriteMesh(rect.x, rect.y, rect.x + rect.width, rect.y + rect.height) - fun smartScaling(referX: Int, referY: Int) = SmartScaling.both(referX, referY, rect.width, rect.height) - - fun alpha(alpha: Float) = AlphaFilter(alpha) - fun add(model: ModelTransform) = mesh.add(model) fun add(filter: ColorFilter) = mesh.add(filter) - fun render(renderSystem: RenderSystem) { - renderSystem.renderGuiTex(mesh, texture) - } + fun render(renderSystem: RenderSystem) = renderSystem.renderGuiTex(mesh, texture) } diff --git a/src/kernel/client/kotlin/terramodulus/mui/gfx/ModelTransform.kt b/src/kernel/client/kotlin/terramodulus/mui/gfx/ModelTransform.kt index ad1ab544..343cc5c6 100644 --- a/src/kernel/client/kotlin/terramodulus/mui/gfx/ModelTransform.kt +++ b/src/kernel/client/kotlin/terramodulus/mui/gfx/ModelTransform.kt @@ -8,3 +8,7 @@ package terramodulus.mui.gfx typealias ModelTransform = terramodulus.engine.ModelTransform typealias SmartScaling = terramodulus.engine.SmartScaling + +typealias FullScaling = terramodulus.engine.FullScaling + +fun FullScaling(rect: Dimension2I) = FullScaling(rect.width, rect.height) diff --git a/src/kernel/client/kotlin/terramodulus/mui/gfx/Rectangle.kt b/src/kernel/client/kotlin/terramodulus/mui/gfx/Rectangle.kt index 9dbc2d14..0f38514a 100644 --- a/src/kernel/client/kotlin/terramodulus/mui/gfx/Rectangle.kt +++ b/src/kernel/client/kotlin/terramodulus/mui/gfx/Rectangle.kt @@ -39,6 +39,8 @@ data class RectangleI( } } + val size get() = Dimension2I(width, height) + fun anchor(pos: Anchor5) = when (pos) { Anchor5.TopLeft -> Vector2I(x, y + width) Anchor5.TopRight -> Vector2I(x + width, y + height) @@ -100,6 +102,8 @@ data class RectangleF( } } + val size get() = Dimension2F(width, height) + fun anchor(pos: Anchor5) = when (pos) { Anchor5.TopLeft -> Vector2F(x, y + width) Anchor5.TopRight -> Vector2F(x + width, y + height) diff --git a/src/kernel/client/kotlin/terramodulus/mui/gfx/RenderSystem.kt b/src/kernel/client/kotlin/terramodulus/mui/gfx/RenderSystem.kt index e63078bd..b7240322 100644 --- a/src/kernel/client/kotlin/terramodulus/mui/gfx/RenderSystem.kt +++ b/src/kernel/client/kotlin/terramodulus/mui/gfx/RenderSystem.kt @@ -6,8 +6,8 @@ package terramodulus.mui.gfx import terramodulus.engine.Canvas -import terramodulus.engine.GeoDrawable -import terramodulus.engine.TexDrawable +import terramodulus.engine.GeomDrawable +import terramodulus.engine.MeshDrawable import java.io.File private fun getPathOfResource(path: String): String { @@ -15,7 +15,7 @@ private fun getPathOfResource(path: String): String { } class RenderSystem internal constructor(private val canvas: Canvas) { - internal val handle: Handle = HandleImpl() + val handle: Handle = HandleImpl() private val texShaders = canvas.loadTexShaders( getPathOfResource("/gms_tex.vsh"), getPathOfResource("/gms_tex.fsh") @@ -24,18 +24,25 @@ class RenderSystem internal constructor(private val canvas: Canvas) { getPathOfResource("/gms_geo.vsh"), getPathOfResource("/gms_geo.fsh") ) + val targetFps = 1000; sealed interface Handle { fun loadTexture(path: String): UInt + + fun setBackgroundColor(red: Float, green: Float, blue: Float, alpha: Float) } private inner class HandleImpl : Handle { override fun loadTexture(path: String) = canvas.loadImage(getPathOfResource(path)) + + override fun setBackgroundColor(red: Float, green: Float, blue: Float, alpha: Float) { + canvas.setClearColor(red, green, blue, alpha) + } } - internal fun renderGuiTex(drawable: TexDrawable, texture: UInt) = canvas.renderGuiTex(drawable, texShaders, texture) + internal fun renderGuiTex(drawable: MeshDrawable, texture: UInt) = canvas.renderGuiTex(drawable, texShaders, texture) - internal fun renderGuiGeo(drawable: GeoDrawable) = canvas.renderGuiGeo(drawable, texShaders) + internal fun renderGuiGeo(drawable: GeomDrawable) = canvas.renderGuiGeo(drawable, geoShaders) internal fun render() { diff --git a/src/kernel/client/kotlin/terramodulus/mui/gfx/Vector.kt b/src/kernel/client/kotlin/terramodulus/mui/gfx/Vector.kt new file mode 100644 index 00000000..7bb49977 --- /dev/null +++ b/src/kernel/client/kotlin/terramodulus/mui/gfx/Vector.kt @@ -0,0 +1,54 @@ +/* + * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package terramodulus.mui.gfx + +data class Vector2I(val x: Int, val y: Int) { + companion object { + val ZERO = Vector2I(0, 0) + } + + operator fun plus(other: Vector2I) = Vector2I(x + other.x, y + other.y) +} + +data class Vector2D(val x: Double, val y: Double) { + companion object { + val ZERO = Vector2D(.0, .0) + } + + operator fun plus(other: Vector2D) = Vector2D(x + other.x, y + other.y) +} + +data class Vector2F(val x: Float, val y: Float) { + companion object { + val ZERO = Vector2F(0F, 0F) + } + + operator fun plus(other: Vector2F) = Vector2F(x + other.x, y + other.y) +} + +data class Vector3I(val x: Int, val y: Int, val z: Int) { + companion object { + val ZERO = Vector3I(0, 0, 0) + } + + operator fun plus(other: Vector3I) = Vector3I(x + other.x, y + other.y, z + other.z) +} + +data class Vector3D(val x: Double, val y: Double, val z: Double) { + companion object { + val ZERO = Vector3D(.0, .0, .0) + } + + operator fun plus(other: Vector3D) = Vector3D(x + other.x, y + other.y, z + other.z) +} + +data class Vector3F(val x: Float, val y: Float, val z: Float) { + companion object { + val ZERO = Vector3F(0F, 0F, 0F) + } + + operator fun plus(other: Vector3F) = Vector3F(x + other.x, y + other.y, z + other.z) +} diff --git a/src/kernel/client/kotlin/terramodulus/mui/gfx/Vector2.kt b/src/kernel/client/kotlin/terramodulus/mui/gfx/Vector2.kt deleted file mode 100644 index d14a4ad9..00000000 --- a/src/kernel/client/kotlin/terramodulus/mui/gfx/Vector2.kt +++ /dev/null @@ -1,18 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors - * SPDX-License-Identifier: LGPL-3.0-only - */ - -package terramodulus.mui.gfx - -data class Vector2I(val x: Int, val y: Int) { - fun plus(other: Vector2I) = Vector2I(x + other.x, y + other.y) -} - -data class Vector2D(val x: Double, val y: Double) { - fun plus(other: Vector2D) = Vector2D(x + other.x, y + other.y) -} - -data class Vector2F(val x: Float, val y: Float) { - fun plus(other: Vector2F) = Vector2F(x + other.x, y + other.y) -} diff --git a/src/kernel/client/kotlin/terramodulus/mui/gms/Screen.kt b/src/kernel/client/kotlin/terramodulus/mui/gms/Screen.kt index fa1375bb..b65f2abf 100644 --- a/src/kernel/client/kotlin/terramodulus/mui/gms/Screen.kt +++ b/src/kernel/client/kotlin/terramodulus/mui/gms/Screen.kt @@ -18,15 +18,35 @@ abstract class Screen { val handle: Handle = HandleImpl() private sealed interface ComponentOperation { - class Add(val component: () -> Component) : ComponentOperation + fun apply(components: LinkedHashSet) - class Remove(val component: Component) : ComponentOperation + class Add(val component: () -> Component) : ComponentOperation { + override fun apply(components: LinkedHashSet) { + components.add(component()) + } + } + + class Remove(val component: Component) : ComponentOperation { + override fun apply(components: LinkedHashSet) { + components.remove(component) + } + } } private sealed interface MenuOperation { - class Add(val menu: () -> Menu) : MenuOperation + fun apply(menus: LinkedHashSet) - class Remove(val menu: Menu) : MenuOperation + class Add(val menu: () -> Menu) : MenuOperation { + override fun apply(menus: LinkedHashSet) { + menus.add(menu()) + } + } + + class Remove(val menu: Menu) : MenuOperation { + override fun apply(menus: LinkedHashSet) { + menus.remove(menu) + } + } } /** @@ -94,8 +114,15 @@ abstract class Screen { } } - internal fun render(renderSystem: RenderSystem) { + internal open fun update(renderSystem: RenderSystem, screenManager: ScreenManager) {}; + + internal fun render(renderSystem: RenderSystem, screenManager: ScreenManager) { + componentQueue.forEach { it.apply(components) } + componentQueue.clear() + menuQueue.forEach { it.apply(menus) } + menuQueue.clear() components.forEach { it.render(renderSystem) } + update(renderSystem, screenManager) } /** diff --git a/src/kernel/client/kotlin/terramodulus/mui/gms/ScreenManager.kt b/src/kernel/client/kotlin/terramodulus/mui/gms/ScreenManager.kt index 97e74f38..e4484052 100644 --- a/src/kernel/client/kotlin/terramodulus/mui/gms/ScreenManager.kt +++ b/src/kernel/client/kotlin/terramodulus/mui/gms/ScreenManager.kt @@ -9,37 +9,79 @@ import terramodulus.mui.gfx.RenderSystem import terramodulus.mui.gms.impl.LaunchingScreen class ScreenManager internal constructor(private val renderSystemHandle: RenderSystem.Handle) { + /** + * FILO screen stack; the top-most screen instance is in the last. + */ private val screens = ArrayDeque() private val screenQueue = ArrayDeque() - private val handle: Handle = HandleImpl() + val handle: Handle = HandleImpl() init { screens.add(LaunchingScreen(renderSystemHandle)) } private sealed interface ScreenOperation { + fun apply(handle: RenderSystem.Handle, screens: ArrayDeque) + /** * Exits `n` times * * @throws IllegalArgumentException when `n` < 1 * @throws IllegalStateException when `n` >= [screens] size during operation */ - class Exit(val n: Int) : ScreenOperation + class Exit(val n: Int) : ScreenOperation { + init { + require(n < 0) { "`n` < 1" } + } + + override fun apply(handle: RenderSystem.Handle, screens: ArrayDeque) { + if (n >= screens.size) { + throw IllegalStateException("`n` >= screens.size") + } + + for (i in 1..n) { + screens.removeLast().exit() + } + } + } /** * Opens the `screen` */ - class Open(val screen: (RenderSystem.Handle) -> Screen) : ScreenOperation + class Open(val screen: (RenderSystem.Handle) -> Screen) : ScreenOperation { + override fun apply(handle: RenderSystem.Handle, screens: ArrayDeque) { + screens.addLast(screen(handle)) + } + } /** * Exits until reaching the `screen` then remains on the `screen` */ - class ExitTo(val screen: Screen) : ScreenOperation + class ExitTo(val screen: Screen) : ScreenOperation { + override fun apply(handle: RenderSystem.Handle, screens: ArrayDeque) { + val it = screens.asReversed().listIterator() + while (it.hasNext()) { + val e = it.next() + if (e == screen) { + break + } else { + it.remove() + e.exit() + } + } + } + } /** * Clears [screens] then opens the `screen` */ - class Reset(val screen: (RenderSystem.Handle) -> Screen) : ScreenOperation + class Reset(val screen: (RenderSystem.Handle) -> Screen) : ScreenOperation { + override fun apply(handle: RenderSystem.Handle, screens: ArrayDeque) { + screens.asReversed().forEach { it.exit() } + screens.clear() + screens.add(screen(handle)) + } + } } sealed interface Handle { @@ -94,7 +136,13 @@ class ScreenManager internal constructor(private val renderSystemHandle: RenderS } } +// internal fun update() { +// screens.forEach { it.update() } +// } + internal fun render(renderSystem: RenderSystem) { - screens.forEach { it.render(renderSystem) } + screenQueue.forEach { it.apply(renderSystemHandle, screens) } + screenQueue.clear() + screens.forEach { it.render(renderSystem, this) } } } diff --git a/src/kernel/client/kotlin/terramodulus/mui/gms/impl/GeomComponent.kt b/src/kernel/client/kotlin/terramodulus/mui/gms/impl/GeomComponent.kt new file mode 100644 index 00000000..54483137 --- /dev/null +++ b/src/kernel/client/kotlin/terramodulus/mui/gms/impl/GeomComponent.kt @@ -0,0 +1,17 @@ +/* + * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package terramodulus.mui.gms.impl + +import terramodulus.mui.gfx.GuiGeometry +import terramodulus.mui.gfx.RectangleI +import terramodulus.mui.gfx.RenderSystem +import terramodulus.mui.gms.Component + +class GeomComponent(val geom: GuiGeometry, override var rect: RectangleI) : Component() { + override fun render(renderSystem: RenderSystem) { + geom.render(renderSystem) + } +} diff --git a/src/kernel/client/kotlin/terramodulus/mui/gms/impl/LaunchingScreen.kt b/src/kernel/client/kotlin/terramodulus/mui/gms/impl/LaunchingScreen.kt index 2c01eb24..f52aadee 100644 --- a/src/kernel/client/kotlin/terramodulus/mui/gms/impl/LaunchingScreen.kt +++ b/src/kernel/client/kotlin/terramodulus/mui/gms/impl/LaunchingScreen.kt @@ -5,16 +5,76 @@ package terramodulus.mui.gms.impl +import terramodulus.mui.gfx.AlphaFilter +import terramodulus.mui.gfx.Dimension2I +import terramodulus.mui.gfx.FullScaling +import terramodulus.mui.gfx.GuiLine +import terramodulus.mui.gfx.GuiRect import terramodulus.mui.gfx.RectangleI import terramodulus.mui.gfx.RenderSystem +import terramodulus.mui.gfx.SmartScaling import terramodulus.mui.gms.Screen +import terramodulus.mui.gms.ScreenManager + +private val REF_SIZE = Dimension2I(800, 480) + +private val BG_COLOR = floatArrayOf(.145F, .776F, 0.768F) + +private const val ANI_DURATION = .75F // in second + +private const val PAUSE_DURATION = 1 // in second internal class LaunchingScreen(renderSystemHandle: RenderSystem.Handle) : Screen() { + private var alpha = 0F + private var stage = 0 + private var last = System.currentTimeMillis() // timestamp in milliseconds + private var alphaFilter = AlphaFilter(alpha) + init { - addComponent(SpriteComponent(RectangleI(0, 0, 400, 100), renderSystemHandle.loadTexture("/test.png"))) + GeomComponent(GuiRect(0, 0, 800, 480, 37, 198, 196, 255), RectangleI(0, 0, 0, 0)).apply { + geom.add(alphaFilter) + geom.add(FullScaling(REF_SIZE)) + addComponent(this) + } + SpriteComponent(RectangleI(0, 0, 400, 100), renderSystemHandle.loadTexture("/logo.png")).apply { + sprite.add(alphaFilter) + sprite.add(SmartScaling.both(REF_SIZE.width, REF_SIZE.height, 400, 100)) + addComponent(this) + } } - override fun exit() { - TODO("Not yet implemented") + override fun update(renderSystem: RenderSystem, screenManager: ScreenManager) { + val current = System.currentTimeMillis() + val elapsed = (current - last) / 1000F // elapsed time for this stage + when (stage) { + 0 -> if (elapsed >= ANI_DURATION) { + stage = 1 + last = current + alpha = 1F + alphaFilter.alpha = alpha + } else { + alpha = elapsed / ANI_DURATION + alphaFilter.alpha = alpha + } + + 1 -> if (elapsed >= PAUSE_DURATION) { + stage = 2 + last = current + } + + 2 -> if (elapsed >= ANI_DURATION) { + stage = 3 + last = current + alpha = 0F + alphaFilter.alpha = alpha + } else { + alpha = 1 - elapsed / ANI_DURATION + alphaFilter.alpha = alpha + } + + 3 -> screenManager.handle.reset(::ResourceLoadingScreen) + } } + + override fun exit() {} } diff --git a/src/kernel/client/kotlin/terramodulus/mui/gms/impl/ResourceLoadingScreen.kt b/src/kernel/client/kotlin/terramodulus/mui/gms/impl/ResourceLoadingScreen.kt index a48a0446..992472ee 100644 --- a/src/kernel/client/kotlin/terramodulus/mui/gms/impl/ResourceLoadingScreen.kt +++ b/src/kernel/client/kotlin/terramodulus/mui/gms/impl/ResourceLoadingScreen.kt @@ -5,9 +5,15 @@ package terramodulus.mui.gms.impl +import terramodulus.mui.gfx.RectangleI +import terramodulus.mui.gfx.RenderSystem import terramodulus.mui.gms.Screen -class ResourceLoadingScreen : Screen() { +class ResourceLoadingScreen(renderSystemHandle: RenderSystem.Handle) : Screen() { + init { + addComponent(SpriteComponent(RectangleI(0, 0, 400, 100), renderSystemHandle.loadTexture("/test.png"))) + } + override fun exit() { TODO("Not yet implemented") } diff --git a/src/kernel/client/kotlin/terramodulus/mui/gms/impl/SpriteComponent.kt b/src/kernel/client/kotlin/terramodulus/mui/gms/impl/SpriteComponent.kt index 2d0b91bc..8253e639 100644 --- a/src/kernel/client/kotlin/terramodulus/mui/gms/impl/SpriteComponent.kt +++ b/src/kernel/client/kotlin/terramodulus/mui/gms/impl/SpriteComponent.kt @@ -11,18 +11,9 @@ import terramodulus.mui.gfx.RenderSystem import terramodulus.mui.gms.Component class SpriteComponent(override var rect: RectangleI, texture: UInt) : Component() { - private val sprite = GuiSprite(rect, texture) - private var tick = 0; - private val alpha = sprite.alpha(0F); - - init { - sprite.add(sprite.smartScaling(800, 480)) - sprite.add(alpha) - } + val sprite = GuiSprite(rect, texture) override fun render(renderSystem: RenderSystem) { sprite.render(renderSystem) - tick = (tick + 1) % 50 - alpha.alpha = tick / 50F } } diff --git a/src/kernel/client/resources/gms_geo.fsh b/src/kernel/client/resources/gms_geo.fsh index c5672186..d697ce6b 100644 --- a/src/kernel/client/resources/gms_geo.fsh +++ b/src/kernel/client/resources/gms_geo.fsh @@ -2,6 +2,8 @@ varying vec4 texColor; +uniform mat4 filter; + void main() { - gl_FragColor = texColor; + gl_FragColor = filter * texColor; } diff --git a/src/kernel/client/resources/logo.png b/src/kernel/client/resources/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..e730454d53b57e80f81f52b97793298641751f01 GIT binary patch literal 20376 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 L9nMys_Pp~SPn9;# literal 0 HcmV?d00001 diff --git a/src/kernel/common/kotlin/terramodulus/common/core/Core.kt b/src/kernel/common/kotlin/terramodulus/common/core/Core.kt index 72461b51..afad88aa 100644 --- a/src/kernel/common/kotlin/terramodulus/common/core/Core.kt +++ b/src/kernel/common/kotlin/terramodulus/common/core/Core.kt @@ -11,6 +11,7 @@ import terramodulus.util.exception.Error import terramodulus.util.exception.Fault import terramodulus.util.exception.UnhandledExceptionFault import terramodulus.util.exception.triggerGlobalCrash +import terramodulus.util.logging.initLogging import terramodulus.util.logging.logger import java.io.Closeable import java.io.File @@ -20,7 +21,10 @@ import java.nio.channels.FileLock import java.nio.file.Files import java.nio.file.Path -private val logger = logger {} +private val logger = run { + initLogging() + logger {} +} /** * This should only be used by `terramodulus.core.Main` in the beginning of `main` function. diff --git a/src/kernel/common/kotlin/terramodulus/util/logging/Core.kt b/src/kernel/common/kotlin/terramodulus/util/logging/Core.kt index 0b7b4fb9..0adf7b81 100644 --- a/src/kernel/common/kotlin/terramodulus/util/logging/Core.kt +++ b/src/kernel/common/kotlin/terramodulus/util/logging/Core.kt @@ -6,6 +6,37 @@ package terramodulus.util.logging import io.github.oshai.kotlinlogging.KLogger +import io.github.oshai.kotlinlogging.Level as KLevel import io.github.oshai.kotlinlogging.KotlinLogging +import org.apache.logging.log4j.Level +import org.apache.logging.log4j.status.StatusData +import org.apache.logging.log4j.status.StatusListener +import org.apache.logging.log4j.status.StatusLogger fun logger(func: () -> Unit): KLogger = KotlinLogging.logger(func) + +internal fun initLogging() { + System.setProperty("log4j2.debug", "true") + StatusLogger.getLogger().registerListener(object : StatusListener { + override fun close() {} + + override fun log(data: StatusData) { + logger.at(when (data.level) { + Level.FATAL -> KLevel.ERROR + Level.ERROR -> KLevel.ERROR + Level.WARN -> KLevel.WARN + Level.INFO -> KLevel.INFO + Level.DEBUG -> KLevel.DEBUG + Level.TRACE -> KLevel.TRACE + else -> throw IllegalArgumentException("Unsupported log level: ${data.level}") + }) { + cause = data.throwable + message = data.message.formattedMessage + } + } + + override fun getStatusLevel() = Level.ALL + }) +} + +private val logger = logger {} diff --git a/src/kernel/common/resources/log4j2.xml b/src/kernel/common/resources/log4j2.xml index 7feaefe2..8a0e4037 100644 --- a/src/kernel/common/resources/log4j2.xml +++ b/src/kernel/common/resources/log4j2.xml @@ -4,7 +4,7 @@ ~ SPDX-License-Identifier: LGPL-3.0-only --> - From bd6b90a1b95767fff34e5a2cb17ecab46e068fa6 Mon Sep 17 00:00:00 2001 From: Ben Forge <74168521+BenCheung0422@users.noreply.github.com> Date: Tue, 17 Jun 2025 05:39:00 +0800 Subject: [PATCH 18/19] GMS: Drafting advanced features --- ferricia | 2 +- .../kotlin/terramodulus/engine/Drawable.kt | 6 +- .../terramodulus/mui/gfx/ManagedRect.kt | 22 ++++ .../terramodulus/mui/gms/AbstractPanel.kt | 9 ++ .../kotlin/terramodulus/mui/gms/Component.kt | 30 ++++- .../kotlin/terramodulus/mui/gms/Container.kt | 12 ++ .../kotlin/terramodulus/mui/gms/Layout.kt | 120 ++++++++++++++++++ .../kotlin/terramodulus/mui/gms/Menu.kt | 17 ++- .../kotlin/terramodulus/mui/gms/Screen.kt | 14 +- .../terramodulus/mui/gms/ScreenManager.kt | 31 +++-- .../mui/gms/event/ComponentEvent.kt | 9 ++ .../terramodulus/mui/gms/event/MenuEvent.kt | 9 ++ .../terramodulus/mui/gms/event/ScreenEvent.kt | 4 +- .../mui/gms/impl/BlankComponent.kt | 16 +++ .../mui/gms/impl/FlexibleBoxLayout.kt | 20 +++ .../mui/gms/impl/GeomComponent.kt | 3 +- .../mui/gms/impl/LaunchingScreen.kt | 22 ++-- .../mui/gms/impl/PositioningComponent.kt | 15 +++ .../mui/gms/impl/ResourceLoadingScreen.kt | 75 ++++++++++- .../mui/gms/impl/SequenceLayout.kt | 83 ++++++++++++ .../mui/gms/impl/SpriteComponent.kt | 5 +- .../terramodulus/mui/gms/impl/TitleScreen.kt | 13 ++ .../resources/{logo.png => game_logo.png} | Bin src/kernel/client/resources/studio_logo.png | Bin 0 -> 107419 bytes src/kernel/client/resources/test.png | Bin 6602 -> 0 bytes 25 files changed, 487 insertions(+), 50 deletions(-) create mode 100644 src/kernel/client/kotlin/terramodulus/mui/gfx/ManagedRect.kt create mode 100644 src/kernel/client/kotlin/terramodulus/mui/gms/AbstractPanel.kt create mode 100644 src/kernel/client/kotlin/terramodulus/mui/gms/Container.kt create mode 100644 src/kernel/client/kotlin/terramodulus/mui/gms/Layout.kt create mode 100644 src/kernel/client/kotlin/terramodulus/mui/gms/event/ComponentEvent.kt create mode 100644 src/kernel/client/kotlin/terramodulus/mui/gms/event/MenuEvent.kt create mode 100644 src/kernel/client/kotlin/terramodulus/mui/gms/impl/BlankComponent.kt create mode 100644 src/kernel/client/kotlin/terramodulus/mui/gms/impl/FlexibleBoxLayout.kt create mode 100644 src/kernel/client/kotlin/terramodulus/mui/gms/impl/PositioningComponent.kt create mode 100644 src/kernel/client/kotlin/terramodulus/mui/gms/impl/SequenceLayout.kt create mode 100644 src/kernel/client/kotlin/terramodulus/mui/gms/impl/TitleScreen.kt rename src/kernel/client/resources/{logo.png => game_logo.png} (100%) create mode 100644 src/kernel/client/resources/studio_logo.png delete mode 100644 src/kernel/client/resources/test.png diff --git a/ferricia b/ferricia index 29903d15..f1c9c3a1 160000 --- a/ferricia +++ b/ferricia @@ -1 +1 @@ -Subproject commit 29903d15ea2b046541e1af32a6673981222c399b +Subproject commit f1c9c3a1dc12e65d25cc9f94069afd1f1d375edc diff --git a/src/internal/client/kotlin/terramodulus/engine/Drawable.kt b/src/internal/client/kotlin/terramodulus/engine/Drawable.kt index 157f010a..870000dd 100644 --- a/src/internal/client/kotlin/terramodulus/engine/Drawable.kt +++ b/src/internal/client/kotlin/terramodulus/engine/Drawable.kt @@ -11,9 +11,5 @@ import terramodulus.engine.ferricia.Mui.addModelTransform sealed class Drawable(internal val handle: ULong) { fun add(model: ModelTransform) = addModelTransform(handle, model.wideHandle) - fun add(filter: ColorFilter) { - println(this) - println(filter) - addColorFilter(handle, filter.wideHandle) - } + fun add(filter: ColorFilter) = addColorFilter(handle, filter.wideHandle) } diff --git a/src/kernel/client/kotlin/terramodulus/mui/gfx/ManagedRect.kt b/src/kernel/client/kotlin/terramodulus/mui/gfx/ManagedRect.kt new file mode 100644 index 00000000..5f251f8f --- /dev/null +++ b/src/kernel/client/kotlin/terramodulus/mui/gfx/ManagedRect.kt @@ -0,0 +1,22 @@ +/* + * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package terramodulus.mui.gfx + +import kotlin.properties.Delegates.observable + +class ManagedRect(rect: RectangleF) { + var rect: RectangleF by observable(rect) { _, _, newValue -> observers.forEach { it(newValue) } } + + private val observers = LinkedHashSet<(RectangleF) -> Unit>() + + fun observe(observer: (RectangleF) -> Unit) { + observers.add(observer) + } + + fun unobserve(observer: (RectangleF) -> Unit) { + observers.remove(observer) + } +} diff --git a/src/kernel/client/kotlin/terramodulus/mui/gms/AbstractPanel.kt b/src/kernel/client/kotlin/terramodulus/mui/gms/AbstractPanel.kt new file mode 100644 index 00000000..3582d919 --- /dev/null +++ b/src/kernel/client/kotlin/terramodulus/mui/gms/AbstractPanel.kt @@ -0,0 +1,9 @@ +/* + * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package terramodulus.mui.gms + +abstract class AbstractPanel : Component(), Container { +} diff --git a/src/kernel/client/kotlin/terramodulus/mui/gms/Component.kt b/src/kernel/client/kotlin/terramodulus/mui/gms/Component.kt index 4da3f8fc..acda77de 100644 --- a/src/kernel/client/kotlin/terramodulus/mui/gms/Component.kt +++ b/src/kernel/client/kotlin/terramodulus/mui/gms/Component.kt @@ -5,11 +5,37 @@ package terramodulus.mui.gms -import terramodulus.mui.gfx.RectangleI +import terramodulus.mui.gfx.ManagedRect import terramodulus.mui.gfx.RenderSystem +import terramodulus.mui.gms.event.ComponentEvent +/** + * [Component] can only be contained by only one [Container]. + * + * It is an undefined behavior when the `Component` is contained repeatedly + * or in different containers simultaneously. + */ abstract class Component { - abstract var rect: RectangleI + private val listeners = HashMap, LinkedHashSet<(ComponentEvent) -> Unit>>() + + /** + * This should only be modified by [Layout] managers. + */ + open lateinit var rect: ManagedRect + internal set abstract fun render(renderSystem: RenderSystem) + + fun addListener(e: Class, l: (T) -> Unit) { + @Suppress("UNCHECKED_CAST") + listeners.computeIfAbsent(e) { LinkedHashSet() }.add(l as (ComponentEvent) -> Unit) + } + + fun removeListener(e: Class, l: (T) -> Unit) { + listeners[e]?.remove(l) + } + + internal fun dispatchEvent(event: ComponentEvent) { + listeners[event.javaClass]?.forEach { it(event) } + } } diff --git a/src/kernel/client/kotlin/terramodulus/mui/gms/Container.kt b/src/kernel/client/kotlin/terramodulus/mui/gms/Container.kt new file mode 100644 index 00000000..59788138 --- /dev/null +++ b/src/kernel/client/kotlin/terramodulus/mui/gms/Container.kt @@ -0,0 +1,12 @@ +/* + * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package terramodulus.mui.gms + +import terramodulus.mui.gfx.ManagedRect + +sealed interface Container { + val rect: ManagedRect +} diff --git a/src/kernel/client/kotlin/terramodulus/mui/gms/Layout.kt b/src/kernel/client/kotlin/terramodulus/mui/gms/Layout.kt new file mode 100644 index 00000000..6fd00483 --- /dev/null +++ b/src/kernel/client/kotlin/terramodulus/mui/gms/Layout.kt @@ -0,0 +1,120 @@ +/* + * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package terramodulus.mui.gms + +import terramodulus.mui.gfx.RectangleF +import kotlin.reflect.KProperty + +/** + * [Layout] is always mutable. + * + * **Layout** is defined only when all its managed components all belong to the container + * associated with this layout manager *exclusively*. + */ +abstract class Layout(private val container: Container) { + companion object { + const val ALIGN_START = 0F; + const val ALIGN_CENTER = .5F; + const val ALIGN_END = 1F; + } + + abstract val components: Iterable + + private val containerObserver = ::layout.apply(container.rect::observe) + + /** + * Updates the layout output using the current layout configurations + * by invoking [layout] internally. + * + * It is recommended to invoke this when this layout is being initialized + * or any layout configuration has been changed. + */ + fun update() = layout(container.rect.rect) + + /** + * Lays out the managed [components] by this [Layout] manager. + * + * Only the `rect`s of the managed `components` should be (re)assigned; + * no other state-changing operations should be done beside this. + * @param rect the rectangle of the container at this moment + */ + protected abstract fun layout(rect: RectangleF) + + /** + * Must be invoked when this [Layout] is no longer in use. + */ + fun clear() { + container.rect.unobserve(containerObserver) + } + + /** + * @param components must not be empty + */ + protected class ComponentIterable(private vararg val components: KProperty) : Iterable { + override fun iterator(): Iterator = object : Iterator { + private var index = 0 + + private fun untilNotNull(): Boolean { + do { + if (components[index].getter.call() != null) + return true + else + index++ + } while (index < components.size) + return false + } + + override fun hasNext(): Boolean = untilNotNull() + + override fun next(): Component = if (untilNotNull()) { + components[index].getter.call()!! + } else { + throw NoSuchElementException() + } + } + } + + /** + * Internal list of the layout elements. + */ + protected class ElementList private constructor( + private val components: MutableList, // order is defined here + private val elementMap: MutableMap, // elements are mapped here + ) : Iterable> { + companion object { + fun withComponentsDefault(default: () -> E, vararg components: Component): ElementList { + val map = hashMapOf() + components.forEach { map[it] = default() } + return ElementList(mutableListOf(*components), map) + } + + fun withComponentsDefault(default: () -> E, components: Collection): ElementList { + val map = hashMapOf() + components.forEach { map[it] = default() } + return ElementList(ArrayList(components), map) + } + + fun withElements(vararg elements: Pair): ElementList = + ElementList(elements.mapTo(ArrayList(elements.size)) { it.first }, hashMapOf(*elements)) + + fun withElements(elements: Map): ElementList = + ElementList(elements.mapTo(ArrayList(elements.size)) { it.key }, HashMap(elements)) + } + + val componentsView: Collection = components + + override fun iterator(): Iterator> = object : Iterator> { + private val it = components.iterator() + + override fun hasNext() = it.hasNext() + + override fun next(): Pair { + val e = it.next() + return e to elementMap[e]!! + } + } + } +} diff --git a/src/kernel/client/kotlin/terramodulus/mui/gms/Menu.kt b/src/kernel/client/kotlin/terramodulus/mui/gms/Menu.kt index b4704109..b3ab96a2 100644 --- a/src/kernel/client/kotlin/terramodulus/mui/gms/Menu.kt +++ b/src/kernel/client/kotlin/terramodulus/mui/gms/Menu.kt @@ -5,9 +5,11 @@ package terramodulus.mui.gms +import terramodulus.mui.gms.event.MenuEvent import java.util.ArrayDeque -abstract class Menu { +abstract class Menu : Container { + private val listeners = HashMap, LinkedHashSet<(MenuEvent) -> Unit>>() private val components = LinkedHashSet() private val componentQueue = ArrayDeque() val handle: Handle = HandleImpl() @@ -32,6 +34,19 @@ abstract class Menu { components.remove(component) } + fun addListener(e: Class, l: (T) -> Unit) { + @Suppress("UNCHECKED_CAST") + listeners.computeIfAbsent(e) { LinkedHashSet() }.add(l as (MenuEvent) -> Unit) + } + + fun removeListener(e: Class, l: (T) -> Unit) { + listeners[e]?.remove(l) + } + + internal fun dispatchEvent(event: MenuEvent) { + listeners[event.javaClass]?.forEach { it(event) } + } + sealed interface Handle { fun addComponent(component: () -> Component) diff --git a/src/kernel/client/kotlin/terramodulus/mui/gms/Screen.kt b/src/kernel/client/kotlin/terramodulus/mui/gms/Screen.kt index b65f2abf..528c4976 100644 --- a/src/kernel/client/kotlin/terramodulus/mui/gms/Screen.kt +++ b/src/kernel/client/kotlin/terramodulus/mui/gms/Screen.kt @@ -9,25 +9,25 @@ import terramodulus.mui.gfx.RenderSystem import terramodulus.mui.gms.event.ScreenEvent import java.util.ArrayDeque -abstract class Screen { +abstract class Screen : Container { private val listeners = HashMap, LinkedHashSet<(ScreenEvent) -> Unit>>() private val menus = LinkedHashSet() - private val components = LinkedHashSet() + private val components = ArrayList() private val componentQueue = ArrayDeque() private val menuQueue = ArrayDeque() val handle: Handle = HandleImpl() private sealed interface ComponentOperation { - fun apply(components: LinkedHashSet) + fun apply(components: ArrayList) class Add(val component: () -> Component) : ComponentOperation { - override fun apply(components: LinkedHashSet) { + override fun apply(components: ArrayList) { components.add(component()) } } class Remove(val component: Component) : ComponentOperation { - override fun apply(components: LinkedHashSet) { + override fun apply(components: ArrayList) { components.remove(component) } } @@ -86,6 +86,10 @@ abstract class Screen { listeners[e]?.remove(l) } + internal fun dispatchEvent(event: ScreenEvent) { + listeners[event.javaClass]?.forEach { it(event) } + } + sealed interface Handle { fun addComponent(component: () -> Component) diff --git a/src/kernel/client/kotlin/terramodulus/mui/gms/ScreenManager.kt b/src/kernel/client/kotlin/terramodulus/mui/gms/ScreenManager.kt index e4484052..eea9e18b 100644 --- a/src/kernel/client/kotlin/terramodulus/mui/gms/ScreenManager.kt +++ b/src/kernel/client/kotlin/terramodulus/mui/gms/ScreenManager.kt @@ -54,6 +54,15 @@ class ScreenManager internal constructor(private val renderSystemHandle: RenderS } } + /** + * Opens the `screen` before the `target` screen + */ + class OpenBefore(val screen: (RenderSystem.Handle) -> Screen, val target: Screen) : ScreenOperation { + override fun apply(handle: RenderSystem.Handle, screens: ArrayDeque) { + screens.add(screens.lastIndexOf(target), screen(handle)) + } + } + /** * Exits until reaching the `screen` then remains on the `screen` */ @@ -95,6 +104,12 @@ class ScreenManager internal constructor(private val renderSystemHandle: RenderS */ fun open(screen: (RenderSystem.Handle) -> Screen) + /** + * It is not recommended to use this in general scenarios. + * @see ScreenOperation.OpenBefore + */ + fun openBefore(screen: (RenderSystem.Handle) -> Screen, target: Screen) + /** * @see ScreenOperation.ExitTo */ @@ -107,30 +122,22 @@ class ScreenManager internal constructor(private val renderSystemHandle: RenderS } private inner class HandleImpl : Handle { - /** - * @see ScreenOperation.Exit - */ override fun exit(n: Int) { screenQueue.add(ScreenOperation.Exit(n)) } - /** - * @see ScreenOperation.Open - */ override fun open(screen: (RenderSystem.Handle) -> Screen) { screenQueue.add(ScreenOperation.Open(screen)) } - /** - * @see ScreenOperation.ExitTo - */ + override fun openBefore(screen: (RenderSystem.Handle) -> Screen, target: Screen) { + screenQueue.add(ScreenOperation.OpenBefore(screen, target)) + } + override fun exitTo(screen: Screen) { screenQueue.add(ScreenOperation.ExitTo(screen)) } - /** - * @see ScreenOperation.Reset - */ override fun reset(screen: (RenderSystem.Handle) -> Screen) { screenQueue.add(ScreenOperation.Reset(screen)) } diff --git a/src/kernel/client/kotlin/terramodulus/mui/gms/event/ComponentEvent.kt b/src/kernel/client/kotlin/terramodulus/mui/gms/event/ComponentEvent.kt new file mode 100644 index 00000000..5a7ae4de --- /dev/null +++ b/src/kernel/client/kotlin/terramodulus/mui/gms/event/ComponentEvent.kt @@ -0,0 +1,9 @@ +/* + * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package terramodulus.mui.gms.event + +sealed interface ComponentEvent { +} diff --git a/src/kernel/client/kotlin/terramodulus/mui/gms/event/MenuEvent.kt b/src/kernel/client/kotlin/terramodulus/mui/gms/event/MenuEvent.kt new file mode 100644 index 00000000..d60a4595 --- /dev/null +++ b/src/kernel/client/kotlin/terramodulus/mui/gms/event/MenuEvent.kt @@ -0,0 +1,9 @@ +/* + * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package terramodulus.mui.gms.event + +sealed interface MenuEvent { +} diff --git a/src/kernel/client/kotlin/terramodulus/mui/gms/event/ScreenEvent.kt b/src/kernel/client/kotlin/terramodulus/mui/gms/event/ScreenEvent.kt index 402dd86e..b49731b9 100644 --- a/src/kernel/client/kotlin/terramodulus/mui/gms/event/ScreenEvent.kt +++ b/src/kernel/client/kotlin/terramodulus/mui/gms/event/ScreenEvent.kt @@ -6,6 +6,6 @@ package terramodulus.mui.gms.event sealed interface ScreenEvent { - object Open : ScreenEvent - object Close : ScreenEvent + data object Open : ScreenEvent + data object Close : ScreenEvent } diff --git a/src/kernel/client/kotlin/terramodulus/mui/gms/impl/BlankComponent.kt b/src/kernel/client/kotlin/terramodulus/mui/gms/impl/BlankComponent.kt new file mode 100644 index 00000000..a8d809c7 --- /dev/null +++ b/src/kernel/client/kotlin/terramodulus/mui/gms/impl/BlankComponent.kt @@ -0,0 +1,16 @@ +/* + * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package terramodulus.mui.gms.impl + +import terramodulus.mui.gfx.RenderSystem +import terramodulus.mui.gms.Component + +/** + * This can act as a placeholder [Component] in a [Layout][terramodulus.mui.gms.Layout]. + */ +class BlankComponent : Component() { + override fun render(renderSystem: RenderSystem) {} +} diff --git a/src/kernel/client/kotlin/terramodulus/mui/gms/impl/FlexibleBoxLayout.kt b/src/kernel/client/kotlin/terramodulus/mui/gms/impl/FlexibleBoxLayout.kt new file mode 100644 index 00000000..8c62f470 --- /dev/null +++ b/src/kernel/client/kotlin/terramodulus/mui/gms/impl/FlexibleBoxLayout.kt @@ -0,0 +1,20 @@ +/* + * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package terramodulus.mui.gms.impl + +import terramodulus.mui.gfx.RectangleF +import terramodulus.mui.gms.Component +import terramodulus.mui.gms.Container +import terramodulus.mui.gms.Layout + +class FlexibleBoxLayout(container: Container) : Layout(container) { + override val components: Iterable + get() = TODO("Not yet implemented") + + override fun layout(rect: RectangleF) { + TODO("Not yet implemented") + } +} diff --git a/src/kernel/client/kotlin/terramodulus/mui/gms/impl/GeomComponent.kt b/src/kernel/client/kotlin/terramodulus/mui/gms/impl/GeomComponent.kt index 54483137..ab4e38d1 100644 --- a/src/kernel/client/kotlin/terramodulus/mui/gms/impl/GeomComponent.kt +++ b/src/kernel/client/kotlin/terramodulus/mui/gms/impl/GeomComponent.kt @@ -6,11 +6,10 @@ package terramodulus.mui.gms.impl import terramodulus.mui.gfx.GuiGeometry -import terramodulus.mui.gfx.RectangleI import terramodulus.mui.gfx.RenderSystem import terramodulus.mui.gms.Component -class GeomComponent(val geom: GuiGeometry, override var rect: RectangleI) : Component() { +class GeomComponent(val geom: GuiGeometry) : Component() { override fun render(renderSystem: RenderSystem) { geom.render(renderSystem) } diff --git a/src/kernel/client/kotlin/terramodulus/mui/gms/impl/LaunchingScreen.kt b/src/kernel/client/kotlin/terramodulus/mui/gms/impl/LaunchingScreen.kt index f52aadee..31b918cb 100644 --- a/src/kernel/client/kotlin/terramodulus/mui/gms/impl/LaunchingScreen.kt +++ b/src/kernel/client/kotlin/terramodulus/mui/gms/impl/LaunchingScreen.kt @@ -8,7 +8,6 @@ package terramodulus.mui.gms.impl import terramodulus.mui.gfx.AlphaFilter import terramodulus.mui.gfx.Dimension2I import terramodulus.mui.gfx.FullScaling -import terramodulus.mui.gfx.GuiLine import terramodulus.mui.gfx.GuiRect import terramodulus.mui.gfx.RectangleI import terramodulus.mui.gfx.RenderSystem @@ -25,20 +24,19 @@ private const val ANI_DURATION = .75F // in second private const val PAUSE_DURATION = 1 // in second internal class LaunchingScreen(renderSystemHandle: RenderSystem.Handle) : Screen() { - private var alpha = 0F private var stage = 0 private var last = System.currentTimeMillis() // timestamp in milliseconds - private var alphaFilter = AlphaFilter(alpha) + private var alphaFilter = AlphaFilter(0F) init { - GeomComponent(GuiRect(0, 0, 800, 480, 37, 198, 196, 255), RectangleI(0, 0, 0, 0)).apply { + GeomComponent(GuiRect(0, 0, 800, 480, 37, 198, 196, 255)).apply { geom.add(alphaFilter) geom.add(FullScaling(REF_SIZE)) addComponent(this) } - SpriteComponent(RectangleI(0, 0, 400, 100), renderSystemHandle.loadTexture("/logo.png")).apply { + SpriteComponent(RectangleI(0, 0, 300, 300), renderSystemHandle.loadTexture("/studio_logo.png")).apply { sprite.add(alphaFilter) - sprite.add(SmartScaling.both(REF_SIZE.width, REF_SIZE.height, 400, 100)) + sprite.add(SmartScaling.both(REF_SIZE.width, REF_SIZE.height, 300, 300)) addComponent(this) } } @@ -50,11 +48,9 @@ internal class LaunchingScreen(renderSystemHandle: RenderSystem.Handle) : Screen 0 -> if (elapsed >= ANI_DURATION) { stage = 1 last = current - alpha = 1F - alphaFilter.alpha = alpha + alphaFilter.alpha = 1F } else { - alpha = elapsed / ANI_DURATION - alphaFilter.alpha = alpha + alphaFilter.alpha = elapsed / ANI_DURATION } 1 -> if (elapsed >= PAUSE_DURATION) { @@ -65,11 +61,9 @@ internal class LaunchingScreen(renderSystemHandle: RenderSystem.Handle) : Screen 2 -> if (elapsed >= ANI_DURATION) { stage = 3 last = current - alpha = 0F - alphaFilter.alpha = alpha + alphaFilter.alpha = 0F } else { - alpha = 1 - elapsed / ANI_DURATION - alphaFilter.alpha = alpha + alphaFilter.alpha = 1 - elapsed / ANI_DURATION } 3 -> screenManager.handle.reset(::ResourceLoadingScreen) diff --git a/src/kernel/client/kotlin/terramodulus/mui/gms/impl/PositioningComponent.kt b/src/kernel/client/kotlin/terramodulus/mui/gms/impl/PositioningComponent.kt new file mode 100644 index 00000000..3cffc424 --- /dev/null +++ b/src/kernel/client/kotlin/terramodulus/mui/gms/impl/PositioningComponent.kt @@ -0,0 +1,15 @@ +/* + * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package terramodulus.mui.gms.impl + +import terramodulus.mui.gfx.RenderSystem +import terramodulus.mui.gms.Component + +class PositioningComponent : Component() { + override fun render(renderSystem: RenderSystem) { + TODO("Not yet implemented") + } +} diff --git a/src/kernel/client/kotlin/terramodulus/mui/gms/impl/ResourceLoadingScreen.kt b/src/kernel/client/kotlin/terramodulus/mui/gms/impl/ResourceLoadingScreen.kt index 992472ee..cbb87b89 100644 --- a/src/kernel/client/kotlin/terramodulus/mui/gms/impl/ResourceLoadingScreen.kt +++ b/src/kernel/client/kotlin/terramodulus/mui/gms/impl/ResourceLoadingScreen.kt @@ -5,16 +5,87 @@ package terramodulus.mui.gms.impl +import terramodulus.mui.gfx.AlphaFilter +import terramodulus.mui.gfx.Dimension2I +import terramodulus.mui.gfx.FullScaling +import terramodulus.mui.gfx.GuiRect import terramodulus.mui.gfx.RectangleI import terramodulus.mui.gfx.RenderSystem +import terramodulus.mui.gfx.SmartScaling import terramodulus.mui.gms.Screen +import terramodulus.mui.gms.ScreenManager + +private val REF_SIZE = Dimension2I(800, 480) + +private val CONTENT_SIZE = Dimension2I(400, 200) + +private val BG_COLOR = floatArrayOf(.145F, .776F, 0.768F) + +private const val ANI_DURATION = 1F // in second class ResourceLoadingScreen(renderSystemHandle: RenderSystem.Handle) : Screen() { + private var stage = 0 + private var last = System.currentTimeMillis() // timestamp in milliseconds + private var alphaFilter = AlphaFilter(0F) + init { - addComponent(SpriteComponent(RectangleI(0, 0, 400, 100), renderSystemHandle.loadTexture("/test.png"))) + GeomComponent(GuiRect(0, 0, 800, 480, 37, 198, 196, 255)).apply { + geom.add(alphaFilter) + geom.add(FullScaling(REF_SIZE)) + addComponent(this) + } + val smartScaling = SmartScaling.both(REF_SIZE.width, REF_SIZE.height, CONTENT_SIZE.width, CONTENT_SIZE.height) + SpriteComponent(RectangleI(0, 100, 400, 100), renderSystemHandle.loadTexture("/game_logo.png")).apply { + sprite.add(alphaFilter) + sprite.add(smartScaling) + addComponent(this) + } + GeomComponent(GuiRect(0, 0, 400, 40, 240, 240, 240, 255)).apply { + geom.add(alphaFilter) + geom.add(smartScaling) + addComponent(this) + } + GeomComponent(GuiRect(5, 5, 390, 30, 37, 198, 196, 255)).apply { + geom.add(alphaFilter) + geom.add(smartScaling) + addComponent(this) + } + GeomComponent(GuiRect(8, 8, 384, 24, 240, 240, 240, 255)).apply { + geom.add(alphaFilter) + geom.add(smartScaling) + addComponent(this) + } + } + + override fun update(renderSystem: RenderSystem, screenManager: ScreenManager) { + val current = System.currentTimeMillis() + val elapsed = (current - last) / 1000F // elapsed time for this stage + when (stage) { + 0 -> if (elapsed >= ANI_DURATION) { + stage = 1 + last = current + alphaFilter.alpha = 1F + } else { + alphaFilter.alpha = elapsed / ANI_DURATION + } + + 1 -> { + + } + + 2 -> if (elapsed >= ANI_DURATION) { + stage = 3 + last = current + alphaFilter.alpha = 0F + } else { + alphaFilter.alpha = 1 - elapsed / ANI_DURATION + } + + 3 -> screenManager.handle.openBefore(::TitleScreen, this) + } } override fun exit() { - TODO("Not yet implemented") + } } diff --git a/src/kernel/client/kotlin/terramodulus/mui/gms/impl/SequenceLayout.kt b/src/kernel/client/kotlin/terramodulus/mui/gms/impl/SequenceLayout.kt new file mode 100644 index 00000000..7b0fb4fb --- /dev/null +++ b/src/kernel/client/kotlin/terramodulus/mui/gms/impl/SequenceLayout.kt @@ -0,0 +1,83 @@ +/* + * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package terramodulus.mui.gms.impl + +import terramodulus.mui.gfx.RectangleF +import terramodulus.mui.gms.Component +import terramodulus.mui.gms.Container +import terramodulus.mui.gms.Layout + +/** + * Common implementation that is either [ColumnLayout] or [RowLayout]. + * + * This is an optimized special version of [FlexibleBoxLayout] without any expected + * multiple *sequences* of components in a single layout. + */ +sealed class SequenceLayout(container: Container, protected val elements: ElementList) : Layout(container) { + final override val components = elements.componentsView + + class Element { + companion object { + fun default() = Element() + } + } +} + +/** + * **Column** case of [SequenceLayout]. + */ +class ColumnLayout private constructor(container: Container, elements: ElementList) : + SequenceLayout(container, elements) { + companion object { + fun withComponents(vararg components: Component) = { it: Container -> + ColumnLayout(it, ElementList.withComponentsDefault(Element::default, *components)) + } + + fun withComponents(components: Collection) = { it: Container -> + ColumnLayout(it, ElementList.withComponentsDefault(Element::default, components)) + } + + fun withElements(vararg elements: Pair) = { it: Container -> + ColumnLayout(it, ElementList.withElements(*elements)) + } + + fun withElements(elements: Map) = { it: Container -> + ColumnLayout(it, ElementList.withElements(elements)) + } + } + + override fun layout(rect: RectangleF) { + elements.forEach { TODO() } + } +} + +/** + * **Row** case of [SequenceLayout]. + */ +class RowLayout private constructor(container: Container, elements: ElementList) : + SequenceLayout(container, elements) { + companion object { + fun withComponents(vararg components: Component) = { it: Container -> + RowLayout(it, ElementList.withComponentsDefault(Element::default, *components)) + } + + fun withComponents(components: Collection) = { it: Container -> + RowLayout(it, ElementList.withComponentsDefault(Element::default, components)) + } + + fun withElements(vararg elements: Pair) = { it: Container -> + RowLayout(it, ElementList.withElements(*elements)) + } + + fun withElements(elements: Map) = { it: Container -> + RowLayout(it, ElementList.withElements(elements)) + } + } + + override fun layout(rect: RectangleF) { + elements.forEach { TODO() } + } +} diff --git a/src/kernel/client/kotlin/terramodulus/mui/gms/impl/SpriteComponent.kt b/src/kernel/client/kotlin/terramodulus/mui/gms/impl/SpriteComponent.kt index 8253e639..70c96c2e 100644 --- a/src/kernel/client/kotlin/terramodulus/mui/gms/impl/SpriteComponent.kt +++ b/src/kernel/client/kotlin/terramodulus/mui/gms/impl/SpriteComponent.kt @@ -6,13 +6,10 @@ package terramodulus.mui.gms.impl import terramodulus.mui.gfx.GuiSprite -import terramodulus.mui.gfx.RectangleI import terramodulus.mui.gfx.RenderSystem import terramodulus.mui.gms.Component -class SpriteComponent(override var rect: RectangleI, texture: UInt) : Component() { - val sprite = GuiSprite(rect, texture) - +class SpriteComponent(val sprite: GuiSprite) : Component() { override fun render(renderSystem: RenderSystem) { sprite.render(renderSystem) } diff --git a/src/kernel/client/kotlin/terramodulus/mui/gms/impl/TitleScreen.kt b/src/kernel/client/kotlin/terramodulus/mui/gms/impl/TitleScreen.kt new file mode 100644 index 00000000..ad9a3dca --- /dev/null +++ b/src/kernel/client/kotlin/terramodulus/mui/gms/impl/TitleScreen.kt @@ -0,0 +1,13 @@ +/* + * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package terramodulus.mui.gms.impl + +import terramodulus.mui.gfx.RenderSystem +import terramodulus.mui.gms.Screen + +class TitleScreen(renderSystemHandle: RenderSystem.Handle) : Screen() { + override fun exit() {} +} diff --git a/src/kernel/client/resources/logo.png b/src/kernel/client/resources/game_logo.png similarity index 100% rename from src/kernel/client/resources/logo.png rename to src/kernel/client/resources/game_logo.png diff --git a/src/kernel/client/resources/studio_logo.png b/src/kernel/client/resources/studio_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..bee167f3c75c836e448c1ef3db784336aea18531 GIT binary patch literal 107419 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 D(iDPC literal 0 HcmV?d00001 diff --git a/src/kernel/client/resources/test.png b/src/kernel/client/resources/test.png deleted file mode 100644 index fd357f990ebcb87f132bb095b753ac32d372ac48..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6602 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 Date: Sat, 28 Mar 2026 01:52:44 +0800 Subject: [PATCH 19/19] Various changes All those changes were made for some time multiple times, just not committed for some reasons. --- .idea/codeStyles/Project.xml | 10 + .idea/modules.xml | 9 - build.profile | 0 .../{ => net}/terramodulus/engine/Canvas.kt | 28 +- .../terramodulus/engine/ColorFilter.kt | 6 +- .../{ => net}/terramodulus/engine/Drawable.kt | 6 +- .../terramodulus/engine/GeomDrawable.kt | 6 +- .../terramodulus/engine/MeshDrawable.kt | 4 +- .../terramodulus/engine/ModelTransform.kt | 6 +- .../{ => net}/terramodulus/engine/MuiEvent.kt | 2 +- .../{ => net}/terramodulus/engine/Window.kt | 18 +- .../terramodulus/engine/ferricia/Mui.kt | 4 +- .../engine/ferricia/package-info.java | 4 +- .../{ => net}/terramodulus/engine/Core.kt | 6 +- .../terramodulus/engine/ferricia/Core.kt | 4 +- .../internal/platform/Kernel32.kt | 2 +- .../{ => net}/terramodulus/core/Main.kt | 16 +- .../terramodulus/core/TerraModulus.kt | 6 +- .../{ => net}/terramodulus/mui/AuiManager.kt | 4 +- .../{ => net}/terramodulus/mui/GuiManager.kt | 14 +- .../terramodulus/mui/audio/AudioSystem.kt | 2 +- .../{ => net}/terramodulus/mui/gfx/Anchor.kt | 2 +- .../terramodulus/mui/gfx/ColorFilter.kt | 2 +- .../terramodulus/mui/gfx/Dimension.kt | 2 +- .../terramodulus/mui/gfx/Direction.kt | 2 +- .../terramodulus/mui/gfx/GuiGeometry.kt | 8 +- .../terramodulus/mui/gfx/GuiSprite.kt | 4 +- .../terramodulus/mui/gfx/ManagedRect.kt | 2 +- .../terramodulus/mui/gfx/ModelTransform.kt | 2 +- .../terramodulus/mui/gfx/Rectangle.kt | 2 +- .../terramodulus/mui/gfx/RenderSystem.kt | 8 +- .../{ => net}/terramodulus/mui/gfx/Vector.kt | 2 +- .../terramodulus/mui/gms/AbstractPanel.kt | 2 +- .../terramodulus/mui/gms/Component.kt | 8 +- .../terramodulus/mui/gms/Container.kt | 4 +- .../kotlin/net/terramodulus/mui/gms/Layout.kt | 274 ++++++++++++++++++ .../{ => net}/terramodulus/mui/gms/Menu.kt | 4 +- .../{ => net}/terramodulus/mui/gms/Screen.kt | 9 +- .../terramodulus/mui/gms/ScreenManager.kt | 6 +- .../mui/gms/event/ComponentEvent.kt | 2 +- .../terramodulus/mui/gms/event/MenuEvent.kt | 2 +- .../terramodulus/mui/gms/event/ScreenEvent.kt | 2 +- .../mui/gms/impl/BlankComponent.kt | 4 +- .../mui/gms/impl/FlexibleBoxLayout.kt | 10 +- .../mui/gms/impl/GeomComponent.kt | 8 +- .../mui/gms/impl/GraphicsComponent.kt | 26 ++ .../mui/gms/impl/LaunchingScreen.kt | 20 +- .../mui/gms/impl/PositioningComponent.kt | 4 +- .../mui/gms/impl/ResourceLoadingScreen.kt | 20 +- .../mui/gms/impl/SequenceLayout.kt | 26 +- .../terramodulus/mui/gms/impl/TitleScreen.kt | 6 +- .../terramodulus/mui/input/InputSystem.kt | 2 +- .../terramodulus/settings/Preference.kt | 0 .../terramodulus/settings/Settings.kt | 0 .../kotlin/terramodulus/mui/gms/Layout.kt | 120 -------- .../mui/gms/impl/SpriteComponent.kt | 16 - src/kernel/client/resources/studio_logo.png | Bin 107419 -> 58411 bytes .../common/core/AbstractTerraModulus.kt | 2 +- .../terramodulus/common/core/Core.kt | 16 +- .../{ => net}/terramodulus/core/Core.kt | 2 +- .../net/terramodulus/core/package-info.java | 9 + .../net/terramodulus/util/TypedAnchorMap.kt | 63 ++++ .../util/exception/CodeLogicFault.kt | 2 +- .../net/terramodulus/util/exception/Core.kt | 94 ++++++ .../terramodulus/util/exception/Error.kt | 2 +- .../terramodulus/util/exception/Exception.kt | 2 +- .../terramodulus/util/exception/Failure.kt | 2 +- .../terramodulus/util/exception/Fault.kt | 2 +- .../util/exception/FerriciaEngineFault.kt | 2 +- .../util/exception/UnhandledExceptionFault.kt | 2 +- .../util/exception/package-info.java | 4 +- .../terramodulus/util/logging/Core.kt | 2 +- .../util/logging/DelayedFileAppender.kt | 2 +- .../terramodulus/util/logging/State.kt | 2 +- .../terramodulus/world/WorldManager.kt | 0 .../terramodulus/world/item/Items.kt | 0 .../terramodulus/world/tile/Tiles.kt | 0 .../terramodulus/core/package-info.java | 9 - .../terramodulus/util/exception/Core.kt | 53 ---- .../{ => net}/terramodulus/core/Main.kt | 6 +- .../terramodulus/core/TerraModulus.kt | 4 +- 81 files changed, 666 insertions(+), 384 deletions(-) delete mode 100644 .idea/modules.xml create mode 100644 build.profile rename src/internal/client/kotlin/{ => net}/terramodulus/engine/Canvas.kt (62%) rename src/internal/client/kotlin/{ => net}/terramodulus/engine/ColorFilter.kt (76%) rename src/internal/client/kotlin/{ => net}/terramodulus/engine/Drawable.kt (68%) rename src/internal/client/kotlin/{ => net}/terramodulus/engine/GeomDrawable.kt (76%) rename src/internal/client/kotlin/{ => net}/terramodulus/engine/MeshDrawable.kt (76%) rename src/internal/client/kotlin/{ => net}/terramodulus/engine/ModelTransform.kt (84%) rename src/internal/client/kotlin/{ => net}/terramodulus/engine/MuiEvent.kt (99%) rename src/internal/client/kotlin/{ => net}/terramodulus/engine/Window.kt (54%) rename src/internal/client/kotlin/{ => net}/terramodulus/engine/ferricia/Mui.kt (98%) rename src/internal/client/kotlin/{ => net}/terramodulus/engine/ferricia/package-info.java (53%) rename src/internal/common/kotlin/{ => net}/terramodulus/engine/Core.kt (56%) rename src/internal/common/kotlin/{ => net}/terramodulus/engine/ferricia/Core.kt (83%) rename src/internal/common/kotlin/{ => net}/terramodulus/internal/platform/Kernel32.kt (94%) rename src/kernel/client/kotlin/{ => net}/terramodulus/core/Main.kt (89%) rename src/kernel/client/kotlin/{ => net}/terramodulus/core/TerraModulus.kt (80%) rename src/kernel/client/kotlin/{ => net}/terramodulus/mui/AuiManager.kt (76%) rename src/kernel/client/kotlin/{ => net}/terramodulus/mui/GuiManager.kt (96%) rename src/kernel/client/kotlin/{ => net}/terramodulus/mui/audio/AudioSystem.kt (82%) rename src/kernel/client/kotlin/{ => net}/terramodulus/mui/gfx/Anchor.kt (94%) rename src/kernel/client/kotlin/{ => net}/terramodulus/mui/gfx/ColorFilter.kt (87%) rename src/kernel/client/kotlin/{ => net}/terramodulus/mui/gfx/Dimension.kt (92%) rename src/kernel/client/kotlin/{ => net}/terramodulus/mui/gfx/Direction.kt (97%) rename src/kernel/client/kotlin/{ => net}/terramodulus/mui/gfx/GuiGeometry.kt (79%) rename src/kernel/client/kotlin/{ => net}/terramodulus/mui/gfx/GuiSprite.kt (86%) rename src/kernel/client/kotlin/{ => net}/terramodulus/mui/gfx/ManagedRect.kt (94%) rename src/kernel/client/kotlin/{ => net}/terramodulus/mui/gfx/ModelTransform.kt (91%) rename src/kernel/client/kotlin/{ => net}/terramodulus/mui/gfx/Rectangle.kt (99%) rename src/kernel/client/kotlin/{ => net}/terramodulus/mui/gfx/RenderSystem.kt (89%) rename src/kernel/client/kotlin/{ => net}/terramodulus/mui/gfx/Vector.kt (97%) rename src/kernel/client/kotlin/{ => net}/terramodulus/mui/gms/AbstractPanel.kt (84%) rename src/kernel/client/kotlin/{ => net}/terramodulus/mui/gms/Component.kt (85%) rename src/kernel/client/kotlin/{ => net}/terramodulus/mui/gms/Container.kt (69%) create mode 100644 src/kernel/client/kotlin/net/terramodulus/mui/gms/Layout.kt rename src/kernel/client/kotlin/{ => net}/terramodulus/mui/gms/Menu.kt (95%) rename src/kernel/client/kotlin/{ => net}/terramodulus/mui/gms/Screen.kt (93%) rename src/kernel/client/kotlin/{ => net}/terramodulus/mui/gms/ScreenManager.kt (96%) rename src/kernel/client/kotlin/{ => net}/terramodulus/mui/gms/event/ComponentEvent.kt (79%) rename src/kernel/client/kotlin/{ => net}/terramodulus/mui/gms/event/MenuEvent.kt (79%) rename src/kernel/client/kotlin/{ => net}/terramodulus/mui/gms/event/ScreenEvent.kt (84%) rename src/kernel/client/kotlin/{ => net}/terramodulus/mui/gms/impl/BlankComponent.kt (79%) rename src/kernel/client/kotlin/{ => net}/terramodulus/mui/gms/impl/FlexibleBoxLayout.kt (63%) rename src/kernel/client/kotlin/{ => net}/terramodulus/mui/gms/impl/GeomComponent.kt (60%) create mode 100644 src/kernel/client/kotlin/net/terramodulus/mui/gms/impl/GraphicsComponent.kt rename src/kernel/client/kotlin/{ => net}/terramodulus/mui/gms/impl/LaunchingScreen.kt (79%) rename src/kernel/client/kotlin/{ => net}/terramodulus/mui/gms/impl/PositioningComponent.kt (76%) rename src/kernel/client/kotlin/{ => net}/terramodulus/mui/gms/impl/ResourceLoadingScreen.kt (83%) rename src/kernel/client/kotlin/{ => net}/terramodulus/mui/gms/impl/SequenceLayout.kt (72%) rename src/kernel/client/kotlin/{ => net}/terramodulus/mui/gms/impl/TitleScreen.kt (64%) rename src/kernel/client/kotlin/{ => net}/terramodulus/mui/input/InputSystem.kt (82%) rename src/kernel/client/kotlin/{ => net}/terramodulus/settings/Preference.kt (100%) rename src/kernel/client/kotlin/{ => net}/terramodulus/settings/Settings.kt (100%) delete mode 100644 src/kernel/client/kotlin/terramodulus/mui/gms/Layout.kt delete mode 100644 src/kernel/client/kotlin/terramodulus/mui/gms/impl/SpriteComponent.kt rename src/kernel/common/kotlin/{ => net}/terramodulus/common/core/AbstractTerraModulus.kt (93%) rename src/kernel/common/kotlin/{ => net}/terramodulus/common/core/Core.kt (84%) rename src/kernel/common/kotlin/{ => net}/terramodulus/core/Core.kt (86%) create mode 100644 src/kernel/common/kotlin/net/terramodulus/core/package-info.java create mode 100644 src/kernel/common/kotlin/net/terramodulus/util/TypedAnchorMap.kt rename src/kernel/common/kotlin/{ => net}/terramodulus/util/exception/CodeLogicFault.kt (94%) create mode 100644 src/kernel/common/kotlin/net/terramodulus/util/exception/Core.kt rename src/kernel/common/kotlin/{ => net}/terramodulus/util/exception/Error.kt (87%) rename src/kernel/common/kotlin/{ => net}/terramodulus/util/exception/Exception.kt (87%) rename src/kernel/common/kotlin/{ => net}/terramodulus/util/exception/Failure.kt (87%) rename src/kernel/common/kotlin/{ => net}/terramodulus/util/exception/Fault.kt (87%) rename src/kernel/common/kotlin/{ => net}/terramodulus/util/exception/FerriciaEngineFault.kt (81%) rename src/kernel/common/kotlin/{ => net}/terramodulus/util/exception/UnhandledExceptionFault.kt (92%) rename src/kernel/common/kotlin/{ => net}/terramodulus/util/exception/package-info.java (66%) rename src/kernel/common/kotlin/{ => net}/terramodulus/util/logging/Core.kt (96%) rename src/kernel/common/kotlin/{ => net}/terramodulus/util/logging/DelayedFileAppender.kt (99%) rename src/kernel/common/kotlin/{ => net}/terramodulus/util/logging/State.kt (86%) rename src/kernel/common/kotlin/{ => net}/terramodulus/world/WorldManager.kt (100%) rename src/kernel/common/kotlin/{ => net}/terramodulus/world/item/Items.kt (100%) rename src/kernel/common/kotlin/{ => net}/terramodulus/world/tile/Tiles.kt (100%) delete mode 100644 src/kernel/common/kotlin/terramodulus/core/package-info.java delete mode 100644 src/kernel/common/kotlin/terramodulus/util/exception/Core.kt rename src/kernel/server/kotlin/{ => net}/terramodulus/core/Main.kt (73%) rename src/kernel/server/kotlin/{ => net}/terramodulus/core/TerraModulus.kt (81%) diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml index 39152545..9b34684f 100644 --- a/.idea/codeStyles/Project.xml +++ b/.idea/codeStyles/Project.xml @@ -3,6 +3,16 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index c81769a0..00000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/build.profile b/build.profile new file mode 100644 index 00000000..e69de29b diff --git a/src/internal/client/kotlin/terramodulus/engine/Canvas.kt b/src/internal/client/kotlin/net/terramodulus/engine/Canvas.kt similarity index 62% rename from src/internal/client/kotlin/terramodulus/engine/Canvas.kt rename to src/internal/client/kotlin/net/terramodulus/engine/Canvas.kt index 7b1d6a6d..911c1900 100644 --- a/src/internal/client/kotlin/terramodulus/engine/Canvas.kt +++ b/src/internal/client/kotlin/net/terramodulus/engine/Canvas.kt @@ -1,21 +1,21 @@ /* - * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-FileCopyrightText: 2025-2026 TerraModulus Team and Contributors * SPDX-License-Identifier: LGPL-3.0-only */ -package terramodulus.engine - -import terramodulus.engine.ferricia.Mui -import terramodulus.engine.ferricia.Mui.clearCanvas -import terramodulus.engine.ferricia.Mui.drawGuiGeo -import terramodulus.engine.ferricia.Mui.drawGuiTex -import terramodulus.engine.ferricia.Mui.dropCanvasHandle -import terramodulus.engine.ferricia.Mui.geoShaders -import terramodulus.engine.ferricia.Mui.getGLVersion -import terramodulus.engine.ferricia.Mui.initCanvasHandle -import terramodulus.engine.ferricia.Mui.loadImageToCanvas -import terramodulus.engine.ferricia.Mui.setCanvasClearColor -import terramodulus.engine.ferricia.Mui.texShaders +package net.terramodulus.engine + +import net.terramodulus.engine.ferricia.Mui +import net.terramodulus.engine.ferricia.Mui.clearCanvas +import net.terramodulus.engine.ferricia.Mui.drawGuiGeo +import net.terramodulus.engine.ferricia.Mui.drawGuiTex +import net.terramodulus.engine.ferricia.Mui.dropCanvasHandle +import net.terramodulus.engine.ferricia.Mui.geoShaders +import net.terramodulus.engine.ferricia.Mui.getGLVersion +import net.terramodulus.engine.ferricia.Mui.initCanvasHandle +import net.terramodulus.engine.ferricia.Mui.loadImageToCanvas +import net.terramodulus.engine.ferricia.Mui.setCanvasClearColor +import net.terramodulus.engine.ferricia.Mui.texShaders import java.io.Closeable /** diff --git a/src/internal/client/kotlin/terramodulus/engine/ColorFilter.kt b/src/internal/client/kotlin/net/terramodulus/engine/ColorFilter.kt similarity index 76% rename from src/internal/client/kotlin/terramodulus/engine/ColorFilter.kt rename to src/internal/client/kotlin/net/terramodulus/engine/ColorFilter.kt index 06b8bef7..713dd697 100644 --- a/src/internal/client/kotlin/terramodulus/engine/ColorFilter.kt +++ b/src/internal/client/kotlin/net/terramodulus/engine/ColorFilter.kt @@ -3,10 +3,10 @@ * SPDX-License-Identifier: LGPL-3.0-only */ -package terramodulus.engine +package net.terramodulus.engine -import terramodulus.engine.ferricia.Mui.editAlphaFilter -import terramodulus.engine.ferricia.Mui.filterAlphaFilter +import net.terramodulus.engine.ferricia.Mui.editAlphaFilter +import net.terramodulus.engine.ferricia.Mui.filterAlphaFilter @OptIn(ExperimentalUnsignedTypes::class) sealed class ColorFilter(handles: ULongArray) { diff --git a/src/internal/client/kotlin/terramodulus/engine/Drawable.kt b/src/internal/client/kotlin/net/terramodulus/engine/Drawable.kt similarity index 68% rename from src/internal/client/kotlin/terramodulus/engine/Drawable.kt rename to src/internal/client/kotlin/net/terramodulus/engine/Drawable.kt index 870000dd..b41997b9 100644 --- a/src/internal/client/kotlin/terramodulus/engine/Drawable.kt +++ b/src/internal/client/kotlin/net/terramodulus/engine/Drawable.kt @@ -3,10 +3,10 @@ * SPDX-License-Identifier: LGPL-3.0-only */ -package terramodulus.engine +package net.terramodulus.engine -import terramodulus.engine.ferricia.Mui.addColorFilter -import terramodulus.engine.ferricia.Mui.addModelTransform +import net.terramodulus.engine.ferricia.Mui.addColorFilter +import net.terramodulus.engine.ferricia.Mui.addModelTransform sealed class Drawable(internal val handle: ULong) { fun add(model: ModelTransform) = addModelTransform(handle, model.wideHandle) diff --git a/src/internal/client/kotlin/terramodulus/engine/GeomDrawable.kt b/src/internal/client/kotlin/net/terramodulus/engine/GeomDrawable.kt similarity index 76% rename from src/internal/client/kotlin/terramodulus/engine/GeomDrawable.kt rename to src/internal/client/kotlin/net/terramodulus/engine/GeomDrawable.kt index b4da6865..3d8ccd53 100644 --- a/src/internal/client/kotlin/terramodulus/engine/GeomDrawable.kt +++ b/src/internal/client/kotlin/net/terramodulus/engine/GeomDrawable.kt @@ -3,10 +3,10 @@ * SPDX-License-Identifier: LGPL-3.0-only */ -package terramodulus.engine +package net.terramodulus.engine -import terramodulus.engine.ferricia.Mui.newSimpleLineGeom -import terramodulus.engine.ferricia.Mui.newSimpleRectGeom +import net.terramodulus.engine.ferricia.Mui.newSimpleLineGeom +import net.terramodulus.engine.ferricia.Mui.newSimpleRectGeom sealed class GeomDrawable(handle: ULong) : Drawable(handle) { } diff --git a/src/internal/client/kotlin/terramodulus/engine/MeshDrawable.kt b/src/internal/client/kotlin/net/terramodulus/engine/MeshDrawable.kt similarity index 76% rename from src/internal/client/kotlin/terramodulus/engine/MeshDrawable.kt rename to src/internal/client/kotlin/net/terramodulus/engine/MeshDrawable.kt index b60c82e4..b9357795 100644 --- a/src/internal/client/kotlin/terramodulus/engine/MeshDrawable.kt +++ b/src/internal/client/kotlin/net/terramodulus/engine/MeshDrawable.kt @@ -3,9 +3,9 @@ * SPDX-License-Identifier: LGPL-3.0-only */ -package terramodulus.engine +package net.terramodulus.engine -import terramodulus.engine.ferricia.Mui.newSpriteMesh +import net.terramodulus.engine.ferricia.Mui.newSpriteMesh sealed class MeshDrawable(handle: ULong) : Drawable(handle) { } diff --git a/src/internal/client/kotlin/terramodulus/engine/ModelTransform.kt b/src/internal/client/kotlin/net/terramodulus/engine/ModelTransform.kt similarity index 84% rename from src/internal/client/kotlin/terramodulus/engine/ModelTransform.kt rename to src/internal/client/kotlin/net/terramodulus/engine/ModelTransform.kt index c7e5645b..556db221 100644 --- a/src/internal/client/kotlin/terramodulus/engine/ModelTransform.kt +++ b/src/internal/client/kotlin/net/terramodulus/engine/ModelTransform.kt @@ -3,10 +3,10 @@ * SPDX-License-Identifier: LGPL-3.0-only */ -package terramodulus.engine +package net.terramodulus.engine -import terramodulus.engine.ferricia.Mui.modelFullScaling -import terramodulus.engine.ferricia.Mui.modelSmartScaling +import net.terramodulus.engine.ferricia.Mui.modelFullScaling +import net.terramodulus.engine.ferricia.Mui.modelSmartScaling @OptIn(ExperimentalUnsignedTypes::class) sealed class ModelTransform(handles: ULongArray) { diff --git a/src/internal/client/kotlin/terramodulus/engine/MuiEvent.kt b/src/internal/client/kotlin/net/terramodulus/engine/MuiEvent.kt similarity index 99% rename from src/internal/client/kotlin/terramodulus/engine/MuiEvent.kt rename to src/internal/client/kotlin/net/terramodulus/engine/MuiEvent.kt index 6f670c53..108dfe61 100644 --- a/src/internal/client/kotlin/terramodulus/engine/MuiEvent.kt +++ b/src/internal/client/kotlin/net/terramodulus/engine/MuiEvent.kt @@ -3,7 +3,7 @@ * SPDX-License-Identifier: LGPL-3.0-only */ -package terramodulus.engine +package net.terramodulus.engine sealed interface MuiEvent { data class DisplayAdded(val displayHandle: Long) : MuiEvent diff --git a/src/internal/client/kotlin/terramodulus/engine/Window.kt b/src/internal/client/kotlin/net/terramodulus/engine/Window.kt similarity index 54% rename from src/internal/client/kotlin/terramodulus/engine/Window.kt rename to src/internal/client/kotlin/net/terramodulus/engine/Window.kt index 969eb47b..ef3c5e54 100644 --- a/src/internal/client/kotlin/terramodulus/engine/Window.kt +++ b/src/internal/client/kotlin/net/terramodulus/engine/Window.kt @@ -3,16 +3,16 @@ * SPDX-License-Identifier: LGPL-3.0-only */ -package terramodulus.engine +package net.terramodulus.engine -import terramodulus.engine.ferricia.Mui.dropSdlHandle -import terramodulus.engine.ferricia.Mui.dropWindowHandle -import terramodulus.engine.ferricia.Mui.initSdlHandle -import terramodulus.engine.ferricia.Mui.initWindowHandle -import terramodulus.engine.ferricia.Mui.resizeGLViewport -import terramodulus.engine.ferricia.Mui.sdlPoll -import terramodulus.engine.ferricia.Mui.showWindow -import terramodulus.engine.ferricia.Mui.swapWindow +import net.terramodulus.engine.ferricia.Mui.dropSdlHandle +import net.terramodulus.engine.ferricia.Mui.dropWindowHandle +import net.terramodulus.engine.ferricia.Mui.initSdlHandle +import net.terramodulus.engine.ferricia.Mui.initWindowHandle +import net.terramodulus.engine.ferricia.Mui.resizeGLViewport +import net.terramodulus.engine.ferricia.Mui.sdlPoll +import net.terramodulus.engine.ferricia.Mui.showWindow +import net.terramodulus.engine.ferricia.Mui.swapWindow import java.io.Closeable /** diff --git a/src/internal/client/kotlin/terramodulus/engine/ferricia/Mui.kt b/src/internal/client/kotlin/net/terramodulus/engine/ferricia/Mui.kt similarity index 98% rename from src/internal/client/kotlin/terramodulus/engine/ferricia/Mui.kt rename to src/internal/client/kotlin/net/terramodulus/engine/ferricia/Mui.kt index bae53762..94b23dd3 100644 --- a/src/internal/client/kotlin/terramodulus/engine/ferricia/Mui.kt +++ b/src/internal/client/kotlin/net/terramodulus/engine/ferricia/Mui.kt @@ -3,9 +3,9 @@ * SPDX-License-Identifier: LGPL-3.0-only */ -package terramodulus.engine.ferricia +package net.terramodulus.engine.ferricia -import terramodulus.engine.MuiEvent +import net.terramodulus.engine.MuiEvent @OptIn(ExperimentalUnsignedTypes::class) internal object Mui { diff --git a/src/internal/client/kotlin/terramodulus/engine/ferricia/package-info.java b/src/internal/client/kotlin/net/terramodulus/engine/ferricia/package-info.java similarity index 53% rename from src/internal/client/kotlin/terramodulus/engine/ferricia/package-info.java rename to src/internal/client/kotlin/net/terramodulus/engine/ferricia/package-info.java index 74afb1ef..8217fb40 100644 --- a/src/internal/client/kotlin/terramodulus/engine/ferricia/package-info.java +++ b/src/internal/client/kotlin/net/terramodulus/engine/ferricia/package-info.java @@ -1,9 +1,9 @@ /* - * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-FileCopyrightText: 2025-2026 TerraModulus Team and Contributors * SPDX-License-Identifier: LGPL-3.0-only */ /** * Contains direct bindings to the Ferricia Engine exposed JNI functions. */ -package terramodulus.engine.ferricia; +package net.terramodulus.engine.ferricia; diff --git a/src/internal/common/kotlin/terramodulus/engine/Core.kt b/src/internal/common/kotlin/net/terramodulus/engine/Core.kt similarity index 56% rename from src/internal/common/kotlin/terramodulus/engine/Core.kt rename to src/internal/common/kotlin/net/terramodulus/engine/Core.kt index fbd31b6f..6e76dea8 100644 --- a/src/internal/common/kotlin/terramodulus/engine/Core.kt +++ b/src/internal/common/kotlin/net/terramodulus/engine/Core.kt @@ -3,10 +3,10 @@ * SPDX-License-Identifier: LGPL-3.0-only */ -package terramodulus.engine +package net.terramodulus.engine -import terramodulus.engine.ferricia.Core -import terramodulus.engine.ferricia.loadLibrary +import net.terramodulus.engine.ferricia.Core +import net.terramodulus.engine.ferricia.loadLibrary fun initEngine() { loadLibrary() diff --git a/src/internal/common/kotlin/terramodulus/engine/ferricia/Core.kt b/src/internal/common/kotlin/net/terramodulus/engine/ferricia/Core.kt similarity index 83% rename from src/internal/common/kotlin/terramodulus/engine/ferricia/Core.kt rename to src/internal/common/kotlin/net/terramodulus/engine/ferricia/Core.kt index 62d5967e..c33e65d3 100644 --- a/src/internal/common/kotlin/terramodulus/engine/ferricia/Core.kt +++ b/src/internal/common/kotlin/net/terramodulus/engine/ferricia/Core.kt @@ -3,9 +3,9 @@ * SPDX-License-Identifier: LGPL-3.0-only */ -package terramodulus.engine.ferricia +package net.terramodulus.engine.ferricia -import terramodulus.internal.platform.Kernel32 +import net.terramodulus.internal.platform.Kernel32 const val NULL: Long = 0; diff --git a/src/internal/common/kotlin/terramodulus/internal/platform/Kernel32.kt b/src/internal/common/kotlin/net/terramodulus/internal/platform/Kernel32.kt similarity index 94% rename from src/internal/common/kotlin/terramodulus/internal/platform/Kernel32.kt rename to src/internal/common/kotlin/net/terramodulus/internal/platform/Kernel32.kt index 17a0e6dc..84541e8b 100644 --- a/src/internal/common/kotlin/terramodulus/internal/platform/Kernel32.kt +++ b/src/internal/common/kotlin/net/terramodulus/internal/platform/Kernel32.kt @@ -3,7 +3,7 @@ * SPDX-License-Identifier: LGPL-3.0-only */ -package terramodulus.internal.platform +package net.terramodulus.internal.platform import com.sun.jna.Native import com.sun.jna.Platform diff --git a/src/kernel/client/kotlin/terramodulus/core/Main.kt b/src/kernel/client/kotlin/net/terramodulus/core/Main.kt similarity index 89% rename from src/kernel/client/kotlin/terramodulus/core/Main.kt rename to src/kernel/client/kotlin/net/terramodulus/core/Main.kt index acfc5bc7..f27c43ad 100644 --- a/src/kernel/client/kotlin/terramodulus/core/Main.kt +++ b/src/kernel/client/kotlin/net/terramodulus/core/Main.kt @@ -3,7 +3,7 @@ * SPDX-License-Identifier: LGPL-3.0-only */ -package terramodulus.core +package net.terramodulus.core import joptsimple.OptionException import joptsimple.OptionParser @@ -11,13 +11,13 @@ import joptsimple.OptionSet import joptsimple.OptionSpec import joptsimple.ValueConversionException import joptsimple.ValueConverter -import terramodulus.common.core.ApplicationArgumentParsingError -import terramodulus.common.core.ApplicationInitializationFault -import terramodulus.common.core.run -import terramodulus.common.core.setupInit -import terramodulus.mui.GuiManager -import terramodulus.util.exception.CodeLogicFault -import terramodulus.util.exception.triggerGlobalCrash +import net.terramodulus.common.core.ApplicationArgumentParsingError +import net.terramodulus.common.core.ApplicationInitializationFault +import net.terramodulus.common.core.run +import net.terramodulus.common.core.setupInit +import net.terramodulus.mui.GuiManager +import net.terramodulus.util.exception.CodeLogicFault +import net.terramodulus.util.exception.triggerGlobalCrash import java.awt.Dimension import java.nio.file.InvalidPathException import java.nio.file.Path diff --git a/src/kernel/client/kotlin/terramodulus/core/TerraModulus.kt b/src/kernel/client/kotlin/net/terramodulus/core/TerraModulus.kt similarity index 80% rename from src/kernel/client/kotlin/terramodulus/core/TerraModulus.kt rename to src/kernel/client/kotlin/net/terramodulus/core/TerraModulus.kt index 1ed220b1..7a615dda 100644 --- a/src/kernel/client/kotlin/terramodulus/core/TerraModulus.kt +++ b/src/kernel/client/kotlin/net/terramodulus/core/TerraModulus.kt @@ -3,10 +3,10 @@ * SPDX-License-Identifier: LGPL-3.0-only */ -package terramodulus.core +package net.terramodulus.core -import terramodulus.common.core.AbstractTerraModulus -import terramodulus.mui.GuiManager +import net.terramodulus.common.core.AbstractTerraModulus +import net.terramodulus.mui.GuiManager class TerraModulus internal constructor() : AbstractTerraModulus() { private val guiManager = GuiManager() diff --git a/src/kernel/client/kotlin/terramodulus/mui/AuiManager.kt b/src/kernel/client/kotlin/net/terramodulus/mui/AuiManager.kt similarity index 76% rename from src/kernel/client/kotlin/terramodulus/mui/AuiManager.kt rename to src/kernel/client/kotlin/net/terramodulus/mui/AuiManager.kt index 3e3ae08d..7d58bc1f 100644 --- a/src/kernel/client/kotlin/terramodulus/mui/AuiManager.kt +++ b/src/kernel/client/kotlin/net/terramodulus/mui/AuiManager.kt @@ -3,9 +3,9 @@ * SPDX-License-Identifier: LGPL-3.0-only */ -package terramodulus.mui +package net.terramodulus.mui -import terramodulus.mui.audio.AudioSystem +import net.terramodulus.mui.audio.AudioSystem /** * Audio User Interface (AUI) Manager diff --git a/src/kernel/client/kotlin/terramodulus/mui/GuiManager.kt b/src/kernel/client/kotlin/net/terramodulus/mui/GuiManager.kt similarity index 96% rename from src/kernel/client/kotlin/terramodulus/mui/GuiManager.kt rename to src/kernel/client/kotlin/net/terramodulus/mui/GuiManager.kt index 04f8ac3e..c95446c5 100644 --- a/src/kernel/client/kotlin/terramodulus/mui/GuiManager.kt +++ b/src/kernel/client/kotlin/net/terramodulus/mui/GuiManager.kt @@ -3,14 +3,14 @@ * SPDX-License-Identifier: LGPL-3.0-only */ -package terramodulus.mui +package net.terramodulus.mui -import terramodulus.engine.MuiEvent -import terramodulus.engine.Window -import terramodulus.mui.gfx.RenderSystem -import terramodulus.mui.gms.ScreenManager -import terramodulus.mui.input.InputSystem -import terramodulus.util.logging.logger +import net.terramodulus.engine.MuiEvent +import net.terramodulus.engine.Window +import net.terramodulus.mui.gfx.RenderSystem +import net.terramodulus.mui.gms.ScreenManager +import net.terramodulus.mui.input.InputSystem +import net.terramodulus.util.logging.logger import java.io.Closeable import java.io.File diff --git a/src/kernel/client/kotlin/terramodulus/mui/audio/AudioSystem.kt b/src/kernel/client/kotlin/net/terramodulus/mui/audio/AudioSystem.kt similarity index 82% rename from src/kernel/client/kotlin/terramodulus/mui/audio/AudioSystem.kt rename to src/kernel/client/kotlin/net/terramodulus/mui/audio/AudioSystem.kt index 0c05c0f3..3f8bc02b 100644 --- a/src/kernel/client/kotlin/terramodulus/mui/audio/AudioSystem.kt +++ b/src/kernel/client/kotlin/net/terramodulus/mui/audio/AudioSystem.kt @@ -3,7 +3,7 @@ * SPDX-License-Identifier: LGPL-3.0-only */ -package terramodulus.mui.audio +package net.terramodulus.mui.audio class AudioSystem internal constructor() { } diff --git a/src/kernel/client/kotlin/terramodulus/mui/gfx/Anchor.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gfx/Anchor.kt similarity index 94% rename from src/kernel/client/kotlin/terramodulus/mui/gfx/Anchor.kt rename to src/kernel/client/kotlin/net/terramodulus/mui/gfx/Anchor.kt index 6065fbd6..b000efe7 100644 --- a/src/kernel/client/kotlin/terramodulus/mui/gfx/Anchor.kt +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gfx/Anchor.kt @@ -3,7 +3,7 @@ * SPDX-License-Identifier: LGPL-3.0-only */ -package terramodulus.mui.gfx +package net.terramodulus.mui.gfx /* * Every set of anchor position directions has different meanings in different context, diff --git a/src/kernel/client/kotlin/terramodulus/mui/gfx/ColorFilter.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gfx/ColorFilter.kt similarity index 87% rename from src/kernel/client/kotlin/terramodulus/mui/gfx/ColorFilter.kt rename to src/kernel/client/kotlin/net/terramodulus/mui/gfx/ColorFilter.kt index 9ed99fb1..94dd0f51 100644 --- a/src/kernel/client/kotlin/terramodulus/mui/gfx/ColorFilter.kt +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gfx/ColorFilter.kt @@ -3,7 +3,7 @@ * SPDX-License-Identifier: LGPL-3.0-only */ -package terramodulus.mui.gfx +package net.terramodulus.mui.gfx typealias ColorFilter = terramodulus.engine.ColorFilter diff --git a/src/kernel/client/kotlin/terramodulus/mui/gfx/Dimension.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gfx/Dimension.kt similarity index 92% rename from src/kernel/client/kotlin/terramodulus/mui/gfx/Dimension.kt rename to src/kernel/client/kotlin/net/terramodulus/mui/gfx/Dimension.kt index df844d8d..6825bcb0 100644 --- a/src/kernel/client/kotlin/terramodulus/mui/gfx/Dimension.kt +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gfx/Dimension.kt @@ -3,7 +3,7 @@ * SPDX-License-Identifier: LGPL-3.0-only */ -package terramodulus.mui.gfx +package net.terramodulus.mui.gfx data class Dimension2I(val width: Int, val height: Int) diff --git a/src/kernel/client/kotlin/terramodulus/mui/gfx/Direction.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gfx/Direction.kt similarity index 97% rename from src/kernel/client/kotlin/terramodulus/mui/gfx/Direction.kt rename to src/kernel/client/kotlin/net/terramodulus/mui/gfx/Direction.kt index 867394e8..621776f8 100644 --- a/src/kernel/client/kotlin/terramodulus/mui/gfx/Direction.kt +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gfx/Direction.kt @@ -3,7 +3,7 @@ * SPDX-License-Identifier: LGPL-3.0-only */ -package terramodulus.mui.gfx +package net.terramodulus.mui.gfx /* * Every set of directions has different meanings in different context, diff --git a/src/kernel/client/kotlin/terramodulus/mui/gfx/GuiGeometry.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gfx/GuiGeometry.kt similarity index 79% rename from src/kernel/client/kotlin/terramodulus/mui/gfx/GuiGeometry.kt rename to src/kernel/client/kotlin/net/terramodulus/mui/gfx/GuiGeometry.kt index 60413c2d..7b94c6c0 100644 --- a/src/kernel/client/kotlin/terramodulus/mui/gfx/GuiGeometry.kt +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gfx/GuiGeometry.kt @@ -3,11 +3,11 @@ * SPDX-License-Identifier: LGPL-3.0-only */ -package terramodulus.mui.gfx +package net.terramodulus.mui.gfx -import terramodulus.engine.GeomDrawable -import terramodulus.engine.SimpleLineGeom -import terramodulus.engine.SimpleRectGeom +import net.terramodulus.engine.GeomDrawable +import net.terramodulus.engine.SimpleLineGeom +import net.terramodulus.engine.SimpleRectGeom sealed class GuiGeometry(protected val geom: GeomDrawable) { fun add(model: ModelTransform) = geom.add(model) diff --git a/src/kernel/client/kotlin/terramodulus/mui/gfx/GuiSprite.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gfx/GuiSprite.kt similarity index 86% rename from src/kernel/client/kotlin/terramodulus/mui/gfx/GuiSprite.kt rename to src/kernel/client/kotlin/net/terramodulus/mui/gfx/GuiSprite.kt index 42935a81..d7d7a944 100644 --- a/src/kernel/client/kotlin/terramodulus/mui/gfx/GuiSprite.kt +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gfx/GuiSprite.kt @@ -3,9 +3,9 @@ * SPDX-License-Identifier: LGPL-3.0-only */ -package terramodulus.mui.gfx +package net.terramodulus.mui.gfx -import terramodulus.engine.SpriteMesh +import net.terramodulus.engine.SpriteMesh class GuiSprite(private val rect: RectangleI, private val texture: UInt) { private val mesh = SpriteMesh(rect.x, rect.y, rect.x + rect.width, rect.y + rect.height) diff --git a/src/kernel/client/kotlin/terramodulus/mui/gfx/ManagedRect.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gfx/ManagedRect.kt similarity index 94% rename from src/kernel/client/kotlin/terramodulus/mui/gfx/ManagedRect.kt rename to src/kernel/client/kotlin/net/terramodulus/mui/gfx/ManagedRect.kt index 5f251f8f..2362b7ae 100644 --- a/src/kernel/client/kotlin/terramodulus/mui/gfx/ManagedRect.kt +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gfx/ManagedRect.kt @@ -3,7 +3,7 @@ * SPDX-License-Identifier: LGPL-3.0-only */ -package terramodulus.mui.gfx +package net.terramodulus.mui.gfx import kotlin.properties.Delegates.observable diff --git a/src/kernel/client/kotlin/terramodulus/mui/gfx/ModelTransform.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gfx/ModelTransform.kt similarity index 91% rename from src/kernel/client/kotlin/terramodulus/mui/gfx/ModelTransform.kt rename to src/kernel/client/kotlin/net/terramodulus/mui/gfx/ModelTransform.kt index 343cc5c6..2ff00f22 100644 --- a/src/kernel/client/kotlin/terramodulus/mui/gfx/ModelTransform.kt +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gfx/ModelTransform.kt @@ -3,7 +3,7 @@ * SPDX-License-Identifier: LGPL-3.0-only */ -package terramodulus.mui.gfx +package net.terramodulus.mui.gfx typealias ModelTransform = terramodulus.engine.ModelTransform diff --git a/src/kernel/client/kotlin/terramodulus/mui/gfx/Rectangle.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gfx/Rectangle.kt similarity index 99% rename from src/kernel/client/kotlin/terramodulus/mui/gfx/Rectangle.kt rename to src/kernel/client/kotlin/net/terramodulus/mui/gfx/Rectangle.kt index 0f38514a..88858755 100644 --- a/src/kernel/client/kotlin/terramodulus/mui/gfx/Rectangle.kt +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gfx/Rectangle.kt @@ -3,7 +3,7 @@ * SPDX-License-Identifier: LGPL-3.0-only */ -package terramodulus.mui.gfx +package net.terramodulus.mui.gfx /** * Rectangle in a coordinate system with (0, 0) on the bottom left. diff --git a/src/kernel/client/kotlin/terramodulus/mui/gfx/RenderSystem.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gfx/RenderSystem.kt similarity index 89% rename from src/kernel/client/kotlin/terramodulus/mui/gfx/RenderSystem.kt rename to src/kernel/client/kotlin/net/terramodulus/mui/gfx/RenderSystem.kt index b7240322..9e7ea7b2 100644 --- a/src/kernel/client/kotlin/terramodulus/mui/gfx/RenderSystem.kt +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gfx/RenderSystem.kt @@ -3,11 +3,11 @@ * SPDX-License-Identifier: LGPL-3.0-only */ -package terramodulus.mui.gfx +package net.terramodulus.mui.gfx -import terramodulus.engine.Canvas -import terramodulus.engine.GeomDrawable -import terramodulus.engine.MeshDrawable +import net.terramodulus.engine.Canvas +import net.terramodulus.engine.GeomDrawable +import net.terramodulus.engine.MeshDrawable import java.io.File private fun getPathOfResource(path: String): String { diff --git a/src/kernel/client/kotlin/terramodulus/mui/gfx/Vector.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gfx/Vector.kt similarity index 97% rename from src/kernel/client/kotlin/terramodulus/mui/gfx/Vector.kt rename to src/kernel/client/kotlin/net/terramodulus/mui/gfx/Vector.kt index 7bb49977..233d2540 100644 --- a/src/kernel/client/kotlin/terramodulus/mui/gfx/Vector.kt +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gfx/Vector.kt @@ -3,7 +3,7 @@ * SPDX-License-Identifier: LGPL-3.0-only */ -package terramodulus.mui.gfx +package net.terramodulus.mui.gfx data class Vector2I(val x: Int, val y: Int) { companion object { diff --git a/src/kernel/client/kotlin/terramodulus/mui/gms/AbstractPanel.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gms/AbstractPanel.kt similarity index 84% rename from src/kernel/client/kotlin/terramodulus/mui/gms/AbstractPanel.kt rename to src/kernel/client/kotlin/net/terramodulus/mui/gms/AbstractPanel.kt index 3582d919..810e7259 100644 --- a/src/kernel/client/kotlin/terramodulus/mui/gms/AbstractPanel.kt +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gms/AbstractPanel.kt @@ -3,7 +3,7 @@ * SPDX-License-Identifier: LGPL-3.0-only */ -package terramodulus.mui.gms +package net.terramodulus.mui.gms abstract class AbstractPanel : Component(), Container { } diff --git a/src/kernel/client/kotlin/terramodulus/mui/gms/Component.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gms/Component.kt similarity index 85% rename from src/kernel/client/kotlin/terramodulus/mui/gms/Component.kt rename to src/kernel/client/kotlin/net/terramodulus/mui/gms/Component.kt index acda77de..8e9b91ca 100644 --- a/src/kernel/client/kotlin/terramodulus/mui/gms/Component.kt +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gms/Component.kt @@ -3,11 +3,11 @@ * SPDX-License-Identifier: LGPL-3.0-only */ -package terramodulus.mui.gms +package net.terramodulus.mui.gms -import terramodulus.mui.gfx.ManagedRect -import terramodulus.mui.gfx.RenderSystem -import terramodulus.mui.gms.event.ComponentEvent +import net.terramodulus.mui.gfx.ManagedRect +import net.terramodulus.mui.gfx.RenderSystem +import net.terramodulus.mui.gms.event.ComponentEvent /** * [Component] can only be contained by only one [Container]. diff --git a/src/kernel/client/kotlin/terramodulus/mui/gms/Container.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gms/Container.kt similarity index 69% rename from src/kernel/client/kotlin/terramodulus/mui/gms/Container.kt rename to src/kernel/client/kotlin/net/terramodulus/mui/gms/Container.kt index 59788138..de7f2fba 100644 --- a/src/kernel/client/kotlin/terramodulus/mui/gms/Container.kt +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gms/Container.kt @@ -3,9 +3,9 @@ * SPDX-License-Identifier: LGPL-3.0-only */ -package terramodulus.mui.gms +package net.terramodulus.mui.gms -import terramodulus.mui.gfx.ManagedRect +import net.terramodulus.mui.gfx.ManagedRect sealed interface Container { val rect: ManagedRect diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gms/Layout.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gms/Layout.kt new file mode 100644 index 00000000..53e52ff9 --- /dev/null +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gms/Layout.kt @@ -0,0 +1,274 @@ +/* + * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package net.terramodulus.mui.gms + +import net.terramodulus.mui.gfx.RectangleF +import net.terramodulus.mui.gms.impl.SequenceLayout.Element +import kotlin.reflect.KProperty + +/** + * [Layout] is always mutable. + * + * **Layout** is defined only when all its managed components all belong to the container + * associated with this layout manager *exclusively*. + */ +abstract class Layout(private val container: Container) { + companion object { + const val ALIGN_START = 0F; + const val ALIGN_CENTER = .5F; + const val ALIGN_END = 1F; + } + + abstract val components: Iterable + + private val containerObserver = ::layout.apply(container.rect::observe) + + /** + * Updates the layout output using the current layout configurations + * by invoking [layout] internally. + * + * It is recommended to invoke this when this layout is being initialized + * or any layout configuration has been changed. + */ + fun update() = layout(container.rect.rect) + + /** + * Lays out the managed [components] by this [Layout] manager. + * + * Only the `rect`s of the managed `components` should be (re)assigned; + * no other state-changing operations should be done beside this. + * @param rect the rectangle of the container at this moment + */ + protected abstract fun layout(rect: RectangleF) + + /** + * Must be invoked when this [Layout] is no longer in use. + */ + fun clear() { + container.rect.unobserve(containerObserver) + } + + /** + * Should not rely on indices in the [Layout] since they are not meaningful. + */ + abstract class Group(container: Container) : Layout(container) { + /** + * Checks if the specified [component] is contained in this layout. + * One should not rely on this function for efficiency and effectiveness. + */ + abstract fun contains(component: Component): Boolean + + /** + * Adds the [component] to this layout. + */ + abstract fun add(component: Component) + + /** + * Removes the [component] from this layout. + * Any error may occur if `target` does not exist in the layout. + */ + abstract fun remove(component: Component) + + /** + * Adds the [component] just before the [target] in this layout. + * Any error may occur if `target` does not exist in the layout. + */ + abstract fun addBefore(target: Component, component: Component) + + /** + * Adds the [component] just after the [target] in this layout. + * Any error may occur if `target` does not exist in the layout. + */ + abstract fun addAfter(target: Component, component: Component) + + /** + * Replaces the [target] in this layout by the [component]. + * Any error may occur if `target` does not exist in the layout. + */ + abstract fun replace(target: Component, component: Component) + } + + abstract class ElementGroup protected constructor( + container: Container, + protected val elements: ElementList, + ) : Group(container) { + final override val components = elements.componentsView + + override fun contains(component: Component): Boolean = elements.contains(component) + + /** + * Adds the [component] with the default element to this layout. + */ + abstract override fun add(component: Component) + + /** + * Adds the [component] with the [element] to this layout. + */ + open fun add(component: Component, element: E) = elements.add(component, element) + + override fun remove(component: Component) = elements.remove(component) + + /** + * Adds the [component] with the default element just before the [target] in this layout. + * Any error may occur if `target` does not exist in the layout. + */ + abstract override fun addBefore(target: Component, component: Component) + + /** + * Adds the [component] with the default element just after the [target] in this layout. + * Any error may occur if `target` does not exist in the layout. + */ + abstract override fun addAfter(target: Component, component: Component) + + /** + * Replaces the [target] in this layout by the [component] reusing `target`'s `element`. + * Any error may occur if `target` does not exist in the list. + */ + open fun replaceKeep(target: Component, component: Component) = + elements.replaceKeep(target, component) + + /** + * Replaces the [target] in this layout by the [component] with the default element. + * Any error may occur if `target` does not exist in the layout. + */ + abstract override fun replace(target: Component, component: Component) + + /** + * Replaces the [target] in this layout by the [component] with the [element]. + * Any error may occur if `target` does not exist in the layout. + */ + open fun replace(target: Component, component: Component, element: E) = + elements.replace(target, component, element) + } + + /** + * @param components must not be empty + */ + protected class ComponentIterable(private vararg val components: KProperty) : Iterable { + override fun iterator(): Iterator = object : Iterator { + private var index = 0 + + private fun untilNotNull(): Boolean { + do { + if (components[index].getter.call() != null) + return true + else + index++ + } while (index < components.size) + return false + } + + override fun hasNext(): Boolean = untilNotNull() + + override fun next(): Component = if (untilNotNull()) { + components[index].getter.call()!! + } else { + throw NoSuchElementException() + } + } + } + + /** + * Internal list of the layout elements. + */ + protected class ElementList private constructor( + private val components: MutableList, // order is defined here + private val elementMap: MutableMap, // elements are mapped here + ) : Iterable> { + companion object { + fun withComponentsDefault(default: () -> E, vararg components: Component): ElementList { + val map = hashMapOf() + components.forEach { map[it] = default() } + return ElementList(mutableListOf(*components), map) + } + + fun withComponentsDefault(default: () -> E, components: Collection): ElementList { + val map = hashMapOf() + components.forEach { map[it] = default() } + return ElementList(ArrayList(components), map) + } + + fun withElements(vararg elements: Pair): ElementList = + ElementList(elements.mapTo(ArrayList(elements.size)) { it.first }, hashMapOf(*elements)) + + fun withElements(elements: Map): ElementList = + ElementList(elements.mapTo(ArrayList(elements.size)) { it.key }, HashMap(elements)) + } + + val componentsView: Collection = components + + override fun iterator(): Iterator> = object : Iterator> { + private val it = components.iterator() + + override fun hasNext() = it.hasNext() + + override fun next(): Pair { + val e = it.next() + return e to elementMap[e]!! + } + } + + /** + * Checks if the specified [component] is contained in this list. + * One should not rely on this function for efficiency and effectiveness. + */ + fun contains(component: Component): Boolean = components.contains(component) + + /** + * Adds the [component] with the [element] to this list. + */ + fun add(component: Component, element: E) { + components.add(component) + elementMap[component] = element + } + + /** + * Removes the [component] from this list. + */ + fun remove(component: Component) { + components.remove(component) + elementMap.remove(component) + } + + /** + * Adds the [component] with the [element] just before the [target] in this list. + * Any error may occur if `target` does not exist in the list. + */ + fun addBefore(target: Component, component: Component, element: E) { + components.add(components.indexOf(target), component) + elementMap[component] = element + } + + /** + * Adds the [component] with the [element] just after the [target] in this list. + * Any error may occur if `target` does not exist in the list. + */ + fun addAfter(target: Component, component: Component, element: E) { + components.add(components.indexOf(target) + 1, component) + elementMap[component] = element + } + + /** + * Replaces the [target] in this list by the [component] reusing `target`'s `element`. + * Any error may occur if `target` does not exist in the list. + */ + fun replaceKeep(target: Component, component: Component) { + components.add(components.indexOf(target), component) + elementMap[component] = elementMap[target]!! + remove(target) + } + + /** + * Replaces the [target] in this list by the [component] with the [element]. + * Any error may occur if `target` does not exist in the list. + */ + fun replace(target: Component, component: Component, element: E) { + components.add(components.indexOf(target), component) + remove(target) + elementMap[target] = element + } + } +} diff --git a/src/kernel/client/kotlin/terramodulus/mui/gms/Menu.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gms/Menu.kt similarity index 95% rename from src/kernel/client/kotlin/terramodulus/mui/gms/Menu.kt rename to src/kernel/client/kotlin/net/terramodulus/mui/gms/Menu.kt index b3ab96a2..ada1df80 100644 --- a/src/kernel/client/kotlin/terramodulus/mui/gms/Menu.kt +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gms/Menu.kt @@ -3,9 +3,9 @@ * SPDX-License-Identifier: LGPL-3.0-only */ -package terramodulus.mui.gms +package net.terramodulus.mui.gms -import terramodulus.mui.gms.event.MenuEvent +import net.terramodulus.mui.gms.event.MenuEvent import java.util.ArrayDeque abstract class Menu : Container { diff --git a/src/kernel/client/kotlin/terramodulus/mui/gms/Screen.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gms/Screen.kt similarity index 93% rename from src/kernel/client/kotlin/terramodulus/mui/gms/Screen.kt rename to src/kernel/client/kotlin/net/terramodulus/mui/gms/Screen.kt index 528c4976..f8e6cee5 100644 --- a/src/kernel/client/kotlin/terramodulus/mui/gms/Screen.kt +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gms/Screen.kt @@ -3,10 +3,11 @@ * SPDX-License-Identifier: LGPL-3.0-only */ -package terramodulus.mui.gms +package net.terramodulus.mui.gms -import terramodulus.mui.gfx.RenderSystem -import terramodulus.mui.gms.event.ScreenEvent +import net.terramodulus.mui.gfx.ManagedRect +import net.terramodulus.mui.gfx.RenderSystem +import net.terramodulus.mui.gms.event.ScreenEvent import java.util.ArrayDeque abstract class Screen : Container { @@ -15,6 +16,8 @@ abstract class Screen : Container { private val components = ArrayList() private val componentQueue = ArrayDeque() private val menuQueue = ArrayDeque() + override val rect: ManagedRect + get() = TODO("Not yet implemented") val handle: Handle = HandleImpl() private sealed interface ComponentOperation { diff --git a/src/kernel/client/kotlin/terramodulus/mui/gms/ScreenManager.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gms/ScreenManager.kt similarity index 96% rename from src/kernel/client/kotlin/terramodulus/mui/gms/ScreenManager.kt rename to src/kernel/client/kotlin/net/terramodulus/mui/gms/ScreenManager.kt index eea9e18b..f513cb27 100644 --- a/src/kernel/client/kotlin/terramodulus/mui/gms/ScreenManager.kt +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gms/ScreenManager.kt @@ -3,10 +3,10 @@ * SPDX-License-Identifier: LGPL-3.0-only */ -package terramodulus.mui.gms +package net.terramodulus.mui.gms -import terramodulus.mui.gfx.RenderSystem -import terramodulus.mui.gms.impl.LaunchingScreen +import net.terramodulus.mui.gfx.RenderSystem +import net.terramodulus.mui.gms.impl.LaunchingScreen class ScreenManager internal constructor(private val renderSystemHandle: RenderSystem.Handle) { /** diff --git a/src/kernel/client/kotlin/terramodulus/mui/gms/event/ComponentEvent.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gms/event/ComponentEvent.kt similarity index 79% rename from src/kernel/client/kotlin/terramodulus/mui/gms/event/ComponentEvent.kt rename to src/kernel/client/kotlin/net/terramodulus/mui/gms/event/ComponentEvent.kt index 5a7ae4de..7383b352 100644 --- a/src/kernel/client/kotlin/terramodulus/mui/gms/event/ComponentEvent.kt +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gms/event/ComponentEvent.kt @@ -3,7 +3,7 @@ * SPDX-License-Identifier: LGPL-3.0-only */ -package terramodulus.mui.gms.event +package net.terramodulus.mui.gms.event sealed interface ComponentEvent { } diff --git a/src/kernel/client/kotlin/terramodulus/mui/gms/event/MenuEvent.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gms/event/MenuEvent.kt similarity index 79% rename from src/kernel/client/kotlin/terramodulus/mui/gms/event/MenuEvent.kt rename to src/kernel/client/kotlin/net/terramodulus/mui/gms/event/MenuEvent.kt index d60a4595..2cdbc0b5 100644 --- a/src/kernel/client/kotlin/terramodulus/mui/gms/event/MenuEvent.kt +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gms/event/MenuEvent.kt @@ -3,7 +3,7 @@ * SPDX-License-Identifier: LGPL-3.0-only */ -package terramodulus.mui.gms.event +package net.terramodulus.mui.gms.event sealed interface MenuEvent { } diff --git a/src/kernel/client/kotlin/terramodulus/mui/gms/event/ScreenEvent.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gms/event/ScreenEvent.kt similarity index 84% rename from src/kernel/client/kotlin/terramodulus/mui/gms/event/ScreenEvent.kt rename to src/kernel/client/kotlin/net/terramodulus/mui/gms/event/ScreenEvent.kt index b49731b9..ae203cae 100644 --- a/src/kernel/client/kotlin/terramodulus/mui/gms/event/ScreenEvent.kt +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gms/event/ScreenEvent.kt @@ -3,7 +3,7 @@ * SPDX-License-Identifier: LGPL-3.0-only */ -package terramodulus.mui.gms.event +package net.terramodulus.mui.gms.event sealed interface ScreenEvent { data object Open : ScreenEvent diff --git a/src/kernel/client/kotlin/terramodulus/mui/gms/impl/BlankComponent.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gms/impl/BlankComponent.kt similarity index 79% rename from src/kernel/client/kotlin/terramodulus/mui/gms/impl/BlankComponent.kt rename to src/kernel/client/kotlin/net/terramodulus/mui/gms/impl/BlankComponent.kt index a8d809c7..129f679b 100644 --- a/src/kernel/client/kotlin/terramodulus/mui/gms/impl/BlankComponent.kt +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gms/impl/BlankComponent.kt @@ -5,8 +5,8 @@ package terramodulus.mui.gms.impl -import terramodulus.mui.gfx.RenderSystem -import terramodulus.mui.gms.Component +import net.terramodulus.mui.gfx.RenderSystem +import net.terramodulus.mui.gms.Component /** * This can act as a placeholder [Component] in a [Layout][terramodulus.mui.gms.Layout]. diff --git a/src/kernel/client/kotlin/terramodulus/mui/gms/impl/FlexibleBoxLayout.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gms/impl/FlexibleBoxLayout.kt similarity index 63% rename from src/kernel/client/kotlin/terramodulus/mui/gms/impl/FlexibleBoxLayout.kt rename to src/kernel/client/kotlin/net/terramodulus/mui/gms/impl/FlexibleBoxLayout.kt index 8c62f470..421b08d2 100644 --- a/src/kernel/client/kotlin/terramodulus/mui/gms/impl/FlexibleBoxLayout.kt +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gms/impl/FlexibleBoxLayout.kt @@ -3,12 +3,12 @@ * SPDX-License-Identifier: LGPL-3.0-only */ -package terramodulus.mui.gms.impl +package net.terramodulus.mui.gms.impl -import terramodulus.mui.gfx.RectangleF -import terramodulus.mui.gms.Component -import terramodulus.mui.gms.Container -import terramodulus.mui.gms.Layout +import net.terramodulus.mui.gfx.RectangleF +import net.terramodulus.mui.gms.Component +import net.terramodulus.mui.gms.Container +import net.terramodulus.mui.gms.Layout class FlexibleBoxLayout(container: Container) : Layout(container) { override val components: Iterable diff --git a/src/kernel/client/kotlin/terramodulus/mui/gms/impl/GeomComponent.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gms/impl/GeomComponent.kt similarity index 60% rename from src/kernel/client/kotlin/terramodulus/mui/gms/impl/GeomComponent.kt rename to src/kernel/client/kotlin/net/terramodulus/mui/gms/impl/GeomComponent.kt index ab4e38d1..a1ed2188 100644 --- a/src/kernel/client/kotlin/terramodulus/mui/gms/impl/GeomComponent.kt +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gms/impl/GeomComponent.kt @@ -3,11 +3,11 @@ * SPDX-License-Identifier: LGPL-3.0-only */ -package terramodulus.mui.gms.impl +package net.terramodulus.mui.gms.impl -import terramodulus.mui.gfx.GuiGeometry -import terramodulus.mui.gfx.RenderSystem -import terramodulus.mui.gms.Component +import net.terramodulus.mui.gfx.GuiGeometry +import net.terramodulus.mui.gfx.RenderSystem +import net.terramodulus.mui.gms.Component class GeomComponent(val geom: GuiGeometry) : Component() { override fun render(renderSystem: RenderSystem) { diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gms/impl/GraphicsComponent.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gms/impl/GraphicsComponent.kt new file mode 100644 index 00000000..2d8aa37f --- /dev/null +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gms/impl/GraphicsComponent.kt @@ -0,0 +1,26 @@ +/* + * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package net.terramodulus.mui.gms.impl + +import net.terramodulus.mui.gfx.GuiSprite +import net.terramodulus.mui.gfx.RenderSystem +import net.terramodulus.mui.gms.AbstractPanel +import net.terramodulus.mui.gms.Component + +sealed interface GraphicsComponent + +class SpriteComponent(val sprite: GuiSprite) : Component(), GraphicsComponent { + override fun render(renderSystem: RenderSystem) { + sprite.render(renderSystem) + } +} + +@Suppress("CanSealedSubClassBeObject") +class CanvasComponent : AbstractPanel(), GraphicsComponent { + override fun render(renderSystem: RenderSystem) { + TODO("Not yet implemented") + } +} diff --git a/src/kernel/client/kotlin/terramodulus/mui/gms/impl/LaunchingScreen.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gms/impl/LaunchingScreen.kt similarity index 79% rename from src/kernel/client/kotlin/terramodulus/mui/gms/impl/LaunchingScreen.kt rename to src/kernel/client/kotlin/net/terramodulus/mui/gms/impl/LaunchingScreen.kt index 31b918cb..06f8bcbc 100644 --- a/src/kernel/client/kotlin/terramodulus/mui/gms/impl/LaunchingScreen.kt +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gms/impl/LaunchingScreen.kt @@ -3,17 +3,17 @@ * SPDX-License-Identifier: LGPL-3.0-only */ -package terramodulus.mui.gms.impl +package net.terramodulus.mui.gms.impl -import terramodulus.mui.gfx.AlphaFilter -import terramodulus.mui.gfx.Dimension2I -import terramodulus.mui.gfx.FullScaling -import terramodulus.mui.gfx.GuiRect -import terramodulus.mui.gfx.RectangleI -import terramodulus.mui.gfx.RenderSystem -import terramodulus.mui.gfx.SmartScaling -import terramodulus.mui.gms.Screen -import terramodulus.mui.gms.ScreenManager +import net.terramodulus.mui.gfx.AlphaFilter +import net.terramodulus.mui.gfx.Dimension2I +import net.terramodulus.mui.gfx.FullScaling +import net.terramodulus.mui.gfx.GuiRect +import net.terramodulus.mui.gfx.RectangleI +import net.terramodulus.mui.gfx.RenderSystem +import net.terramodulus.mui.gfx.SmartScaling +import net.terramodulus.mui.gms.Screen +import net.terramodulus.mui.gms.ScreenManager private val REF_SIZE = Dimension2I(800, 480) diff --git a/src/kernel/client/kotlin/terramodulus/mui/gms/impl/PositioningComponent.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gms/impl/PositioningComponent.kt similarity index 76% rename from src/kernel/client/kotlin/terramodulus/mui/gms/impl/PositioningComponent.kt rename to src/kernel/client/kotlin/net/terramodulus/mui/gms/impl/PositioningComponent.kt index 3cffc424..729b19be 100644 --- a/src/kernel/client/kotlin/terramodulus/mui/gms/impl/PositioningComponent.kt +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gms/impl/PositioningComponent.kt @@ -5,8 +5,8 @@ package terramodulus.mui.gms.impl -import terramodulus.mui.gfx.RenderSystem -import terramodulus.mui.gms.Component +import net.terramodulus.mui.gfx.RenderSystem +import net.terramodulus.mui.gms.Component class PositioningComponent : Component() { override fun render(renderSystem: RenderSystem) { diff --git a/src/kernel/client/kotlin/terramodulus/mui/gms/impl/ResourceLoadingScreen.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gms/impl/ResourceLoadingScreen.kt similarity index 83% rename from src/kernel/client/kotlin/terramodulus/mui/gms/impl/ResourceLoadingScreen.kt rename to src/kernel/client/kotlin/net/terramodulus/mui/gms/impl/ResourceLoadingScreen.kt index cbb87b89..f873a87f 100644 --- a/src/kernel/client/kotlin/terramodulus/mui/gms/impl/ResourceLoadingScreen.kt +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gms/impl/ResourceLoadingScreen.kt @@ -3,17 +3,17 @@ * SPDX-License-Identifier: LGPL-3.0-only */ -package terramodulus.mui.gms.impl +package net.terramodulus.mui.gms.impl -import terramodulus.mui.gfx.AlphaFilter -import terramodulus.mui.gfx.Dimension2I -import terramodulus.mui.gfx.FullScaling -import terramodulus.mui.gfx.GuiRect -import terramodulus.mui.gfx.RectangleI -import terramodulus.mui.gfx.RenderSystem -import terramodulus.mui.gfx.SmartScaling -import terramodulus.mui.gms.Screen -import terramodulus.mui.gms.ScreenManager +import net.terramodulus.mui.gfx.AlphaFilter +import net.terramodulus.mui.gfx.Dimension2I +import net.terramodulus.mui.gfx.FullScaling +import net.terramodulus.mui.gfx.GuiRect +import net.terramodulus.mui.gfx.RectangleI +import net.terramodulus.mui.gfx.RenderSystem +import net.terramodulus.mui.gfx.SmartScaling +import net.terramodulus.mui.gms.Screen +import net.terramodulus.mui.gms.ScreenManager private val REF_SIZE = Dimension2I(800, 480) diff --git a/src/kernel/client/kotlin/terramodulus/mui/gms/impl/SequenceLayout.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gms/impl/SequenceLayout.kt similarity index 72% rename from src/kernel/client/kotlin/terramodulus/mui/gms/impl/SequenceLayout.kt rename to src/kernel/client/kotlin/net/terramodulus/mui/gms/impl/SequenceLayout.kt index 7b0fb4fb..9d80c12c 100644 --- a/src/kernel/client/kotlin/terramodulus/mui/gms/impl/SequenceLayout.kt +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gms/impl/SequenceLayout.kt @@ -3,12 +3,12 @@ * SPDX-License-Identifier: LGPL-3.0-only */ -package terramodulus.mui.gms.impl +package net.terramodulus.mui.gms.impl -import terramodulus.mui.gfx.RectangleF -import terramodulus.mui.gms.Component -import terramodulus.mui.gms.Container -import terramodulus.mui.gms.Layout +import net.terramodulus.mui.gfx.RectangleF +import net.terramodulus.mui.gms.Component +import net.terramodulus.mui.gms.Container +import net.terramodulus.mui.gms.Layout /** * Common implementation that is either [ColumnLayout] or [RowLayout]. @@ -16,14 +16,24 @@ import terramodulus.mui.gms.Layout * This is an optimized special version of [FlexibleBoxLayout] without any expected * multiple *sequences* of components in a single layout. */ -sealed class SequenceLayout(container: Container, protected val elements: ElementList) : Layout(container) { - final override val components = elements.componentsView - +sealed class SequenceLayout(container: Container, elements: ElementList) : + Layout.ElementGroup(container, elements) { class Element { companion object { fun default() = Element() } } + + override fun add(component: Component) = elements.add(component, Element.default()) + + override fun addBefore(target: Component, component: Component) = + elements.addBefore(target, component, Element.default()) + + override fun addAfter(target: Component, component: Component) = + elements.addAfter(target, component, Element.default()) + + override fun replace(target: Component, component: Component) = + elements.replace(target, component, Element.default()) } /** diff --git a/src/kernel/client/kotlin/terramodulus/mui/gms/impl/TitleScreen.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gms/impl/TitleScreen.kt similarity index 64% rename from src/kernel/client/kotlin/terramodulus/mui/gms/impl/TitleScreen.kt rename to src/kernel/client/kotlin/net/terramodulus/mui/gms/impl/TitleScreen.kt index ad9a3dca..a13d8e02 100644 --- a/src/kernel/client/kotlin/terramodulus/mui/gms/impl/TitleScreen.kt +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gms/impl/TitleScreen.kt @@ -3,10 +3,10 @@ * SPDX-License-Identifier: LGPL-3.0-only */ -package terramodulus.mui.gms.impl +package net.terramodulus.mui.gms.impl -import terramodulus.mui.gfx.RenderSystem -import terramodulus.mui.gms.Screen +import net.terramodulus.mui.gfx.RenderSystem +import net.terramodulus.mui.gms.Screen class TitleScreen(renderSystemHandle: RenderSystem.Handle) : Screen() { override fun exit() {} diff --git a/src/kernel/client/kotlin/terramodulus/mui/input/InputSystem.kt b/src/kernel/client/kotlin/net/terramodulus/mui/input/InputSystem.kt similarity index 82% rename from src/kernel/client/kotlin/terramodulus/mui/input/InputSystem.kt rename to src/kernel/client/kotlin/net/terramodulus/mui/input/InputSystem.kt index fe1b1b39..ccac05de 100644 --- a/src/kernel/client/kotlin/terramodulus/mui/input/InputSystem.kt +++ b/src/kernel/client/kotlin/net/terramodulus/mui/input/InputSystem.kt @@ -3,7 +3,7 @@ * SPDX-License-Identifier: LGPL-3.0-only */ -package terramodulus.mui.input +package net.terramodulus.mui.input class InputSystem internal constructor() { } diff --git a/src/kernel/client/kotlin/terramodulus/settings/Preference.kt b/src/kernel/client/kotlin/net/terramodulus/settings/Preference.kt similarity index 100% rename from src/kernel/client/kotlin/terramodulus/settings/Preference.kt rename to src/kernel/client/kotlin/net/terramodulus/settings/Preference.kt diff --git a/src/kernel/client/kotlin/terramodulus/settings/Settings.kt b/src/kernel/client/kotlin/net/terramodulus/settings/Settings.kt similarity index 100% rename from src/kernel/client/kotlin/terramodulus/settings/Settings.kt rename to src/kernel/client/kotlin/net/terramodulus/settings/Settings.kt diff --git a/src/kernel/client/kotlin/terramodulus/mui/gms/Layout.kt b/src/kernel/client/kotlin/terramodulus/mui/gms/Layout.kt deleted file mode 100644 index 6fd00483..00000000 --- a/src/kernel/client/kotlin/terramodulus/mui/gms/Layout.kt +++ /dev/null @@ -1,120 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors - * SPDX-License-Identifier: LGPL-3.0-only - */ - -package terramodulus.mui.gms - -import terramodulus.mui.gfx.RectangleF -import kotlin.reflect.KProperty - -/** - * [Layout] is always mutable. - * - * **Layout** is defined only when all its managed components all belong to the container - * associated with this layout manager *exclusively*. - */ -abstract class Layout(private val container: Container) { - companion object { - const val ALIGN_START = 0F; - const val ALIGN_CENTER = .5F; - const val ALIGN_END = 1F; - } - - abstract val components: Iterable - - private val containerObserver = ::layout.apply(container.rect::observe) - - /** - * Updates the layout output using the current layout configurations - * by invoking [layout] internally. - * - * It is recommended to invoke this when this layout is being initialized - * or any layout configuration has been changed. - */ - fun update() = layout(container.rect.rect) - - /** - * Lays out the managed [components] by this [Layout] manager. - * - * Only the `rect`s of the managed `components` should be (re)assigned; - * no other state-changing operations should be done beside this. - * @param rect the rectangle of the container at this moment - */ - protected abstract fun layout(rect: RectangleF) - - /** - * Must be invoked when this [Layout] is no longer in use. - */ - fun clear() { - container.rect.unobserve(containerObserver) - } - - /** - * @param components must not be empty - */ - protected class ComponentIterable(private vararg val components: KProperty) : Iterable { - override fun iterator(): Iterator = object : Iterator { - private var index = 0 - - private fun untilNotNull(): Boolean { - do { - if (components[index].getter.call() != null) - return true - else - index++ - } while (index < components.size) - return false - } - - override fun hasNext(): Boolean = untilNotNull() - - override fun next(): Component = if (untilNotNull()) { - components[index].getter.call()!! - } else { - throw NoSuchElementException() - } - } - } - - /** - * Internal list of the layout elements. - */ - protected class ElementList private constructor( - private val components: MutableList, // order is defined here - private val elementMap: MutableMap, // elements are mapped here - ) : Iterable> { - companion object { - fun withComponentsDefault(default: () -> E, vararg components: Component): ElementList { - val map = hashMapOf() - components.forEach { map[it] = default() } - return ElementList(mutableListOf(*components), map) - } - - fun withComponentsDefault(default: () -> E, components: Collection): ElementList { - val map = hashMapOf() - components.forEach { map[it] = default() } - return ElementList(ArrayList(components), map) - } - - fun withElements(vararg elements: Pair): ElementList = - ElementList(elements.mapTo(ArrayList(elements.size)) { it.first }, hashMapOf(*elements)) - - fun withElements(elements: Map): ElementList = - ElementList(elements.mapTo(ArrayList(elements.size)) { it.key }, HashMap(elements)) - } - - val componentsView: Collection = components - - override fun iterator(): Iterator> = object : Iterator> { - private val it = components.iterator() - - override fun hasNext() = it.hasNext() - - override fun next(): Pair { - val e = it.next() - return e to elementMap[e]!! - } - } - } -} diff --git a/src/kernel/client/kotlin/terramodulus/mui/gms/impl/SpriteComponent.kt b/src/kernel/client/kotlin/terramodulus/mui/gms/impl/SpriteComponent.kt deleted file mode 100644 index 70c96c2e..00000000 --- a/src/kernel/client/kotlin/terramodulus/mui/gms/impl/SpriteComponent.kt +++ /dev/null @@ -1,16 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors - * SPDX-License-Identifier: LGPL-3.0-only - */ - -package terramodulus.mui.gms.impl - -import terramodulus.mui.gfx.GuiSprite -import terramodulus.mui.gfx.RenderSystem -import terramodulus.mui.gms.Component - -class SpriteComponent(val sprite: GuiSprite) : Component() { - override fun render(renderSystem: RenderSystem) { - sprite.render(renderSystem) - } -} diff --git a/src/kernel/client/resources/studio_logo.png b/src/kernel/client/resources/studio_logo.png index bee167f3c75c836e448c1ef3db784336aea18531..2b0eb8ae15759aae25bb1fb58c0f39181e557938 100644 GIT binary patch literal 58411 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 WnXevKCH6LT5XBiy+InlE?(kpte5ED; literal 107419 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 D(iDPC diff --git a/src/kernel/common/kotlin/terramodulus/common/core/AbstractTerraModulus.kt b/src/kernel/common/kotlin/net/terramodulus/common/core/AbstractTerraModulus.kt similarity index 93% rename from src/kernel/common/kotlin/terramodulus/common/core/AbstractTerraModulus.kt rename to src/kernel/common/kotlin/net/terramodulus/common/core/AbstractTerraModulus.kt index 256b08a9..b0373bdf 100644 --- a/src/kernel/common/kotlin/terramodulus/common/core/AbstractTerraModulus.kt +++ b/src/kernel/common/kotlin/net/terramodulus/common/core/AbstractTerraModulus.kt @@ -3,7 +3,7 @@ * SPDX-License-Identifier: LGPL-3.0-only */ -package terramodulus.common.core +package net.terramodulus.common.core import java.io.Closeable diff --git a/src/kernel/common/kotlin/terramodulus/common/core/Core.kt b/src/kernel/common/kotlin/net/terramodulus/common/core/Core.kt similarity index 84% rename from src/kernel/common/kotlin/terramodulus/common/core/Core.kt rename to src/kernel/common/kotlin/net/terramodulus/common/core/Core.kt index afad88aa..2200d5e9 100644 --- a/src/kernel/common/kotlin/terramodulus/common/core/Core.kt +++ b/src/kernel/common/kotlin/net/terramodulus/common/core/Core.kt @@ -3,16 +3,16 @@ * SPDX-License-Identifier: LGPL-3.0-only */ -package terramodulus.common.core +package net.terramodulus.common.core import joptsimple.OptionException -import terramodulus.engine.initEngine -import terramodulus.util.exception.Error -import terramodulus.util.exception.Fault -import terramodulus.util.exception.UnhandledExceptionFault -import terramodulus.util.exception.triggerGlobalCrash -import terramodulus.util.logging.initLogging -import terramodulus.util.logging.logger +import net.terramodulus.engine.initEngine +import net.terramodulus.util.exception.Error +import net.terramodulus.util.exception.Fault +import net.terramodulus.util.exception.UnhandledExceptionFault +import net.terramodulus.util.exception.triggerGlobalCrash +import net.terramodulus.util.logging.initLogging +import net.terramodulus.util.logging.logger import java.io.Closeable import java.io.File import java.io.RandomAccessFile diff --git a/src/kernel/common/kotlin/terramodulus/core/Core.kt b/src/kernel/common/kotlin/net/terramodulus/core/Core.kt similarity index 86% rename from src/kernel/common/kotlin/terramodulus/core/Core.kt rename to src/kernel/common/kotlin/net/terramodulus/core/Core.kt index 46dcb456..51676a0e 100644 --- a/src/kernel/common/kotlin/terramodulus/core/Core.kt +++ b/src/kernel/common/kotlin/net/terramodulus/core/Core.kt @@ -3,7 +3,7 @@ * SPDX-License-Identifier: LGPL-3.0-only */ -package terramodulus.core +package net.terramodulus.core const val NAME = "TerraModulus" const val VERSION = "0.1.0" // TODO placeholder diff --git a/src/kernel/common/kotlin/net/terramodulus/core/package-info.java b/src/kernel/common/kotlin/net/terramodulus/core/package-info.java new file mode 100644 index 00000000..b2abf9f8 --- /dev/null +++ b/src/kernel/common/kotlin/net/terramodulus/core/package-info.java @@ -0,0 +1,9 @@ +/* + * SPDX-FileCopyrightText: 2025-2026 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +/** + * Critical components + */ +package net.terramodulus.core; diff --git a/src/kernel/common/kotlin/net/terramodulus/util/TypedAnchorMap.kt b/src/kernel/common/kotlin/net/terramodulus/util/TypedAnchorMap.kt new file mode 100644 index 00000000..071e0ba1 --- /dev/null +++ b/src/kernel/common/kotlin/net/terramodulus/util/TypedAnchorMap.kt @@ -0,0 +1,63 @@ +/* + * SPDX-FileCopyrightText: 2026 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package net.terramodulus.util + +import net.terramodulus.util.TypedAnchorMap.Key +import java.util.function.BiFunction +import java.util.function.Function + +/** + * A map with access by references to unique keys with type of values specified, + * matching the pattern of **Typed Anchor Dynamic Mapping**. + * Localized constrains may be applied by copying this universal structure.\ + * All bulk functions are all unsupported for type safety. + * @param T base class for map values + */ +open class TypedAnchorMap private constructor(private val map: MutableMap, Any?>) : MutableMap, Any?> by map { + constructor() : this(HashMap()) + + companion object { + fun default() = TypedAnchorMap() + } + + /** + * A unique, immutable key that defines and enforces the type for values of a [TypedAnchorMap]. + * By hiding public access to the map instance, constrains may be applied by subclassing this. + */ + open class Key(val type: Class) { + companion object { + inline operator fun invoke() = Key(T::class.java) + } + } + + @Suppress("UNCHECKED_CAST") // impossible, as constrained by #put + fun get(key: Key) = map[key] as T? + + @Suppress("UNCHECKED_CAST") // impossible, as constrained by #put + fun getOrDefault(key: Key, defaultValue: V) = super.getOrDefault(key, defaultValue) as T? + + fun put(key: Key, value: V) = map.put(key, value) + + override fun putAll(from: Map, Any?>) = throw UnsupportedOperationException() + + fun putIfAbsent(key: Key, value: V) = super.putIfAbsent(key, value) + + fun replace(key: Key, oldValue: V, newValue: V) = super.replace(key, oldValue, newValue) + + fun replace(key: Key, value: V): Any? { + return super.replace(key, value) + } + + override fun replaceAll(function: BiFunction, in Any?, out Any?>) = throw UnsupportedOperationException() + + override fun compute(key: Key<*>, remappingFunction: BiFunction, in Any?, out Any?>) = throw UnsupportedOperationException() + + override fun computeIfAbsent(key: Key<*>, mappingFunction: Function, out Any?>) = throw UnsupportedOperationException() + + override fun computeIfPresent(key: Key<*>, remappingFunction: BiFunction, in Any, out Any?>) = throw UnsupportedOperationException() + + override fun merge(key: Key<*>, value: Any, remappingFunction: BiFunction) = throw UnsupportedOperationException() +} diff --git a/src/kernel/common/kotlin/terramodulus/util/exception/CodeLogicFault.kt b/src/kernel/common/kotlin/net/terramodulus/util/exception/CodeLogicFault.kt similarity index 94% rename from src/kernel/common/kotlin/terramodulus/util/exception/CodeLogicFault.kt rename to src/kernel/common/kotlin/net/terramodulus/util/exception/CodeLogicFault.kt index 3a1f7d25..bae718d6 100644 --- a/src/kernel/common/kotlin/terramodulus/util/exception/CodeLogicFault.kt +++ b/src/kernel/common/kotlin/net/terramodulus/util/exception/CodeLogicFault.kt @@ -3,7 +3,7 @@ * SPDX-License-Identifier: LGPL-3.0-only */ -package terramodulus.util.exception +package net.terramodulus.util.exception /** * Code logic problems or bugs are regarded as defects in the software code and thus diff --git a/src/kernel/common/kotlin/net/terramodulus/util/exception/Core.kt b/src/kernel/common/kotlin/net/terramodulus/util/exception/Core.kt new file mode 100644 index 00000000..d822c0b4 --- /dev/null +++ b/src/kernel/common/kotlin/net/terramodulus/util/exception/Core.kt @@ -0,0 +1,94 @@ +/* + * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package net.terramodulus.util.exception + +import net.terramodulus.util.logging.logger +import java.util.function.Predicate +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract +import kotlin.reflect.KClass + +private val logger = logger {} + +@OptIn(ExperimentalContracts::class) +inline fun codeAssert(block: () -> R): R { + contract { + callsInPlace(block, InvocationKind.EXACTLY_ONCE) + } + try { + return block() + } catch (t: Throwable) { + if (t is X) throw CodeLogicFault(t) else throw t + } +} + +@OptIn(ExperimentalContracts::class) +inline fun codeAssert(vararg clazz: KClass, block: () -> R): R { + contract { + callsInPlace(block, InvocationKind.EXACTLY_ONCE) + } + try { + return block() + } catch (t: Throwable) { + if (clazz.any { it.isInstance(t) }) throw CodeLogicFault(t) else throw t + } +} + +@OptIn(ExperimentalContracts::class) +inline fun codeAssert(predicate: Predicate, block: () -> R): R { + contract { + callsInPlace(block, InvocationKind.EXACTLY_ONCE) + } + try { + return block() + } catch (t: Throwable) { + if (predicate.test(t)) throw CodeLogicFault(t) else throw t + } +} + +/** + * Records the recovered [exception] to logger. + * This logs the `exception` as a warning. + */ +fun recordException(exception: Exception) { + +} + +/** + * Pushes the recovered [exception] to application notification and records it to logger. + * This logs the `exception` as a warning. + */ +fun notifyAndRecordException(exception: Exception) { + // TODO exception notification + recordException(exception) +} + +/** + * Records the suppressed [error] to logger. + * This logs the `exception` as an error. + */ +fun recordError(error: Error) { + +} + +/** + * Pushes the suppressed [error] to application notification and records it to logger. + * This logs the `error` as an error. + */ +fun notifyAndRecordError(error: Error) { + // TODO exception notification + recordError(error) +} + +fun triggerSessionCrash() { + TODO() +} + +fun triggerGlobalCrash(error: Error): Nothing { + logger.error(error) {} + TODO() +} diff --git a/src/kernel/common/kotlin/terramodulus/util/exception/Error.kt b/src/kernel/common/kotlin/net/terramodulus/util/exception/Error.kt similarity index 87% rename from src/kernel/common/kotlin/terramodulus/util/exception/Error.kt rename to src/kernel/common/kotlin/net/terramodulus/util/exception/Error.kt index 8539effb..80f6b1ed 100644 --- a/src/kernel/common/kotlin/terramodulus/util/exception/Error.kt +++ b/src/kernel/common/kotlin/net/terramodulus/util/exception/Error.kt @@ -3,7 +3,7 @@ * SPDX-License-Identifier: LGPL-3.0-only */ -package terramodulus.util.exception +package net.terramodulus.util.exception abstract class Error(override val message: String, override val cause: Throwable?) : Exception(message, cause) { constructor(message: String) : this(message, null) diff --git a/src/kernel/common/kotlin/terramodulus/util/exception/Exception.kt b/src/kernel/common/kotlin/net/terramodulus/util/exception/Exception.kt similarity index 87% rename from src/kernel/common/kotlin/terramodulus/util/exception/Exception.kt rename to src/kernel/common/kotlin/net/terramodulus/util/exception/Exception.kt index 947c9fff..22f54d38 100644 --- a/src/kernel/common/kotlin/terramodulus/util/exception/Exception.kt +++ b/src/kernel/common/kotlin/net/terramodulus/util/exception/Exception.kt @@ -3,7 +3,7 @@ * SPDX-License-Identifier: LGPL-3.0-only */ -package terramodulus.util.exception +package net.terramodulus.util.exception abstract class Exception(override val message: String, override val cause: Throwable?) : Throwable(message, cause) { constructor(message: String) : this(message, null) diff --git a/src/kernel/common/kotlin/terramodulus/util/exception/Failure.kt b/src/kernel/common/kotlin/net/terramodulus/util/exception/Failure.kt similarity index 87% rename from src/kernel/common/kotlin/terramodulus/util/exception/Failure.kt rename to src/kernel/common/kotlin/net/terramodulus/util/exception/Failure.kt index c918f712..9bb2cec9 100644 --- a/src/kernel/common/kotlin/terramodulus/util/exception/Failure.kt +++ b/src/kernel/common/kotlin/net/terramodulus/util/exception/Failure.kt @@ -3,7 +3,7 @@ * SPDX-License-Identifier: LGPL-3.0-only */ -package terramodulus.util.exception +package net.terramodulus.util.exception abstract class Failure(override val message: String, override val cause: Throwable?) : Exception(message, cause) { constructor(message: String) : this(message, null) diff --git a/src/kernel/common/kotlin/terramodulus/util/exception/Fault.kt b/src/kernel/common/kotlin/net/terramodulus/util/exception/Fault.kt similarity index 87% rename from src/kernel/common/kotlin/terramodulus/util/exception/Fault.kt rename to src/kernel/common/kotlin/net/terramodulus/util/exception/Fault.kt index a4f578cf..9a83f965 100644 --- a/src/kernel/common/kotlin/terramodulus/util/exception/Fault.kt +++ b/src/kernel/common/kotlin/net/terramodulus/util/exception/Fault.kt @@ -3,7 +3,7 @@ * SPDX-License-Identifier: LGPL-3.0-only */ -package terramodulus.util.exception +package net.terramodulus.util.exception abstract class Fault(override val message: String, override val cause: Throwable?) : Error(message, cause) { constructor(message: String) : this(message, null) diff --git a/src/kernel/common/kotlin/terramodulus/util/exception/FerriciaEngineFault.kt b/src/kernel/common/kotlin/net/terramodulus/util/exception/FerriciaEngineFault.kt similarity index 81% rename from src/kernel/common/kotlin/terramodulus/util/exception/FerriciaEngineFault.kt rename to src/kernel/common/kotlin/net/terramodulus/util/exception/FerriciaEngineFault.kt index a649dcf0..31073665 100644 --- a/src/kernel/common/kotlin/terramodulus/util/exception/FerriciaEngineFault.kt +++ b/src/kernel/common/kotlin/net/terramodulus/util/exception/FerriciaEngineFault.kt @@ -3,6 +3,6 @@ * SPDX-License-Identifier: LGPL-3.0-only */ -package terramodulus.util.exception +package net.terramodulus.util.exception class FerriciaEngineFault(message: String) : Fault(message) diff --git a/src/kernel/common/kotlin/terramodulus/util/exception/UnhandledExceptionFault.kt b/src/kernel/common/kotlin/net/terramodulus/util/exception/UnhandledExceptionFault.kt similarity index 92% rename from src/kernel/common/kotlin/terramodulus/util/exception/UnhandledExceptionFault.kt rename to src/kernel/common/kotlin/net/terramodulus/util/exception/UnhandledExceptionFault.kt index 90fcfa76..c594b538 100644 --- a/src/kernel/common/kotlin/terramodulus/util/exception/UnhandledExceptionFault.kt +++ b/src/kernel/common/kotlin/net/terramodulus/util/exception/UnhandledExceptionFault.kt @@ -3,7 +3,7 @@ * SPDX-License-Identifier: LGPL-3.0-only */ -package terramodulus.util.exception +package net.terramodulus.util.exception class UnhandledExceptionFault private constructor(message: String, cause: Throwable) : Fault(message, cause) { companion object { diff --git a/src/kernel/common/kotlin/terramodulus/util/exception/package-info.java b/src/kernel/common/kotlin/net/terramodulus/util/exception/package-info.java similarity index 66% rename from src/kernel/common/kotlin/terramodulus/util/exception/package-info.java rename to src/kernel/common/kotlin/net/terramodulus/util/exception/package-info.java index 2299de90..bfb3fac8 100644 --- a/src/kernel/common/kotlin/terramodulus/util/exception/package-info.java +++ b/src/kernel/common/kotlin/net/terramodulus/util/exception/package-info.java @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-FileCopyrightText: 2025-2026 TerraModulus Team and Contributors * SPDX-License-Identifier: LGPL-3.0-only */ @@ -7,4 +7,4 @@ * This package refers to EFP 7 * for implementation and standardization information. */ -package terramodulus.util.exception; +package net.terramodulus.util.exception; diff --git a/src/kernel/common/kotlin/terramodulus/util/logging/Core.kt b/src/kernel/common/kotlin/net/terramodulus/util/logging/Core.kt similarity index 96% rename from src/kernel/common/kotlin/terramodulus/util/logging/Core.kt rename to src/kernel/common/kotlin/net/terramodulus/util/logging/Core.kt index 0adf7b81..7b98eb0e 100644 --- a/src/kernel/common/kotlin/terramodulus/util/logging/Core.kt +++ b/src/kernel/common/kotlin/net/terramodulus/util/logging/Core.kt @@ -3,7 +3,7 @@ * SPDX-License-Identifier: LGPL-3.0-only */ -package terramodulus.util.logging +package net.terramodulus.util.logging import io.github.oshai.kotlinlogging.KLogger import io.github.oshai.kotlinlogging.Level as KLevel diff --git a/src/kernel/common/kotlin/terramodulus/util/logging/DelayedFileAppender.kt b/src/kernel/common/kotlin/net/terramodulus/util/logging/DelayedFileAppender.kt similarity index 99% rename from src/kernel/common/kotlin/terramodulus/util/logging/DelayedFileAppender.kt rename to src/kernel/common/kotlin/net/terramodulus/util/logging/DelayedFileAppender.kt index 76a47b32..a5e8cb27 100644 --- a/src/kernel/common/kotlin/terramodulus/util/logging/DelayedFileAppender.kt +++ b/src/kernel/common/kotlin/net/terramodulus/util/logging/DelayedFileAppender.kt @@ -3,7 +3,7 @@ * SPDX-License-Identifier: LGPL-3.0-only */ -package terramodulus.util.logging +package net.terramodulus.util.logging import org.apache.logging.log4j.core.Appender import org.apache.logging.log4j.core.Layout diff --git a/src/kernel/common/kotlin/terramodulus/util/logging/State.kt b/src/kernel/common/kotlin/net/terramodulus/util/logging/State.kt similarity index 86% rename from src/kernel/common/kotlin/terramodulus/util/logging/State.kt rename to src/kernel/common/kotlin/net/terramodulus/util/logging/State.kt index cd0daed9..671e3786 100644 --- a/src/kernel/common/kotlin/terramodulus/util/logging/State.kt +++ b/src/kernel/common/kotlin/net/terramodulus/util/logging/State.kt @@ -3,7 +3,7 @@ * SPDX-License-Identifier: LGPL-3.0-only */ -package terramodulus.util.logging +package net.terramodulus.util.logging internal object State { fun appender() { diff --git a/src/kernel/common/kotlin/terramodulus/world/WorldManager.kt b/src/kernel/common/kotlin/net/terramodulus/world/WorldManager.kt similarity index 100% rename from src/kernel/common/kotlin/terramodulus/world/WorldManager.kt rename to src/kernel/common/kotlin/net/terramodulus/world/WorldManager.kt diff --git a/src/kernel/common/kotlin/terramodulus/world/item/Items.kt b/src/kernel/common/kotlin/net/terramodulus/world/item/Items.kt similarity index 100% rename from src/kernel/common/kotlin/terramodulus/world/item/Items.kt rename to src/kernel/common/kotlin/net/terramodulus/world/item/Items.kt diff --git a/src/kernel/common/kotlin/terramodulus/world/tile/Tiles.kt b/src/kernel/common/kotlin/net/terramodulus/world/tile/Tiles.kt similarity index 100% rename from src/kernel/common/kotlin/terramodulus/world/tile/Tiles.kt rename to src/kernel/common/kotlin/net/terramodulus/world/tile/Tiles.kt diff --git a/src/kernel/common/kotlin/terramodulus/core/package-info.java b/src/kernel/common/kotlin/terramodulus/core/package-info.java deleted file mode 100644 index 923995bd..00000000 --- a/src/kernel/common/kotlin/terramodulus/core/package-info.java +++ /dev/null @@ -1,9 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors - * SPDX-License-Identifier: LGPL-3.0-only - */ - -/** - * Critical components - */ -package terramodulus.core; diff --git a/src/kernel/common/kotlin/terramodulus/util/exception/Core.kt b/src/kernel/common/kotlin/terramodulus/util/exception/Core.kt deleted file mode 100644 index 99b1c628..00000000 --- a/src/kernel/common/kotlin/terramodulus/util/exception/Core.kt +++ /dev/null @@ -1,53 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors - * SPDX-License-Identifier: LGPL-3.0-only - */ - -package terramodulus.util.exception - -import terramodulus.util.logging.logger - -private val logger = logger {} - -/** - * Records the recovered [exception] to logger. - * This logs the `exception` as a warning. - */ -fun recordException(exception: Exception) { - -} - -/** - * Pushes the recovered [exception] to application notification and records it to logger. - * This logs the `exception` as a warning. - */ -fun notifyAndRecordException(exception: Exception) { - // TODO exception notification - recordException(exception) -} - -/** - * Records the suppressed [error] to logger. - * This logs the `exception` as an error. - */ -fun recordError(error: Error) { - -} - -/** - * Pushes the suppressed [error] to application notification and records it to logger. - * This logs the `error` as an error. - */ -fun notifyAndRecordError(error: Error) { - // TODO exception notification - recordError(error) -} - -fun triggerSessionCrash() { - TODO() -} - -fun triggerGlobalCrash(error: Error): Nothing { - logger.error(error) {} - TODO() -} diff --git a/src/kernel/server/kotlin/terramodulus/core/Main.kt b/src/kernel/server/kotlin/net/terramodulus/core/Main.kt similarity index 73% rename from src/kernel/server/kotlin/terramodulus/core/Main.kt rename to src/kernel/server/kotlin/net/terramodulus/core/Main.kt index a14f1062..41f004dc 100644 --- a/src/kernel/server/kotlin/terramodulus/core/Main.kt +++ b/src/kernel/server/kotlin/net/terramodulus/core/Main.kt @@ -3,10 +3,10 @@ * SPDX-License-Identifier: LGPL-3.0-only */ -package terramodulus.core +package net.terramodulus.core -import terramodulus.common.core.run -import terramodulus.common.core.setupInit +import net.terramodulus.common.core.run +import net.terramodulus.common.core.setupInit fun main(args: Array) { setupInit() diff --git a/src/kernel/server/kotlin/terramodulus/core/TerraModulus.kt b/src/kernel/server/kotlin/net/terramodulus/core/TerraModulus.kt similarity index 81% rename from src/kernel/server/kotlin/terramodulus/core/TerraModulus.kt rename to src/kernel/server/kotlin/net/terramodulus/core/TerraModulus.kt index 7cb78315..55ea70e5 100644 --- a/src/kernel/server/kotlin/terramodulus/core/TerraModulus.kt +++ b/src/kernel/server/kotlin/net/terramodulus/core/TerraModulus.kt @@ -3,9 +3,9 @@ * SPDX-License-Identifier: LGPL-3.0-only */ -package terramodulus.core +package net.terramodulus.core -import terramodulus.common.core.AbstractTerraModulus +import net.terramodulus.common.core.AbstractTerraModulus class TerraModulus internal constructor() : AbstractTerraModulus() { override var tps: Int