aboutsummaryrefslogtreecommitdiff
path: root/src/main/kotlin/moe/nea/notenoughupdates/util/ConfigHolder.kt
blob: 782770807482672ec75170e1f003b8b79c4548e3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
package moe.nea.notenoughupdates.util

import kotlinx.serialization.KSerializer
import kotlinx.serialization.SerializationException
import moe.nea.notenoughupdates.NotEnoughUpdates
import moe.nea.notenoughupdates.events.NEUScreenEvents
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents
import net.minecraft.client.MinecraftClient
import net.minecraft.command.CommandSource
import net.minecraft.server.command.CommandOutput
import net.minecraft.text.Text
import java.io.IOException
import java.nio.file.Path
import java.util.concurrent.CopyOnWriteArrayList
import kotlin.io.path.exists
import kotlin.io.path.readText
import kotlin.io.path.writeText
import kotlin.reflect.KClass

abstract class ConfigHolder<T>(
    val serializer: KSerializer<T>,
    val name: String,
    val default: () -> T
) {

    var config: T
        private set

    init {
        config = readValueOrDefault()
        putConfig(this::class, this)
    }

    val file: Path get() = NotEnoughUpdates.CONFIG_DIR.resolve("$name.json")

    protected fun readValueOrDefault(): T {
        if (file.exists())
            try {
                return NotEnoughUpdates.json.decodeFromString(
                    serializer,
                    file.readText()
                )
            } catch (e: IOException) {
                badLoads.add(name)
                NotEnoughUpdates.logger.error(
                    "IO exception during loading of config file $name. This will reset this config.",
                    e
                )
            } catch (e: SerializationException) {
                badLoads.add(name)
                NotEnoughUpdates.logger.error(
                    "Serialization exception during loading of config file $name. This will reset this config.",
                    e
                )
            }
        return default()
    }

    private fun writeValue(t: T) {
        file.writeText(NotEnoughUpdates.json.encodeToString(serializer, t))
    }

    fun save() {
        writeValue(config)
    }

    fun load() {
        config = readValueOrDefault()
    }

    fun markDirty() {
        Companion.markDirty(this::class)
    }

    companion object {
        private var badLoads: MutableList<String> = CopyOnWriteArrayList()
        private val allConfigs: MutableMap<KClass<out ConfigHolder<*>>, ConfigHolder<*>> = mutableMapOf()
        private val dirty: MutableSet<KClass<out ConfigHolder<*>>> = mutableSetOf()

        private fun <T : ConfigHolder<K>, K> putConfig(kClass: KClass<T>, inst: ConfigHolder<K>) {
            allConfigs[kClass] = inst
        }

        fun <T : ConfigHolder<K>, K> markDirty(kClass: KClass<T>) {
            if (kClass !in allConfigs) {
                NotEnoughUpdates.logger.error("Tried to markDirty '${kClass.qualifiedName}', which isn't registered as 'ConfigHolder'")
                return
            }
            dirty.add(kClass)
        }

        private fun performSaves() {
            val toSave = dirty.toList().also {
                dirty.clear()
            }
            for (it in toSave) {
                val obj = allConfigs[it]
                if (obj == null) {
                    NotEnoughUpdates.logger.error("Tried to save '${it}', which isn't registered as 'ConfigHolder'")
                    continue
                }
                obj.save()
            }
        }

        private fun warnForResetConfigs(player: CommandOutput) {
            if (badLoads.isNotEmpty()) {
                player.sendMessage(
                    Text.literal(
                        "The following configs have been reset: ${badLoads.joinToString(", ")}. " +
                                "This can be intentional, but probably isn't."
                    )
                )
                badLoads.clear()
            }
        }

        fun registerEvents() {
            NEUScreenEvents.SCREEN_OPEN.register(NEUScreenEvents.OnScreenOpen { old, new ->
                performSaves()
                val p = MinecraftClient.getInstance().player
                if (p != null) {
                    warnForResetConfigs(p)
                }
                false
            })
            ClientLifecycleEvents.CLIENT_STOPPING.register(ClientLifecycleEvents.ClientStopping {
                performSaves()
            })
        }

    }
}