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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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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