rudimentary config gui (again)

This commit is contained in:
nea
2022-10-22 00:34:22 +02:00
parent c98d4693f1
commit f85c449ed5
13 changed files with 277 additions and 53 deletions

View File

@@ -1,4 +1,3 @@
- translations
- recipes - recipes
- fairy souls - fairy souls
- easy config gui builder - easy config gui builder

View File

@@ -26,7 +26,7 @@ import moe.nea.notenoughupdates.dbus.NEUDbusObject
import moe.nea.notenoughupdates.features.FeatureManager import moe.nea.notenoughupdates.features.FeatureManager
import moe.nea.notenoughupdates.repo.RepoManager import moe.nea.notenoughupdates.repo.RepoManager
import moe.nea.notenoughupdates.util.SBData import moe.nea.notenoughupdates.util.SBData
import moe.nea.notenoughupdates.util.config.IConfigHolder import moe.nea.notenoughupdates.util.data.IDataHolder
object NotEnoughUpdates : ModInitializer, ClientModInitializer { object NotEnoughUpdates : ModInitializer, ClientModInitializer {
const val MOD_ID = "notenoughupdates" const val MOD_ID = "notenoughupdates"
@@ -72,7 +72,7 @@ object NotEnoughUpdates : ModInitializer, ClientModInitializer {
override fun onInitialize() { override fun onInitialize() {
dbusConnection.requestBusName("moe.nea.notenoughupdates") dbusConnection.requestBusName("moe.nea.notenoughupdates")
dbusConnection.exportObject(NEUDbusObject) dbusConnection.exportObject(NEUDbusObject)
IConfigHolder.registerEvents() IDataHolder.registerEvents()
RepoManager.initialize() RepoManager.initialize()
SBData.init() SBData.init()
FeatureManager.autoload() FeatureManager.autoload()

View File

@@ -4,6 +4,7 @@ import com.mojang.brigadier.CommandDispatcher
import io.github.cottonmc.cotton.gui.client.CottonClientScreen import io.github.cottonmc.cotton.gui.client.CottonClientScreen
import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource
import net.minecraft.text.Text import net.minecraft.text.Text
import moe.nea.notenoughupdates.features.world.FairySouls
import moe.nea.notenoughupdates.gui.repoGui import moe.nea.notenoughupdates.gui.repoGui
import moe.nea.notenoughupdates.repo.RepoManager import moe.nea.notenoughupdates.repo.RepoManager
import moe.nea.notenoughupdates.util.SBData import moe.nea.notenoughupdates.util.SBData
@@ -29,6 +30,11 @@ fun neuCommand() = literal("neu") {
} }
} }
thenLiteral("dev") { thenLiteral("dev") {
thenLiteral("config") {
thenExecute {
FairySouls.TConfig.showConfigEditor()
}
}
thenLiteral("sbdata") { thenLiteral("sbdata") {
thenExecute { thenExecute {
source.sendFeedback(Text.translatable("notenoughupdates.sbinfo.profile", SBData.profileCuteName)) source.sendFeedback(Text.translatable("notenoughupdates.sbinfo.profile", SBData.profileCuteName))

View File

@@ -4,9 +4,9 @@ import kotlinx.serialization.Serializable
import kotlinx.serialization.serializer import kotlinx.serialization.serializer
import moe.nea.notenoughupdates.NotEnoughUpdates import moe.nea.notenoughupdates.NotEnoughUpdates
import moe.nea.notenoughupdates.features.world.FairySouls import moe.nea.notenoughupdates.features.world.FairySouls
import moe.nea.notenoughupdates.util.config.ConfigHolder import moe.nea.notenoughupdates.util.data.DataHolder
object FeatureManager : ConfigHolder<FeatureManager.Config>(serializer(), "features", ::Config) { object FeatureManager : DataHolder<FeatureManager.Config>(serializer(), "features", ::Config) {
@Serializable @Serializable
data class Config( data class Config(
val enabledFeatures: MutableMap<String, Boolean> = mutableMapOf() val enabledFeatures: MutableMap<String, Boolean> = mutableMapOf()
@@ -40,11 +40,11 @@ object FeatureManager : ConfigHolder<FeatureManager.Config>(serializer(), "featu
} }
fun isEnabled(identifier: String): Boolean? = fun isEnabled(identifier: String): Boolean? =
config.enabledFeatures[identifier] data.enabledFeatures[identifier]
fun setEnabled(identifier: String, value: Boolean) { fun setEnabled(identifier: String, value: Boolean) {
config.enabledFeatures[identifier] = value data.enabledFeatures[identifier] = value
markDirty() markDirty()
} }

View File

@@ -3,6 +3,7 @@ package moe.nea.notenoughupdates.features.world
import io.github.moulberry.repo.data.Coordinate import io.github.moulberry.repo.data.Coordinate
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.serializer import kotlinx.serialization.serializer
import net.minecraft.client.MinecraftClient
import net.minecraft.util.math.BlockPos import net.minecraft.util.math.BlockPos
import moe.nea.notenoughupdates.events.ServerChatLineReceivedEvent import moe.nea.notenoughupdates.events.ServerChatLineReceivedEvent
import moe.nea.notenoughupdates.events.SkyblockServerUpdateEvent import moe.nea.notenoughupdates.events.SkyblockServerUpdateEvent
@@ -11,7 +12,8 @@ import moe.nea.notenoughupdates.features.NEUFeature
import moe.nea.notenoughupdates.repo.RepoManager import moe.nea.notenoughupdates.repo.RepoManager
import moe.nea.notenoughupdates.util.MC import moe.nea.notenoughupdates.util.MC
import moe.nea.notenoughupdates.util.SBData import moe.nea.notenoughupdates.util.SBData
import moe.nea.notenoughupdates.util.config.ProfileSpecificConfigHolder import moe.nea.notenoughupdates.util.config.ManagedConfig
import moe.nea.notenoughupdates.util.data.ProfileSpecificDataHolder
import moe.nea.notenoughupdates.util.render.RenderBlockContext.Companion.renderBlocks import moe.nea.notenoughupdates.util.render.RenderBlockContext.Companion.renderBlocks
import moe.nea.notenoughupdates.util.unformattedString import moe.nea.notenoughupdates.util.unformattedString
@@ -19,12 +21,22 @@ val Coordinate.blockPos: BlockPos
get() = BlockPos(x, y, z) get() = BlockPos(x, y, z)
object FairySouls : NEUFeature, object FairySouls : NEUFeature,
ProfileSpecificConfigHolder<FairySouls.Config>(serializer(), "fairy-souls.json", ::Config) { ProfileSpecificDataHolder<FairySouls.Config>(serializer(), "found-fairysouls", ::Config) {
@Serializable @Serializable
data class Config( data class Config(
val foundSouls: MutableMap<String, MutableSet<Int>> = mutableMapOf() val foundSouls: MutableMap<String, MutableSet<Int>> = mutableMapOf()
) )
object TConfig : ManagedConfig("fairysouls") {
val displaySouls by toggle("show") { false }
val resetSouls by button("reset") {
FairySouls.data?.foundSouls?.clear() != null
updateMissingSouls()
}
}
override val name: String get() = "Fairy Souls" override val name: String get() = "Fairy Souls"
override val identifier: String get() = "fairy-souls" override val identifier: String get() = "fairy-souls"
@@ -37,7 +49,7 @@ object FairySouls : NEUFeature,
fun updateMissingSouls() { fun updateMissingSouls() {
currentMissingSouls = emptyList() currentMissingSouls = emptyList()
val c = config ?: return val c = data ?: return
val fi = c.foundSouls[currentLocationName] ?: setOf() val fi = c.foundSouls[currentLocationName] ?: setOf()
val cms = currentLocationSouls.toMutableList() val cms = currentLocationSouls.toMutableList()
fi.asSequence().sortedDescending().filter { it in cms.indices }.forEach { cms.removeAt(it) } fi.asSequence().sortedDescending().filter { it in cms.indices }.forEach { cms.removeAt(it) }
@@ -65,7 +77,7 @@ object FairySouls : NEUFeature,
private fun markNearestSoul() { private fun markNearestSoul() {
val nearestSoul = findNearestClickableSoul() ?: return val nearestSoul = findNearestClickableSoul() ?: return
val c = config ?: return val c = data ?: return
val loc = currentLocationName ?: return val loc = currentLocationName ?: return
val idx = currentLocationSouls.indexOf(nearestSoul) val idx = currentLocationSouls.indexOf(nearestSoul)
c.foundSouls.computeIfAbsent(loc) { mutableSetOf() }.add(idx) c.foundSouls.computeIfAbsent(loc) { mutableSetOf() }.add(idx)
@@ -92,6 +104,7 @@ object FairySouls : NEUFeature,
} }
} }
WorldRenderLastEvent.subscribe { WorldRenderLastEvent.subscribe {
if (!TConfig.displaySouls) return@subscribe
renderBlocks(it.camera) { renderBlocks(it.camera) {
color(1F, 1F, 0F, 0.8F) color(1F, 1F, 0F, 0.8F)
currentMissingSouls.forEach { currentMissingSouls.forEach {

View File

@@ -9,11 +9,11 @@ import io.github.cottonmc.cotton.gui.widget.data.HorizontalAlignment
import io.github.cottonmc.cotton.gui.widget.data.Insets import io.github.cottonmc.cotton.gui.widget.data.Insets
import io.github.cottonmc.cotton.gui.widget.data.VerticalAlignment import io.github.cottonmc.cotton.gui.widget.data.VerticalAlignment
import moe.nea.notenoughupdates.NotEnoughUpdates import moe.nea.notenoughupdates.NotEnoughUpdates
import moe.nea.notenoughupdates.util.config.ConfigHolder import moe.nea.notenoughupdates.util.data.DataHolder
import net.minecraft.text.Text import net.minecraft.text.Text
import kotlin.reflect.KMutableProperty1 import kotlin.reflect.KMutableProperty1
class ConfigGui<K>(val holder: ConfigHolder<K>, val build: ConfigGui<K>.() -> Unit) : LightweightGuiDescription() { class ConfigGui<K>(val holder: DataHolder<K>, val build: ConfigGui<K>.() -> Unit) : LightweightGuiDescription() {
private val root = WGridPanelWithPadding(verticalPadding = 4) private val root = WGridPanelWithPadding(verticalPadding = 4)
private val reloadables = mutableListOf<(() -> Unit)>() private val reloadables = mutableListOf<(() -> Unit)>()
@@ -43,9 +43,9 @@ class ConfigGui<K>(val holder: ConfigHolder<K>, val build: ConfigGui<K>.() -> Un
fun toggle(text: Text, prop: KMutableProperty1<K, Boolean>) { fun toggle(text: Text, prop: KMutableProperty1<K, Boolean>) {
val toggle = WToggleButton(text) val toggle = WToggleButton(text)
reloadables.add { toggle.toggle = prop.get(holder.config) } reloadables.add { toggle.toggle = prop.get(holder.data) }
toggle.setOnToggle { toggle.setOnToggle {
prop.set(holder.config, true) prop.set(holder.data, true)
holder.markDirty() holder.markDirty()
} }
root.add(toggle, 5, col, 6, 1) root.add(toggle, 5, col, 6, 1)
@@ -72,11 +72,11 @@ class ConfigGui<K>(val holder: ConfigHolder<K>, val build: ConfigGui<K>.() -> Un
val textfield = WTextField(background) val textfield = WTextField(background)
textfield.isEditable = true textfield.isEditable = true
reloadables.add { reloadables.add {
textfield.text = prop.get(holder.config) textfield.text = prop.get(holder.data)
} }
textfield.maxLength = maxLength textfield.maxLength = maxLength
textfield.setChangedListener { textfield.setChangedListener {
prop.set(holder.config, it) prop.set(holder.data, it)
holder.markDirty() holder.markDirty()
} }
root.add(textfield, 5, col, 6, 11) root.add(textfield, 5, col, 6, 11)

View File

@@ -27,9 +27,9 @@ fun repoGui(): ConfigGui<RepoManager.Config> {
Text.translatable("notenoughupdates.gui.repo.reset.label"), Text.translatable("notenoughupdates.gui.repo.reset.label"),
Text.translatable("notenoughupdates.gui.repo.reset"), Text.translatable("notenoughupdates.gui.repo.reset"),
) { ) {
RepoManager.config.user = "NotEnoughUpdates" RepoManager.data.user = "NotEnoughUpdates"
RepoManager.config.repo = "NotEnoughUpdates-REPO" RepoManager.data.repo = "NotEnoughUpdates-REPO"
RepoManager.config.branch = "dangerous" RepoManager.data.branch = "dangerous"
reload() reload()
} }
} }

View File

@@ -50,7 +50,7 @@ object RepoDownloadManager {
private suspend fun requestLatestGithubSha(): String? { private suspend fun requestLatestGithubSha(): String? {
val response = val response =
NotEnoughUpdates.httpClient.get("https://api.github.com/repos/${RepoManager.config.user}/${RepoManager.config.repo}/commits/${RepoManager.config.branch}") NotEnoughUpdates.httpClient.get("https://api.github.com/repos/${RepoManager.data.user}/${RepoManager.data.repo}/commits/${RepoManager.data.branch}")
if (response.status.value != 200) { if (response.status.value != 200) {
return null return null
} }
@@ -77,7 +77,7 @@ object RepoDownloadManager {
} }
val currentSha = loadSavedVersionHash() val currentSha = loadSavedVersionHash()
if (latestSha != currentSha || force) { if (latestSha != currentSha || force) {
val requestUrl = "https://github.com/${RepoManager.config.user}/${RepoManager.config.repo}/archive/$latestSha.zip" val requestUrl = "https://github.com/${RepoManager.data.user}/${RepoManager.data.repo}/archive/$latestSha.zip"
logger.info("Planning to upgrade repository from $currentSha to $latestSha from $requestUrl") logger.info("Planning to upgrade repository from $currentSha to $latestSha from $requestUrl")
val zipFile = downloadGithubArchive(requestUrl) val zipFile = downloadGithubArchive(requestUrl)
logger.info("Download repository zip file to $zipFile. Deleting old repository") logger.info("Download repository zip file to $zipFile. Deleting old repository")

View File

@@ -9,13 +9,13 @@ import kotlinx.serialization.serializer
import moe.nea.notenoughupdates.NotEnoughUpdates import moe.nea.notenoughupdates.NotEnoughUpdates
import moe.nea.notenoughupdates.NotEnoughUpdates.logger import moe.nea.notenoughupdates.NotEnoughUpdates.logger
import moe.nea.notenoughupdates.hud.ProgressBar import moe.nea.notenoughupdates.hud.ProgressBar
import moe.nea.notenoughupdates.util.config.ConfigHolder import moe.nea.notenoughupdates.util.data.DataHolder
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents
import net.minecraft.client.MinecraftClient import net.minecraft.client.MinecraftClient
import net.minecraft.network.packet.s2c.play.SynchronizeRecipesS2CPacket import net.minecraft.network.packet.s2c.play.SynchronizeRecipesS2CPacket
import net.minecraft.text.Text import net.minecraft.text.Text
object RepoManager : ConfigHolder<RepoManager.Config>(serializer(), "repo", ::Config) { object RepoManager : DataHolder<RepoManager.Config>(serializer(), "repo", ::Config) {
@Serializable @Serializable
data class Config( data class Config(
var user: String = "NotEnoughUpdates", var user: String = "NotEnoughUpdates",
@@ -80,7 +80,7 @@ object RepoManager : ConfigHolder<RepoManager.Config>(serializer(), "repo", ::Co
} }
fun initialize() { fun initialize() {
if (config.autoUpdate) { if (data.autoUpdate) {
launchAsyncUpdate() launchAsyncUpdate()
} else { } else {
reload() reload()

View File

@@ -0,0 +1,206 @@
package moe.nea.notenoughupdates.util.config
import io.github.cottonmc.cotton.gui.client.CottonClientScreen
import io.github.cottonmc.cotton.gui.client.LibGui
import io.github.cottonmc.cotton.gui.client.LightweightGuiDescription
import io.github.cottonmc.cotton.gui.widget.WButton
import io.github.cottonmc.cotton.gui.widget.WLabel
import io.github.cottonmc.cotton.gui.widget.WToggleButton
import io.github.cottonmc.cotton.gui.widget.WWidget
import io.github.cottonmc.cotton.gui.widget.data.Insets
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.boolean
import kotlinx.serialization.json.jsonPrimitive
import kotlin.io.path.createDirectories
import kotlin.io.path.readText
import kotlin.io.path.writeText
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty
import net.minecraft.client.MinecraftClient
import net.minecraft.text.Text
import moe.nea.notenoughupdates.NotEnoughUpdates
import moe.nea.notenoughupdates.gui.WGridPanelWithPadding
import moe.nea.notenoughupdates.util.ScreenUtil.setScreenLater
abstract class ManagedConfig(val name: String) {
class GuiAppender(val width: Int) {
private var row = 0
internal val panel = WGridPanelWithPadding(verticalPadding = 4, horizontalPadding = 4)
internal val reloadables = mutableListOf<(() -> Unit)>()
fun set(x: Int, y: Int, w: Int, h: Int, widget: WWidget) {
panel.add(widget, x, y, w, h)
}
fun onReload(reloadable: () -> Unit) {
reloadables.add(reloadable)
}
fun skipRows(r: Int) {
row += r
}
fun appendSplitRow(left: WWidget, right: WWidget) {
val lw = width / 2
set(0, row, lw, 1, left)
set(lw, row, width - lw, 1, right)
skipRows(1)
}
fun appendFullRow(widget: WWidget) {
set(0, row, width, 1, widget)
skipRows(1)
}
}
interface OptionHandler<T : Any> {
fun toJson(element: T): JsonElement?
fun fromJson(element: JsonElement): T
fun emitGuiElements(opt: Option<T>, guiAppender: GuiAppender)
}
inner class Option<T : Any> internal constructor(
val propertyName: String,
val default: () -> T,
val handler: OptionHandler<T>
) : ReadOnlyProperty<Any?, T> {
private lateinit var _value: T
private var loaded = false
var value: T
get() {
if (!loaded)
load()
return _value
}
set(value) {
loaded = true
_value = value
}
override fun getValue(thisRef: Any?, property: KProperty<*>): T {
return value
}
private fun load() {
if (data.containsKey(propertyName)) {
try {
value = handler.fromJson(data[propertyName]!!)
} catch (e: Exception) {
NotEnoughUpdates.logger.error(
"Exception during loading of config file $name. This will reset this config.",
e
)
}
}
value = default()
}
internal fun toJson(): JsonElement? {
return handler.toJson(value)
}
fun appendToGui(guiapp: GuiAppender) {
handler.emitGuiElements(this, guiapp)
}
}
val file = NotEnoughUpdates.CONFIG_DIR.resolve("$name.json")
val data: JsonObject by lazy {
try {
NotEnoughUpdates.json.decodeFromString(
file.readText()
)
} catch (e: Exception) {
NotEnoughUpdates.logger.info("Could not read config $name. Loading empty config.")
JsonObject(mutableMapOf())
}
}
fun save() {
val data = JsonObject(allOptions.mapNotNull { (key, value) ->
value.toJson()?.let {
key to it
}
}.toMap())
file.parent.createDirectories()
file.writeText(NotEnoughUpdates.json.encodeToString(data))
}
val allOptions = mutableMapOf<String, Option<*>>()
val sortedOptions = mutableListOf<Option<*>>()
protected fun <T : Any> option(propertyName: String, default: () -> T, handler: OptionHandler<T>): Option<T> {
if (propertyName in allOptions) error("Cannot register the same name twice")
return Option(propertyName, default, handler).also {
allOptions[propertyName] = it
sortedOptions.add(it)
}
}
class BooleanHandler(val config: ManagedConfig) : OptionHandler<Boolean> {
override fun toJson(element: Boolean): JsonElement? {
return JsonPrimitive(element)
}
override fun fromJson(element: JsonElement): Boolean {
return element.jsonPrimitive.boolean
}
override fun emitGuiElements(opt: Option<Boolean>, guiAppender: GuiAppender) {
guiAppender.appendFullRow(
WToggleButton(Text.translatable("neu.config.${config.name}.${opt.propertyName}")).apply {
guiAppender.onReload { toggle = opt.value }
setOnToggle {
opt.value = it
config.save()
}
}
)
}
}
class ClickHandler(val config: ManagedConfig, val runnable: () -> Unit) : OptionHandler<Unit> {
override fun toJson(element: Unit): JsonElement? {
return null
}
override fun fromJson(element: JsonElement) {}
override fun emitGuiElements(opt: Option<Unit>, guiAppender: GuiAppender) {
guiAppender.appendSplitRow(
WLabel(Text.translatable("neu.config.${config.name}.${opt.propertyName}")),
WButton(Text.translatable("neu.config.${config.name}.${opt.propertyName}")).apply {
setOnClick {
runnable()
}
},
)
}
}
protected fun toggle(propertyName: String, default: () -> Boolean): Option<Boolean> {
return option(propertyName, default, BooleanHandler(this))
}
fun showConfigEditor() {
val lwgd = LightweightGuiDescription()
val guiapp = GuiAppender(20)
guiapp.panel.insets = Insets.ROOT_PANEL
sortedOptions.forEach { it.appendToGui(guiapp) }
guiapp.reloadables.forEach { it() }
lwgd.setRootPanel(guiapp.panel)
setScreenLater(CottonClientScreen(lwgd))
}
protected fun button(propertyName: String, runnable: () -> Unit): Option<Unit> {
return option(propertyName, { }, ClickHandler(this, runnable))
}
}

View File

@@ -1,4 +1,4 @@
package moe.nea.notenoughupdates.util.config package moe.nea.notenoughupdates.util.data
import java.nio.file.Path import java.nio.file.Path
import kotlinx.serialization.KSerializer import kotlinx.serialization.KSerializer
@@ -7,19 +7,19 @@ import kotlin.io.path.readText
import kotlin.io.path.writeText import kotlin.io.path.writeText
import moe.nea.notenoughupdates.NotEnoughUpdates import moe.nea.notenoughupdates.NotEnoughUpdates
abstract class ConfigHolder<T>( abstract class DataHolder<T>(
val serializer: KSerializer<T>, val serializer: KSerializer<T>,
val name: String, val name: String,
val default: () -> T val default: () -> T
) : IConfigHolder<T> { ) : IDataHolder<T> {
final override var config: T final override var data: T
private set private set
init { init {
config = readValueOrDefault() data = readValueOrDefault()
IConfigHolder.putConfig(this::class, this) IDataHolder.putDataHolder(this::class, this)
} }
private val file: Path get() = NotEnoughUpdates.CONFIG_DIR.resolve("$name.json") private val file: Path get() = NotEnoughUpdates.CONFIG_DIR.resolve("$name.json")
@@ -32,7 +32,7 @@ abstract class ConfigHolder<T>(
file.readText() file.readText()
) )
} catch (e: Exception) {/* Expecting IOException and SerializationException, but Kotlin doesn't allow multi catches*/ } catch (e: Exception) {/* Expecting IOException and SerializationException, but Kotlin doesn't allow multi catches*/
IConfigHolder.badLoads.add(name) IDataHolder.badLoads.add(name)
NotEnoughUpdates.logger.error( NotEnoughUpdates.logger.error(
"Exception during loading of config file $name. This will reset this config.", "Exception during loading of config file $name. This will reset this config.",
e e
@@ -46,15 +46,15 @@ abstract class ConfigHolder<T>(
} }
override fun save() { override fun save() {
writeValue(config) writeValue(data)
} }
override fun load() { override fun load() {
config = readValueOrDefault() data = readValueOrDefault()
} }
override fun markDirty() { override fun markDirty() {
IConfigHolder.markDirty(this::class) IDataHolder.markDirty(this::class)
} }
} }

View File

@@ -1,4 +1,4 @@
package moe.nea.notenoughupdates.util.config package moe.nea.notenoughupdates.util.data
import java.util.concurrent.CopyOnWriteArrayList import java.util.concurrent.CopyOnWriteArrayList
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents
@@ -9,17 +9,17 @@ import net.minecraft.text.Text
import moe.nea.notenoughupdates.NotEnoughUpdates import moe.nea.notenoughupdates.NotEnoughUpdates
import moe.nea.notenoughupdates.events.ScreenOpenEvent import moe.nea.notenoughupdates.events.ScreenOpenEvent
interface IConfigHolder<T> { interface IDataHolder<T> {
companion object { companion object {
internal var badLoads: MutableList<String> = CopyOnWriteArrayList() internal var badLoads: MutableList<String> = CopyOnWriteArrayList()
private val allConfigs: MutableMap<KClass<out IConfigHolder<*>>, IConfigHolder<*>> = mutableMapOf() private val allConfigs: MutableMap<KClass<out IDataHolder<*>>, IDataHolder<*>> = mutableMapOf()
private val dirty: MutableSet<KClass<out IConfigHolder<*>>> = mutableSetOf() private val dirty: MutableSet<KClass<out IDataHolder<*>>> = mutableSetOf()
internal fun <T : IConfigHolder<K>, K> putConfig(kClass: KClass<T>, inst: IConfigHolder<K>) { internal fun <T : IDataHolder<K>, K> putDataHolder(kClass: KClass<T>, inst: IDataHolder<K>) {
allConfigs[kClass] = inst allConfigs[kClass] = inst
} }
fun <T : IConfigHolder<K>, K> markDirty(kClass: KClass<T>) { fun <T : IDataHolder<K>, K> markDirty(kClass: KClass<T>) {
if (kClass !in allConfigs) { if (kClass !in allConfigs) {
NotEnoughUpdates.logger.error("Tried to markDirty '${kClass.qualifiedName}', which isn't registered as 'IConfigHolder'") NotEnoughUpdates.logger.error("Tried to markDirty '${kClass.qualifiedName}', which isn't registered as 'IConfigHolder'")
return return
@@ -68,7 +68,7 @@ interface IConfigHolder<T> {
} }
val config: T val data: T
fun save() fun save()
fun markDirty() fun markDirty()
fun load() fun load()

View File

@@ -1,4 +1,4 @@
package moe.nea.notenoughupdates.util.config package moe.nea.notenoughupdates.util.data
import java.nio.file.Path import java.nio.file.Path
import kotlinx.serialization.KSerializer import kotlinx.serialization.KSerializer
@@ -13,15 +13,15 @@ import kotlin.io.path.writeText
import moe.nea.notenoughupdates.NotEnoughUpdates import moe.nea.notenoughupdates.NotEnoughUpdates
import moe.nea.notenoughupdates.util.SBData import moe.nea.notenoughupdates.util.SBData
abstract class ProfileSpecificConfigHolder<S>( abstract class ProfileSpecificDataHolder<S>(
private val configSerializer: KSerializer<S>, private val dataSerializer: KSerializer<S>,
val configName: String, val configName: String,
private val configDefault: () -> S private val configDefault: () -> S
) : IConfigHolder<S?> { ) : IDataHolder<S?> {
var allConfigs: MutableMap<String, S> var allConfigs: MutableMap<String, S>
override val config: S? override val data: S?
get() = SBData.profileCuteName?.let { get() = SBData.profileCuteName?.let {
allConfigs.computeIfAbsent(it) { configDefault() } allConfigs.computeIfAbsent(it) { configDefault() }
} }
@@ -29,10 +29,10 @@ abstract class ProfileSpecificConfigHolder<S>(
init { init {
allConfigs = readValues() allConfigs = readValues()
readValues() readValues()
IConfigHolder.putConfig(this::class, this) IDataHolder.putDataHolder(this::class, this)
} }
private val configDirectory: Path get() = NotEnoughUpdates.CONFIG_DIR.resolve("profiles") private val configDirectory: Path get() = NotEnoughUpdates.CONFIG_DIR.resolve("profiles").resolve(configName)
private fun readValues(): MutableMap<String, S> { private fun readValues(): MutableMap<String, S> {
if (!configDirectory.exists()) { if (!configDirectory.exists()) {
@@ -43,9 +43,9 @@ abstract class ProfileSpecificConfigHolder<S>(
.filter { it.extension == "json" } .filter { it.extension == "json" }
.mapNotNull { .mapNotNull {
try { try {
it.nameWithoutExtension to NotEnoughUpdates.json.decodeFromString(configSerializer, it.readText()) it.nameWithoutExtension to NotEnoughUpdates.json.decodeFromString(dataSerializer, it.readText())
} catch (e: Exception) { /* Expecting IOException and SerializationException, but Kotlin doesn't allow multi catches*/ } catch (e: Exception) { /* Expecting IOException and SerializationException, but Kotlin doesn't allow multi catches*/
IConfigHolder.badLoads.add(configName) IDataHolder.badLoads.add(configName)
NotEnoughUpdates.logger.error( NotEnoughUpdates.logger.error(
"Exception during loading of profile specific config file $it ($configName). This will reset that profiles config.", "Exception during loading of profile specific config file $it ($configName). This will reset that profiles config.",
e e
@@ -67,12 +67,12 @@ abstract class ProfileSpecificConfigHolder<S>(
} }
c.forEach { (name, value) -> c.forEach { (name, value) ->
val f = configDirectory.resolve("$name.json") val f = configDirectory.resolve("$name.json")
f.writeText(NotEnoughUpdates.json.encodeToString(configSerializer, value)) f.writeText(NotEnoughUpdates.json.encodeToString(dataSerializer, value))
} }
} }
override fun markDirty() { override fun markDirty() {
IConfigHolder.markDirty(this::class) IDataHolder.markDirty(this::class)
} }
override fun load() { override fun load() {