Skip to content

Commit 06ea6c8

Browse files
committed
Config system and basic modules
1 parent 36d975f commit 06ea6c8

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+839
-35
lines changed

build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ val minecraftVersion = project.properties["minecraft_version"].toString()
88
val yarnMappings = project.properties["yarn_mappings"].toString()
99

1010
plugins {
11-
kotlin("jvm") version ("1.9.22")
11+
kotlin("jvm") version "1.9.22"
1212
id("org.jetbrains.dokka") version "1.9.20"
1313
id("architectury-plugin") version "3.4-SNAPSHOT"
1414
id("dev.architectury.loom") version "1.5-SNAPSHOT" apply false

common/src/main/java/com/lambda/mixin/MinecraftClientMixin.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.lambda.mixin;
22

33
import com.lambda.event.EventFlow;
4+
import com.lambda.event.events.GameEvent;
45
import com.lambda.event.events.TickEvent;
56
import net.minecraft.client.MinecraftClient;
67
import org.spongepowered.asm.mixin.Mixin;
@@ -19,4 +20,17 @@ void onTickPre(CallbackInfo ci) {
1920
void onTickPost(CallbackInfo ci) {
2021
EventFlow.post(new TickEvent.Post());
2122
}
23+
24+
@Inject(at = @At(value = "INVOKE", target = "Lorg/slf4j/Logger;info(Ljava/lang/String;)V", shift = At.Shift.AFTER, remap = false), method = "stop")
25+
private void onShutdown(CallbackInfo ci) {
26+
EventFlow.post(GameEvent.Shutdown.INSTANCE);
27+
}
28+
29+
/**
30+
* Inject after the thread field is set so `ThreadExecutor#getThread` is available
31+
*/
32+
@Inject(at = @At(value = "FIELD", target = "Lnet/minecraft/client/MinecraftClient;thread:Ljava/lang/Thread;", shift = At.Shift.AFTER, ordinal = 0), method = "run")
33+
private void onStartup(CallbackInfo ci) {
34+
EventFlow.post(GameEvent.Startup.INSTANCE);
35+
}
2236
}

common/src/main/kotlin/com/lambda/Lambda.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
package com.lambda
22

3+
import com.google.gson.Gson
4+
import com.google.gson.GsonBuilder
35
import com.lambda.event.events.KeyPressEvent
46
import com.lambda.event.listener.SafeListener.Companion.listener
7+
import com.lambda.module.modules.BoringModule
8+
import com.lambda.module.modules.BoringModule2
59
import com.lambda.task.tasks.HelloWorldTask
610
import com.lambda.threading.taskContext
711
import net.minecraft.client.MinecraftClient
@@ -17,8 +21,12 @@ object Lambda {
1721

1822
val LOG: Logger = LogManager.getLogger(SYMBOL)
1923
val mc: MinecraftClient = MinecraftClient.getInstance()
24+
val gson: Gson = GsonBuilder().setPrettyPrinting().create()
2025

2126
init {
27+
BoringModule
28+
BoringModule2
29+
2230
listener<KeyPressEvent> {
2331
if (it.key != GLFW.GLFW_KEY_Z) {
2432
return@listener
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package com.lambda.config
2+
3+
import com.google.gson.JsonElement
4+
import com.lambda.Lambda.gson
5+
import com.lambda.context.SafeContext
6+
import com.lambda.threading.runSafe
7+
import com.lambda.util.Nameable
8+
import kotlin.properties.Delegates
9+
import kotlin.reflect.KProperty
10+
11+
abstract class AbstractSetting<T : Any>(
12+
private val defaultValue: T,
13+
val visibility: () -> Boolean,
14+
val description: String
15+
) : Jsonable, Nameable {
16+
private val listeners = mutableListOf<(from: T, to: T) -> Unit>()
17+
18+
var value: T by Delegates.observable(defaultValue) { _, from, to ->
19+
if (from == to) return@observable
20+
listeners.forEach { it(from, to) }
21+
}
22+
23+
val isVisible get() = visibility()
24+
val isModified get() = value != defaultValue
25+
26+
operator fun getValue(thisRef: Any?, property: KProperty<*>) = value
27+
open operator fun setValue(thisRef: Any?, property: KProperty<*>, valueIn: T) {
28+
value = valueIn
29+
}
30+
31+
override fun toJson(): JsonElement =
32+
gson.toJsonTree(value)
33+
34+
override fun loadFromJson(serialized: JsonElement) {
35+
value = gson.fromJson(serialized, value::class.java)
36+
}
37+
38+
fun listener(block: SafeContext.(from: T, to: T) -> Unit) {
39+
listeners.add { from, to ->
40+
runSafe {
41+
block(from, to)
42+
}
43+
}
44+
}
45+
46+
fun unsafeListener(block: (from: T, to: T) -> Unit) {
47+
listeners.add(block)
48+
}
49+
50+
fun reset() {
51+
value = defaultValue
52+
}
53+
}
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
package com.lambda.config
2+
3+
import com.google.gson.JsonElement
4+
import com.google.gson.JsonObject
5+
import com.lambda.Lambda.LOG
6+
import com.lambda.config.settings.StringSetting
7+
import com.lambda.config.settings.collections.ListSetting
8+
import com.lambda.config.settings.collections.MapSetting
9+
import com.lambda.config.settings.collections.SetSetting
10+
import com.lambda.config.settings.comparable.BooleanSetting
11+
import com.lambda.config.settings.numeric.*
12+
import com.lambda.util.Nameable
13+
14+
/**
15+
* Holds a set of [AbstractSetting]s that are associated with the [name] of the [Configurable].
16+
*/
17+
abstract class Configurable(configuration: Configuration) : Jsonable, Nameable {
18+
private val settings = mutableSetOf<AbstractSetting<*>>()
19+
20+
init {
21+
configuration.configurables.add(this) // ToDo: Find non-leaking solution
22+
}
23+
24+
override fun toJson() =
25+
JsonObject().apply {
26+
settings.forEach { setting ->
27+
add(setting.name, setting.toJson())
28+
}
29+
}
30+
31+
override fun loadFromJson(serialized: JsonElement) {
32+
serialized.asJsonObject.entrySet().forEach { (name, value) ->
33+
settings.find { it.name == name }?.loadFromJson(value) ?: LOG.info("No saved setting found for $name")
34+
}
35+
}
36+
37+
fun setting(
38+
name: String,
39+
defaultValue: Boolean,
40+
visibility: () -> Boolean = { true },
41+
description: String = ""
42+
) = BooleanSetting(name, defaultValue, visibility, description).also {
43+
settings.add(it)
44+
}
45+
46+
fun setting(
47+
name: String,
48+
defaultValue: String,
49+
visibility: () -> Boolean = { true },
50+
description: String = ""
51+
) = StringSetting(name, defaultValue, visibility, description).also {
52+
settings.add(it)
53+
}
54+
55+
private inline fun <reified T : Any> setting(
56+
name: String,
57+
defaultValue: List<T>,
58+
noinline visibility: () -> Boolean = { true },
59+
description: String = ""
60+
) = ListSetting(name, defaultValue, visibility, description).also {
61+
settings.add(it)
62+
}
63+
64+
private inline fun <reified K : Any, V : Any> setting(
65+
name: String,
66+
defaultValue: Map<K, V>,
67+
noinline visibility: () -> Boolean = { true },
68+
description: String = ""
69+
) = MapSetting(name, defaultValue, visibility, description).also {
70+
settings.add(it)
71+
}
72+
73+
private inline fun <reified T : Any> setting(
74+
name: String,
75+
defaultValue: Set<T>,
76+
noinline visibility: () -> Boolean = { true },
77+
description: String = ""
78+
) = SetSetting(name, defaultValue, visibility, description).also {
79+
settings.add(it)
80+
}
81+
82+
fun setting(
83+
name: String,
84+
defaultValue: Byte,
85+
range: ClosedRange<Byte>,
86+
step: Byte = 1,
87+
visibility: () -> Boolean = { true },
88+
description: String = ""
89+
) = ByteSetting(name, defaultValue, range, step, visibility, description).also {
90+
settings.add(it)
91+
}
92+
93+
fun setting(
94+
name: String,
95+
defaultValue: Double,
96+
range: ClosedRange<Double>,
97+
step: Double = 1.0,
98+
visibility: () -> Boolean = { true },
99+
description: String = ""
100+
) = DoubleSetting(name, defaultValue, range, step, visibility, description).also {
101+
settings.add(it)
102+
}
103+
104+
fun setting(
105+
name: String,
106+
defaultValue: Float,
107+
range: ClosedRange<Float>,
108+
step: Float = 1f,
109+
visibility: () -> Boolean = { true },
110+
description: String = ""
111+
) = FloatSetting(name, defaultValue, range, step, visibility, description).also {
112+
settings.add(it)
113+
}
114+
115+
fun setting(
116+
name: String,
117+
defaultValue: Int,
118+
range: ClosedRange<Int>,
119+
step: Int = 1,
120+
visibility: () -> Boolean = { true },
121+
description: String = ""
122+
) = IntegerSetting(name, defaultValue, range, step, visibility, description).also {
123+
settings.add(it)
124+
}
125+
126+
fun setting(
127+
name: String,
128+
defaultValue: Long,
129+
range: ClosedRange<Long>,
130+
step: Long = 1,
131+
visibility: () -> Boolean = { true },
132+
description: String = ""
133+
) = LongSetting(name, defaultValue, range, step, visibility, description).also {
134+
settings.add(it)
135+
}
136+
137+
fun setting(
138+
name: String,
139+
defaultValue: Short,
140+
range: ClosedRange<Short>,
141+
step: Short = 1,
142+
visibility: () -> Boolean = { true },
143+
description: String = ""
144+
) = ShortSetting(name, defaultValue, range, step, visibility, description).also {
145+
settings.add(it)
146+
}
147+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package com.lambda.config
2+
3+
import com.google.gson.JsonElement
4+
import com.google.gson.JsonObject
5+
import com.google.gson.JsonParser
6+
import com.lambda.Lambda.LOG
7+
import com.lambda.Lambda.gson
8+
import com.lambda.event.EventFlow.lambdaScope
9+
import com.lambda.event.events.GameEvent
10+
import com.lambda.event.listener.UnsafeListener.Companion.unsafeListener
11+
import kotlinx.coroutines.Dispatchers
12+
import kotlinx.coroutines.launch
13+
import java.io.File
14+
15+
abstract class Configuration : Jsonable {
16+
abstract val configName: String
17+
abstract val primary: File
18+
19+
val configurables = mutableSetOf<Configurable>()
20+
private val backup: File
21+
get() = File("${primary.parent}/${primary.nameWithoutExtension}-backup.${primary.extension}")
22+
23+
init {
24+
unsafeListener<GameEvent.Startup> { tryLoad() }
25+
26+
unsafeListener<GameEvent.Shutdown> { trySave() }
27+
}
28+
29+
override fun toJson() =
30+
JsonObject().apply {
31+
configurables.forEach {
32+
add(it.name, it.toJson())
33+
}
34+
}
35+
36+
override fun loadFromJson(serialized: JsonElement) {
37+
serialized.asJsonObject.entrySet().forEach { (name, value) ->
38+
configurables.find {
39+
it.name == name
40+
}?.loadFromJson(value) ?: println("No saved setting found for $name")
41+
}
42+
}
43+
44+
private fun save() {
45+
with(primary) {
46+
if (exists()) {
47+
copyTo(backup, true)
48+
}
49+
parentFile.mkdirs()
50+
writeText(gson.toJson(toJson()))
51+
}
52+
}
53+
54+
private fun load(file: File) {
55+
check(file.exists()) {
56+
"No configuration file found for $configName"
57+
}
58+
59+
loadFromJson(JsonParser.parseReader(file.reader()).asJsonObject)
60+
}
61+
62+
private fun tryLoad() {
63+
lambdaScope.launch(Dispatchers.IO) {
64+
runCatching { load(primary) }
65+
.onSuccess { LOG.info("$configName config loaded") }
66+
.onFailure { LOG.error("Failed to load $configName config, loading backup", it) }
67+
.recoverCatching {
68+
runCatching { load(backup) }
69+
.onSuccess { LOG.info("$configName config loaded from backup") }
70+
.onFailure { LOG.error("Failed to load $configName config from backup, unrecoverable error", it) }
71+
.isSuccess
72+
}
73+
.isSuccess
74+
}
75+
}
76+
77+
private fun trySave() {
78+
lambdaScope.launch(Dispatchers.IO) {
79+
runCatching { save() }
80+
.onSuccess { LOG.info("$configName config saved") }
81+
.onFailure { LOG.error("Failed to save $configName config", it) }
82+
.isSuccess
83+
}
84+
}
85+
86+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.lambda.config
2+
3+
import com.google.gson.JsonElement
4+
5+
6+
interface Jsonable {
7+
fun toJson(): JsonElement
8+
fun loadFromJson(serialized: JsonElement)
9+
}

0 commit comments

Comments
 (0)