diff options
Diffstat (limited to 'src/main/kotlin/dev/isxander/yacl3/dsl/Impl.kt')
-rw-r--r-- | src/main/kotlin/dev/isxander/yacl3/dsl/Impl.kt | 298 |
1 files changed, 298 insertions, 0 deletions
diff --git a/src/main/kotlin/dev/isxander/yacl3/dsl/Impl.kt b/src/main/kotlin/dev/isxander/yacl3/dsl/Impl.kt new file mode 100644 index 0000000..ab0d1a2 --- /dev/null +++ b/src/main/kotlin/dev/isxander/yacl3/dsl/Impl.kt @@ -0,0 +1,298 @@ +package dev.isxander.yacl3.dsl + +import dev.isxander.yacl3.api.* +import dev.isxander.yacl3.impl.utils.YACLConstants +import net.minecraft.network.chat.Component +import org.slf4j.Logger +import java.util.concurrent.CompletableFuture +import kotlin.properties.ReadOnlyProperty + +private val LOGGER: Logger = YACLConstants.LOGGER + +class ParentRegistrarImpl<T, DSL : Buildable<T>, INNER>( + private val adder: (registrant: T, id: String) -> Unit, + private val dslFactory: (id: String) -> DSL, + private val getter: (id: String) -> CompletableFuture<T>, + private val innerGetter: (id: String) -> CompletableFuture<INNER>, +) : ParentRegistrar<T, DSL, INNER> { + override fun register(id: String, registrant: T) = + adder(registrant, id).let { registrant } + + override fun register(id: String, block: DSL.() -> Unit): T = + register(id, dslFactory(id).apply(block).build()) + + override fun registering(id: String?, block: DSL.() -> Unit) = + RegisterableActionDelegateProvider(this::register, block, id) + + override fun futureRef(id: String): CompletableFuture<T> = getter(id) + + override val futureRef: ReadOnlyProperty<Any?, CompletableFuture<T>> + get() = ReadOnlyProperty { _, property -> futureRef(property.name) } + + override fun ref(id: String): T? = + futureRef(id).getNow(null) + + override val ref: ReadOnlyProperty<Any?, T?> + get() = ReadOnlyProperty { _, property -> ref(property.name) } + + override fun get(id: String): CompletableFuture<INNER> = innerGetter(id) +} + +class RootDslImpl( + override val rootId: String +) : RootDsl, Buildable<YetAnotherConfigLib> { + override val rootKey: String = "yacl3.config.$rootId" + + override val thisRoot = CompletableFuture<YetAnotherConfigLib>() + + override val built = thisRoot + private val builder = YetAnotherConfigLib.createBuilder() + + private val categoryFutures = mutableMapOf<String, CompletableFuture<CategoryDsl>>() + private fun createFuture(id: String) = categoryFutures.computeIfAbsent(id) { CompletableFuture() } + + init { + builder.title(Component.translatable("$rootKey.title")) + } + + override val categories: CategoryRegistrar = ParentRegistrarImpl( + { category, _ -> builder.category(category) }, + { id -> CategoryDslImpl(id, this) + .also { createFuture(id).complete(it) } + }, + { id -> createFuture(id).thenCompose { it.built } }, + { id -> createFuture(id).thenApply { it.groups } }, + ) + + override fun title(component: Component) { + builder.title(component) + } + + override fun title(block: () -> Component) = title(block()) + + override fun screenInit(block: () -> Unit) { + builder.screenInit { block() } + } + + override fun save(block: () -> Unit) { + builder.save { block() } + } + + override fun build(): YetAnotherConfigLib = + builder.build().also { + thisRoot.complete(it) + checkUnresolvedFutures() + } + + private fun checkUnresolvedFutures() { + categoryFutures.filterValues { !it.isDone } + .forEach { LOGGER.error("Future category ${it.key} was referenced but was never built.") } + } +} + +class CategoryDslImpl( + override val categoryId: String, + private val parent: RootDsl, +) : CategoryDsl { + override val categoryKey = "${parent.rootKey}.category.$categoryId" + + override val thisCategory = CompletableFuture<ConfigCategory>() + + override val built = thisCategory + private val builder = ConfigCategory.createBuilder() + + private val groupFutures = mutableMapOf<String, CompletableFuture<GroupDsl>>() + private fun createGroupFuture(id: String) = groupFutures.computeIfAbsent(id) { CompletableFuture() } + + private val rootOptFutures = mutableMapOf<String, CompletableFuture<Option<*>>>() + private fun createRootOptFuture(id: String) = rootOptFutures.computeIfAbsent(id) { CompletableFuture() } + + init { + builder.name(Component.translatable(categoryKey)) + } + + override val groups: GroupRegistrar = ParentRegistrarImpl( + { group, _ -> builder.group(group) }, + { id -> GroupDslImpl(id, this) + .also { createGroupFuture(id).complete(it) } + }, + { id -> createGroupFuture(id).thenCompose { it.built } }, + { id -> createGroupFuture(id).thenApply { it.options } }, + ) + + override val rootOptions: OptionRegistrar = OptionRegistrarImpl( + { option, id -> builder.option(option).also { createRootOptFuture(id).complete(option) } }, + { id -> createRootOptFuture(id) }, + "$categoryKey.root", + ) + + override fun name(component: Component) { + builder.name(component) + } + + override fun name(block: () -> Component) = name(block()) + + override fun tooltip(block: TextLineBuilderDsl.() -> Unit) { + builder.tooltip(TextLineBuilderDsl.createText(block)) + } + + override fun tooltip(vararg component: Component) = tooltip { + component.forEach { +it } + } + + override fun build(): ConfigCategory = + builder.build().also { + thisCategory.complete(it) + checkUnresolvedFutures() + } + + private fun checkUnresolvedFutures() { + groupFutures.filterValues { !it.isDone } + .forEach { LOGGER.error("Future group $categoryId/${it.key} was referenced but was never built.") } + rootOptFutures.filterValues { !it.isDone } + .forEach { LOGGER.error("Future option $categoryId/root/${it.key} was referenced but was never built.") } + } +} + +class GroupDslImpl( + override val groupId: String, + private val parent: CategoryDsl, +) : GroupDsl { + override val groupKey = "${parent.categoryKey}.group.$groupId" + + override val thisGroup = CompletableFuture<OptionGroup>() + + override val built = thisGroup + private val builder = OptionGroup.createBuilder() + + private val optionFutures = mutableMapOf<String, CompletableFuture<Option<*>>>() + private fun createOptionFuture(id: String) = optionFutures.computeIfAbsent(id) { CompletableFuture() } + + init { + builder.name(Component.translatable(groupKey)) + } + + override val options: OptionRegistrar = OptionRegistrarImpl( + { option, id -> builder.option(option).also { createOptionFuture(id).complete(option) } }, + { id -> createOptionFuture(id) }, + groupKey, + ) + + override fun name(component: Component) { + builder.name(component) + } + + override fun name(block: () -> Component) = name(block()) + + override fun description(description: OptionDescription) { + builder.description(description) + } + + override fun descriptionBuilder(block: OptionDescription.Builder.() -> Unit) { + builder.description(OptionDescription.createBuilder().apply(block).build()) + } + + override fun OptionDescription.Builder.addDefaultText(lines: Int?) { + addDefaultText("$groupKey.description", lines) + } + + override fun build(): OptionGroup = + builder.build().also { + thisGroup.complete(it) + checkUnresolvedFutures() + } + + private fun checkUnresolvedFutures() { + optionFutures.filterValues { !it.isDone } + .forEach { LOGGER.error("Future option ${parent.categoryId}/$groupId/${it.key} was referenced but was never built.") } + } +} + +class OptionRegistrarImpl( + private val adder: (registrant: Option<*>, id: String) -> Unit, + private val getter: (id: String) -> CompletableFuture<Option<*>>, + private val groupKey: String, +) : OptionRegistrar { + override fun <T, OPT : Option<T>> register(id: String, option: OPT): OPT = + adder(option, id).let { option } + + override fun <T> register(id: String, block: OptionDsl<T>.() -> Unit): Option<T> = + register(id, OptionDslImpl<T>(id, groupKey).apply(block).build()) + + override fun <T> registering( + id: String?, + block: OptionDsl<T>.() -> Unit + ) = RegisterableActionDelegateProvider(this::register, block, id) + + @Suppress("UNCHECKED_CAST") + override fun <T> futureRef(id: String): CompletableFuture<Option<T>> = + getter(id) as CompletableFuture<Option<T>> + + override fun <T> futureRef() = + RegisterableDelegateProvider({ this.futureRef<T>(it) }, null) + + override fun <T> ref(id: String?) = ReadOnlyProperty<Any?, Option<T>?> { _, property -> + futureRef<T>(id ?: property.name).getNow(null) + } + + override fun registerLabel(id: String): LabelOption = + register(id, LabelOption.create(Component.translatable("$groupKey.label.$id"))) + + override val registeringLabel = RegisterableDelegateProvider(this::registerLabel, null) + + override fun registerLabel(id: String, text: Component): LabelOption = + register(id, LabelOption.create(text)) + + override fun registerLabel(id: String, builder: TextLineBuilderDsl.() -> Unit): LabelOption = + registerLabel(id, TextLineBuilderDsl.createText(builder)) + + override fun registerButton(id: String, block: ButtonOptionDsl.() -> Unit): ButtonOption = + register(id, ButtonOptionDslImpl(id, groupKey).apply(block).build()) + + override fun registeringButton( + id: String?, + block: ButtonOptionDsl.() -> Unit + ) = RegisterableActionDelegateProvider(this::registerButton, block, id) +} + +class OptionDslImpl<T>( + override val optionId: String, + groupKey: String, + private val builder: Option.Builder<T> = Option.createBuilder(), +) : OptionDsl<T>, Option.Builder<T> by builder { + override val optionKey = "$groupKey.option.$optionId" + + override val thisOption = CompletableFuture<Option<T>>() + override val built = thisOption + + init { + builder.name(Component.translatable(optionKey)) + } + + override fun OptionDescription.Builder.addDefaultText(lines: Int?) = + addDefaultText(prefix = "$optionKey.description", lines = lines) + + override fun build(): Option<T> = + builder.build().also { thisOption.complete(it) } +} + +class ButtonOptionDslImpl( + override val optionId: String, + groupKey: String, + private val builder: ButtonOption.Builder = ButtonOption.createBuilder(), +) : ButtonOptionDsl, ButtonOption.Builder by builder { + override val optionKey = "$groupKey.option.$optionId" + + override val thisOption = CompletableFuture<ButtonOption>() + override val built = thisOption + + init { + builder.name(Component.translatable(optionKey)) + } + + override fun OptionDescription.Builder.addDefaultText(lines: Int?) = + addDefaultText(prefix = "$optionKey.description", lines = lines) + + override fun build(): ButtonOption = + builder.build().also { thisOption.complete(it) } +} |