Config delegation & reload improvements

- Added a class and an interface to make using list config data easier
- Made everything config-related reusable and reloadable because delegated properties don't update automatically when reassigned
- This also means that the original ConfigData instance should also properly update now when reloading, although now we return the delegate
This commit is contained in:
Norbi Peti 2023-06-18 20:16:12 +02:00
parent a26c16565f
commit 58729ab885
No known key found for this signature in database
GPG key ID: DBA4C4549A927E56
8 changed files with 61 additions and 50 deletions

View file

@ -30,7 +30,7 @@ class ComponentCommand : ICommand2MC() {
@Subcommand(helpText = ["Enable component", "Temporarily or permanently enables a component."])
fun enable(sender: CommandSender, plugin: Plugin, component: String, @OptionalArg permanent: Boolean): Boolean {
if (plugin is ButtonPlugin) {
if (!plugin.justReload()) {
if (!plugin.tryReloadConfig()) {
sender.sendMessage("${ChatColor.RED}Couldn't reload config, check console.")
return true
}

View file

@ -16,10 +16,14 @@ import java.util.function.Consumer
@HasConfig(global = true)
abstract class ButtonPlugin : JavaPlugin() {
protected var iConfig = getIConfigInstance()
private set
protected val iConfig = IHaveConfig(::saveConfig, section)
private var yaml: YamlConfiguration? = null
/**
* May change if the config is reloaded
*/
private val section get() = config.getConfigurationSection("global") ?: config.createSection("global")
protected val data //TODO
: IHaveConfig? = null
@ -39,7 +43,7 @@ abstract class ButtonPlugin : JavaPlugin() {
*/
protected open fun pluginPreDisable() {}
override fun onEnable() {
if (!reloadIConfig()) {
if (!tryReloadConfig()) {
logger.warning("Please fix the issues and restart the server to load the plugin.")
return
}
@ -52,18 +56,6 @@ abstract class ButtonPlugin : JavaPlugin() {
IHaveConfig.pregenConfig(this, null)
}
private fun getIConfigInstance(): IHaveConfig {
return IHaveConfig(
::saveConfig,
this.config.getConfigurationSection("global") ?: this.config.createSection("global")
)
}
private fun reloadIConfig(): Boolean {
iConfig = getIConfigInstance()
return isConfigLoaded // If loading fails, getConfig() returns a temporary instance
}
override fun onDisable() {
try {
pluginPreDisable()
@ -81,14 +73,17 @@ abstract class ButtonPlugin : JavaPlugin() {
}
fun tryReloadConfig(): Boolean {
if (!justReload()) return false
reloadIConfig()
if (!saveAndReloadYaml()) return false
iConfig.reload(section)
componentStack.forEach(Consumer { c -> c.updateConfig() })
return true
}
fun justReload(): Boolean {
if (yaml != null && ConfigData.saveNow(config)) {
/**
* Returns whether the config was loaded successfully. Otherwise, an empty config is used.
*/
private fun saveAndReloadYaml(): Boolean {
if (isConfigLoaded && ConfigData.saveNow(config)) {
logger.warning("Saved pending configuration changes to the file, didn't reload. Apply your changes again.")
return false
}
@ -106,10 +101,8 @@ abstract class ButtonPlugin : JavaPlugin() {
e.printStackTrace()
return false
}
this.yaml = yaml
} else {
return false
}
this.yaml = yaml
val res = getTextResource("configHelp.yml") ?: return true
val yc = YamlConfiguration.loadConfiguration(res)
for ((key, value) in yc.getValues(true))
@ -121,7 +114,7 @@ abstract class ButtonPlugin : JavaPlugin() {
}
override fun getConfig(): FileConfiguration {
if (yaml == null) justReload()
if (!isConfigLoaded) saveAndReloadYaml()
return yaml ?: YamlConfiguration() //Return a temporary instance
}

View file

@ -121,10 +121,11 @@ abstract class Component<TP : JavaPlugin> {
private val className: String get() = javaClass.simpleName
internal fun updateConfig() {
this.config = IHaveConfig(plugin::saveConfig, getConfigSection(plugin))
if (!this::config.isInitialized) this.config = IHaveConfig(plugin::saveConfig, getConfigSection())
else this.config.reload(getConfigSection())
}
private fun getConfigSection(plugin: JavaPlugin): ConfigurationSection {
private fun getConfigSection(): ConfigurationSection {
var compconf = plugin.config.getConfigurationSection("components")
if (compconf == null) compconf = plugin.config.createSection("components")
var configSect = compconf.getConfigurationSection(className)
@ -261,7 +262,7 @@ abstract class Component<TP : JavaPlugin> {
if (component.isEnabled == enabled) return //Don't do anything
if (enabled.also { component.isEnabled = it }) {
try {
component.getConfigSection(component.plugin)
component.updateConfig()
component.enable()
if (ButtonPlugin.configGenAllowed(component)) {
IHaveConfig.pregenConfig(component, null)

View file

@ -23,7 +23,7 @@ import java.util.function.Function
* @param T The type of the config value. May be nullable if the getter cannot always return a value
*/
class ConfigData<T : Any?> internal constructor(
val config: IHaveConfig?,
var config: IHaveConfig?,
override val path: String,
private val primitiveDef: Any,
private val getter: Function<Any, T>,
@ -52,6 +52,10 @@ class ConfigData<T : Any?> internal constructor(
return getter.apply(convertPrimitiveType(freshValue)).also { value = it }
}
override fun reload() {
value = null
}
/**
* Converts a value to [T] from the representation returned by [Configuration.get].
*/
@ -72,9 +76,9 @@ class ConfigData<T : Any?> internal constructor(
}
private fun setInternal(`val`: Any?) {
if (config == null) return
config.config.set(path, `val`)
signalChange(config)
val conf = config ?: return
conf.config.set(path, `val`)
signalChange(conf)
}
/**

View file

@ -1,8 +1,9 @@
package buttondevteam.lib.architecture
import buttondevteam.lib.architecture.config.ConfigDataDelegate
import buttondevteam.lib.architecture.config.ConfigDataDelegate.Companion.delegate
import buttondevteam.lib.architecture.config.IConfigData
import buttondevteam.lib.architecture.config.ListConfigDataDelegate
import buttondevteam.lib.architecture.config.delegate
import org.bukkit.configuration.ConfigurationSection
import java.util.function.Function
@ -18,7 +19,7 @@ class IHaveConfig(
/**
* Returns the Bukkit ConfigurationSection. Use [.signalChange] after changing it.
*/
val config: ConfigurationSection
var config: ConfigurationSection
) {
private val datamap = HashMap<String, IConfigData<*>>()
@ -88,7 +89,7 @@ class IHaveConfig(
elementGetter: Function<Any?, T>? = null,
elementSetter: Function<T, Any?>? = null,
readOnly: Boolean = false
): ConfigDataDelegate<ListConfigData<T>.List> {
): ListConfigDataDelegate<T> {
var data = datamap[path]
if (data == null) datamap[path] = ListConfigData(
this,
@ -109,6 +110,11 @@ class IHaveConfig(
ConfigData.signalChange(this)
}
fun reload(section: ConfigurationSection) {
config = section
datamap.forEach { it.value.reload() }
}
companion object {
/**
* Generates the config YAML.

View file

@ -1,6 +1,7 @@
package buttondevteam.lib.architecture
import buttondevteam.lib.architecture.config.IConfigData
import buttondevteam.lib.architecture.config.IListConfigData
import java.util.function.Function
import java.util.function.Predicate
import java.util.function.UnaryOperator
@ -12,19 +13,14 @@ class ListConfigData<T> internal constructor(
private val elementGetter: Function<Any?, T>,
private val elementSetter: Function<T, Any?>,
readOnly: Boolean
) : IConfigData<ListConfigData<T>.List> {
) : IConfigData<ListConfigData<T>.List>, IListConfigData<T> {
val listConfig: ConfigData<List> =
ConfigData(config, path, primitiveDef, { List((it as ArrayList<*>).toMutableList()) }, { it }, readOnly)
override val path: String get() = listConfig.path
override fun get(): List {
return listConfig.get()
}
override fun set(value: List) {
listConfig.set(value)
}
override val path get() = listConfig.path
override fun get() = listConfig.get()
override fun set(value: List) = listConfig.set(value)
override fun reload() = listConfig.reload()
inner class List(backingList: MutableList<Any?>) : MutableList<T> {
private val primitiveList = backingList

View file

@ -1,12 +1,14 @@
package buttondevteam.lib.architecture.config
import buttondevteam.lib.architecture.ListConfigData
import kotlin.reflect.KProperty
class ConfigDataDelegate<T>(val data: IConfigData<T>) : IConfigData<T> by data {
open class ConfigDataDelegate<T>(private val data: IConfigData<T>) : IConfigData<T> by data {
operator fun getValue(thisRef: Any?, property: KProperty<*>): T = data.get()
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) = data.set(value)
companion object {
fun <T> IConfigData<T>.delegate() = ConfigDataDelegate(this)
}
}
class ListConfigDataDelegate<T>(data: IConfigData<ListConfigData<T>.List>) : ConfigDataDelegate<ListConfigData<T>.List>(data), IListConfigData<T>
fun <T> IConfigData<T>.delegate() = ConfigDataDelegate(this)
fun <T> IListConfigData<T>.delegate() = ListConfigDataDelegate(this)

View file

@ -1,5 +1,7 @@
package buttondevteam.lib.architecture.config
import buttondevteam.lib.architecture.ListConfigData
interface IConfigData<T> {
/**
* Gets the value from the config using the getter specified for the config. If the config is not set, the default value is returned.
@ -11,8 +13,15 @@ interface IConfigData<T> {
*/
fun set(value: T)
/**
* Reload the config from the file.
*/
fun reload()
/**
* The path to the config value.
*/
val path: String
}
}
interface IListConfigData<T> : IConfigData<ListConfigData<T>.List>