From a757fcc9ab73bd7318d0153fa58eb39abc08f4b3 Mon Sep 17 00:00:00 2001 From: DBotThePony Date: Sat, 29 Oct 2022 00:39:45 +0700 Subject: [PATCH] New matter values system checkpoint --- .../ru/dbotthepony/mc/otm/datagen/DataGen.kt | 6 + .../mc/otm/OverdriveThatMatters.java | 3 + .../kotlin/ru/dbotthepony/mc/otm/core/Ext.kt | 4 + .../mc/otm/core/ImpreciseFraction.kt | 4 + .../mc/otm/matter/MatterDataProvider.kt | 288 ++++++ .../mc/otm/matter/MatterManager.kt | 843 ++++++++++++++++++ .../mc/otm/matter/MatterRegistry.kt | 4 +- 7 files changed, 1150 insertions(+), 2 deletions(-) create mode 100644 src/main/kotlin/ru/dbotthepony/mc/otm/matter/MatterDataProvider.kt create mode 100644 src/main/kotlin/ru/dbotthepony/mc/otm/matter/MatterManager.kt diff --git a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/DataGen.kt b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/DataGen.kt index d4d82bec9..35da1f29b 100644 --- a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/DataGen.kt +++ b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/DataGen.kt @@ -47,6 +47,7 @@ import ru.dbotthepony.mc.otm.datagen.recipes.addShapelessRecipes import ru.dbotthepony.mc.otm.datagen.recipes.addOreSmeltingRecipes import ru.dbotthepony.mc.otm.datagen.tags.TagsProvider import ru.dbotthepony.mc.otm.datagen.tags.addTags +import ru.dbotthepony.mc.otm.matter.MatterDataProvider import ru.dbotthepony.mc.otm.registry.objects.ColoredDecorativeBlock import ru.dbotthepony.mc.otm.registry.objects.DecorativeBlock import kotlin.properties.Delegates @@ -73,6 +74,8 @@ object DataGen { private set var researchProvider: AndroidResearchDataProvider by WriteOnce() private set + var matterData: MatterDataProvider by WriteOnce() + private set fun decorativeCubeAll(vararg blocks: Block) { blockModelProvider.decorativeCubeAll(*blocks) @@ -401,6 +404,7 @@ object DataGen { val recipeProvider = MatteryRecipeProvider(event.generator) val lootModifier = LootModifiers(event.generator) val languageProvider = MatteryLanguageProvider(event.generator) + val matterData = MatterDataProvider(event) val researchProvider = AndroidResearchDataProvider(event.generator).also { it.exec { addResearchData(it, languageProvider) } } this.blockModelProvider = blockModelProvider @@ -411,6 +415,7 @@ object DataGen { this.lootModifier = lootModifier this.languageProvider = languageProvider this.researchProvider = researchProvider + this.matterData = matterData val tagsProvider = TagsProvider(event) val advancementProvider = AdvancementProvider(event) @@ -428,6 +433,7 @@ object DataGen { event.generator.addProvider(true, SoundDataProvider(event)) event.generator.addProvider(true, researchProvider) event.generator.addProvider(true, advancementProvider) + event.generator.addProvider(true, matterData) AddEnglishLanguage(languageProvider) diff --git a/src/main/java/ru/dbotthepony/mc/otm/OverdriveThatMatters.java b/src/main/java/ru/dbotthepony/mc/otm/OverdriveThatMatters.java index de072a52c..4dbb042e0 100644 --- a/src/main/java/ru/dbotthepony/mc/otm/OverdriveThatMatters.java +++ b/src/main/java/ru/dbotthepony/mc/otm/OverdriveThatMatters.java @@ -39,6 +39,7 @@ import ru.dbotthepony.mc.otm.item.QuantumBatteryItem; import ru.dbotthepony.mc.otm.item.weapon.AbstractWeaponItem; import ru.dbotthepony.mc.otm.item.PortableCondensationDriveItem; import ru.dbotthepony.mc.otm.matter.MatterDataKt; +import ru.dbotthepony.mc.otm.matter.MatterManager; import ru.dbotthepony.mc.otm.matter.MatterRegistryKt; import ru.dbotthepony.mc.otm.network.*; import ru.dbotthepony.mc.otm.registry.*; @@ -142,6 +143,8 @@ public final class OverdriveThatMatters { EVENT_BUS.addListener(EventPriority.NORMAL, AndroidResearchManager.INSTANCE::reloadEvent); EVENT_BUS.addListener(EventPriority.NORMAL, AndroidResearchManager.INSTANCE::syncEvent); + EVENT_BUS.addListener(EventPriority.NORMAL, MatterManager.INSTANCE::reloadEvent); + EVENT_BUS.addListener(EventPriority.NORMAL, SynchronizedBlockEntity.Companion::onServerStopping); EVENT_BUS.addListener(EventPriority.NORMAL, SynchronizedBlockEntity.Companion::onLevelUnload); EVENT_BUS.addListener(EventPriority.NORMAL, SynchronizedBlockEntity.Companion::onWatch); diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/Ext.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/Ext.kt index f19157160..9940de08c 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/Ext.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/Ext.kt @@ -6,6 +6,7 @@ package ru.dbotthepony.mc.otm.core import com.google.common.collect.ImmutableList import com.google.gson.JsonElement import com.google.gson.JsonObject +import com.google.gson.JsonPrimitive import net.minecraft.core.BlockPos import net.minecraft.nbt.ByteArrayTag import net.minecraft.nbt.CompoundTag @@ -239,6 +240,9 @@ fun FriendlyByteBuf.readBigInteger(byteLimit: Int = 128) = BigInteger(readByteAr operator fun IItemHandler.get(index: Int): ItemStack = getStackInSlot(index) operator fun JsonObject.set(s: String, value: JsonElement) = add(s, value) +operator fun JsonObject.set(s: String, value: String) = add(s, JsonPrimitive(value)) +operator fun JsonObject.set(s: String, value: Number) = add(s, JsonPrimitive(value)) +operator fun JsonObject.set(s: String, value: Boolean) = add(s, JsonPrimitive(value)) fun LazyOptional.orNull(): T? { if (!isPresent) { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/ImpreciseFraction.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/ImpreciseFraction.kt index 200bea576..fb79cf399 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/ImpreciseFraction.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/ImpreciseFraction.kt @@ -753,6 +753,10 @@ class ImpreciseFraction @JvmOverloads constructor(whole: BigInteger, decimal: Do return toBigDecmial().divide(divisor.toBigDecmial(), PERCENTAGE_CONTEXT).toFloat() } + operator fun rem(divisor: ImpreciseFraction): ImpreciseFraction { + TODO("Not yet implemented") + } + companion object { @JvmStatic val serialVersionUID: Long = 287354739494574838L diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/matter/MatterDataProvider.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/matter/MatterDataProvider.kt new file mode 100644 index 000000000..05337d485 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/matter/MatterDataProvider.kt @@ -0,0 +1,288 @@ +package ru.dbotthepony.mc.otm.matter + +import net.minecraft.data.CachedOutput +import net.minecraft.data.DataGenerator +import net.minecraft.data.DataProvider +import net.minecraft.resources.ResourceLocation +import net.minecraft.tags.TagKey +import net.minecraft.world.item.Item +import net.minecraft.world.level.ItemLike +import net.minecraftforge.data.event.GatherDataEvent +import ru.dbotthepony.mc.otm.core.ImpreciseFraction +import ru.dbotthepony.mc.otm.core.registryName +import java.util.Collections +import java.util.function.Consumer + +open class MatterDataProvider(protected val dataGenerator: DataGenerator, val namespace: String?) : DataProvider { + constructor(event: GatherDataEvent) : this(event.generator, event.modContainer.namespace) + + protected val pathProvider: DataGenerator.PathProvider = dataGenerator.createPathProvider(DataGenerator.Target.DATA_PACK, MatterManager.DIRECTORY) + protected val actions = LinkedHashMap() + + sealed class Configuration(val name: ResourceLocation) { + var errorOnFailure: Boolean = false + var matter: ImpreciseFraction? = null + var complexity: Double? = null + var priority: Int? = null + + private var _key: ResourceLocation? = null + private var _tag: TagKey? = null + + var key: ResourceLocation? + get() = _key + set(value) { + checkNotNull(value) { "Can't set key to null" } + _tag = null + _key = value + } + + var tag: TagKey? + get() = _tag + set(value) { + checkNotNull(value) { "Can't set tag to null" } + _key = null + _tag = value + } + + protected fun checkKeyTag() { + check(_key != null || _tag != null) { "You must define either key or tag for $name" } + } + + protected fun checkMatterValues() { + check(matter == null || (matter ?: throw ConcurrentModificationException()) >= ImpreciseFraction.ZERO) { "Invalid matter value: ${matter ?: throw ConcurrentModificationException()}" } + check(complexity == null || (complexity ?: throw ConcurrentModificationException()) >= 0.0) { "Invalid matter value: ${matter ?: throw ConcurrentModificationException()}" } + } + + abstract fun build(): MatterManager.AbstractRegistryAction + } + + class InsertConfiguration(name: ResourceLocation) : Configuration(name) { + var replaceIfExists: Boolean = false + var comparePriority: Boolean = false + + override fun build(): MatterManager.InsertAction { + checkKeyTag() + checkMatterValues() + check(matter != null && complexity != null) { "You must define both matter value and complexity for $name" } + + if (key != null) { + return MatterManager.InsertAction( + key = key ?: throw ConcurrentModificationException(), + matter = matter, + complexity = complexity, + priority = priority, + errorOnFailure = errorOnFailure, + replaceIfExists = replaceIfExists, + comparePriority = comparePriority + ) + } else { + return MatterManager.InsertAction( + tag = tag ?: throw ConcurrentModificationException(), + matter = matter, + complexity = complexity, + priority = priority, + errorOnFailure = errorOnFailure, + replaceIfExists = replaceIfExists, + comparePriority = comparePriority + ) + } + } + } + + class DeleteConfiguration(name: ResourceLocation) : Configuration(name) { + override fun build(): MatterManager.DeleteAction { + checkKeyTag() + + if (key != null) { + return MatterManager.DeleteAction( + key = key ?: throw ConcurrentModificationException(), + errorOnFailure = errorOnFailure, + ) + } else { + return MatterManager.DeleteAction( + tag = tag ?: throw ConcurrentModificationException(), + errorOnFailure = errorOnFailure, + ) + } + } + } + + class UpdateConfiguration(name: ResourceLocation) : Configuration(name) { + val matterFunctions = ArrayList>() + val complexityFunctions = ArrayList>() + val priorityFunctions = ArrayList>() + + fun addMatterFunction(function: MatterManager.UpdateAction.Function, value: ImpreciseFraction): UpdateConfiguration { + matterFunctions.add(MatterManager.UpdateAction.BoundFunction(function, value)) + matter = null + return this + } + + fun addComplexityFunction(function: MatterManager.UpdateAction.Function, value: Double): UpdateConfiguration { + complexityFunctions.add(MatterManager.UpdateAction.BoundFunction(function, value)) + matter = null + return this + } + + fun addPriorityFunction(function: MatterManager.UpdateAction.Function, value: Int): UpdateConfiguration { + priorityFunctions.add(MatterManager.UpdateAction.BoundFunction(function, value)) + matter = null + return this + } + + override fun build(): MatterManager.UpdateAction { + check(!(matterFunctions.isNotEmpty() && matter != null)) { "Can't have both matter value and matter value functions be defined at the same time" } + check(!(complexityFunctions.isNotEmpty() && complexity != null)) { "Can't have both complexity and complexity functions be defined at the same time" } + check(!(priorityFunctions.isNotEmpty() && priority != null)) { "Can't have both priority and priority functions be defined at the same time" } + + check( + matterFunctions.isNotEmpty() || matter != null || + complexityFunctions.isNotEmpty() || complexity != null || + priorityFunctions.isNotEmpty() || priority != null + ) { "Update configuration is completely empty" } + + checkMatterValues() + checkKeyTag() + + if (key != null) { + return MatterManager.UpdateAction( + key = key ?: throw ConcurrentModificationException(), + matter = matter, + complexity = complexity, + priority = priority, + errorOnFailure = errorOnFailure, + matterFunctions = matterFunctions, + complexityFunctions = complexityFunctions, + priorityFunctions = priorityFunctions + ) + } else { + return MatterManager.UpdateAction( + tag = tag ?: throw ConcurrentModificationException(), + matter = matter, + complexity = complexity, + priority = priority, + errorOnFailure = errorOnFailure, + matterFunctions = matterFunctions, + complexityFunctions = complexityFunctions, + priorityFunctions = priorityFunctions + ) + } + } + } + + fun insert(name: ResourceLocation, configurator: InsertConfiguration.() -> Unit): MatterManager.InsertAction { + check(!actions.containsKey(name)) { "Already has action with name $name" } + val config = InsertConfiguration(name) + configurator.invoke(config) + val built = config.build() + actions[name] = built + return built + } + + fun delete(name: ResourceLocation, configurator: DeleteConfiguration.() -> Unit): MatterManager.DeleteAction { + check(!actions.containsKey(name)) { "Already has action with name $name" } + val config = DeleteConfiguration(name) + configurator.invoke(config) + val built = config.build() + actions[name] = built + return built + } + + fun update(name: ResourceLocation, configurator: UpdateConfiguration.() -> Unit): MatterManager.UpdateAction { + check(!actions.containsKey(name)) { "Already has action with name $name" } + val config = UpdateConfiguration(name) + configurator.invoke(config) + val built = config.build() + actions[name] = built + return built + } + + protected fun updateLocation(input: ResourceLocation, prefix: String): ResourceLocation { + if (namespace != null) { + if (input.namespace == namespace) { + return ResourceLocation(input.namespace, prefix + input.path) + } else { + return ResourceLocation(namespace, prefix + input.namespace + "/" + input.path) + } + } else { + return ResourceLocation(input.namespace, prefix + input.path) + } + } + + fun insert(name: ItemLike, configurator: InsertConfiguration.() -> Unit): MatterManager.InsertAction { + return insert(updateLocation(name.asItem().registryName ?: throw NullPointerException("${name.asItem()} does not have registry name!"), "item/")) { + key = name.asItem().registryName ?: throw ConcurrentModificationException() + configurator.invoke(this) + } + } + + fun delete(name: ItemLike, configurator: DeleteConfiguration.() -> Unit): MatterManager.DeleteAction { + return delete(updateLocation(name.asItem().registryName ?: throw NullPointerException("${name.asItem()} does not have registry name!"), "item/")) { + key = name.asItem().registryName ?: throw ConcurrentModificationException() + configurator.invoke(this) + } + } + + fun update(name: ItemLike, configurator: UpdateConfiguration.() -> Unit): MatterManager.UpdateAction { + return update(updateLocation(name.asItem().registryName ?: throw NullPointerException("${name.asItem()} does not have registry name!"), "item/")) { + key = name.asItem().registryName ?: throw ConcurrentModificationException() + configurator.invoke(this) + } + } + + fun insert(name: TagKey, configurator: InsertConfiguration.() -> Unit): MatterManager.InsertAction { + return insert(updateLocation(name.location, "tag/")) { + tag = name + configurator.invoke(this) + } + } + + fun delete(name: TagKey, configurator: DeleteConfiguration.() -> Unit): MatterManager.DeleteAction { + return delete(updateLocation(name.location, "tag/")) { + tag = name + configurator.invoke(this) + } + } + + fun update(name: TagKey, configurator: UpdateConfiguration.() -> Unit): MatterManager.UpdateAction { + return update(updateLocation(name.location, "tag/")) { + tag = name + configurator.invoke(this) + } + } + + fun insert(name: ResourceLocation, configurator: Consumer) = insert(name, configurator::accept) + fun delete(name: ResourceLocation, configurator: Consumer) = delete(name, configurator::accept) + fun update(name: ResourceLocation, configurator: Consumer) = update(name, configurator::accept) + + fun insert(name: ItemLike, configurator: Consumer) = insert(name, configurator::accept) + fun delete(name: ItemLike, configurator: Consumer) = delete(name, configurator::accept) + fun update(name: ItemLike, configurator: Consumer) = update(name, configurator::accept) + + fun insert(name: TagKey, configurator: Consumer) = insert(name, configurator::accept) + fun delete(name: TagKey, configurator: Consumer) = delete(name, configurator::accept) + fun update(name: TagKey, configurator: Consumer) = update(name, configurator::accept) + + /** + * Override this if you prefer The Mundane Way™ instead of calling [insert], [delete] and [update] directly + * + * Called inside [run] + */ + protected open fun addActions() {} + + protected val added = ArrayList() + val addedView: List = Collections.unmodifiableList(added) + + final override fun run(output: CachedOutput) { + addActions() + + for ((key, value) in actions) { + DataProvider.saveStable(output, value.toJson(), pathProvider.json(key)) + added.add(value) + } + } + + override fun getName(): String { + return "Matter Data" + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/matter/MatterManager.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/matter/MatterManager.kt new file mode 100644 index 000000000..4c059ca97 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/matter/MatterManager.kt @@ -0,0 +1,843 @@ +package ru.dbotthepony.mc.otm.matter + +import com.google.common.collect.ImmutableList +import com.google.gson.GsonBuilder +import com.google.gson.JsonArray +import com.google.gson.JsonElement +import com.google.gson.JsonObject +import com.google.gson.JsonParseException +import com.google.gson.JsonPrimitive +import it.unimi.dsi.fastutil.objects.Reference2ObjectFunction +import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap +import net.minecraft.resources.ResourceLocation +import net.minecraft.server.packs.resources.ResourceManager +import net.minecraft.server.packs.resources.SimpleJsonResourceReloadListener +import net.minecraft.tags.ItemTags +import net.minecraft.tags.TagKey +import net.minecraft.util.profiling.ProfilerFiller +import net.minecraft.world.item.Item +import net.minecraftforge.event.AddReloadListenerEvent +import net.minecraftforge.registries.ForgeRegistries +import org.apache.logging.log4j.LogManager +import ru.dbotthepony.mc.otm.core.ImpreciseFraction +import ru.dbotthepony.mc.otm.core.integerDivisionDown +import ru.dbotthepony.mc.otm.core.integerDivisionUp +import ru.dbotthepony.mc.otm.core.registryName +import ru.dbotthepony.mc.otm.core.set +import ru.dbotthepony.mc.otm.data.stream + +object MatterManager : SimpleJsonResourceReloadListener(GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create(), "otm_matter") { + const val DIRECTORY = "otm_matter" + + sealed class AbstractRegistryAction { + sealed class Condition { + abstract fun test(): Boolean + } + + class CombinedCondition(val conditions: Collection) : Condition() { + override fun test(): Boolean { + return conditions.all { it.test() } + } + } + + val errorOnFailure: Boolean + val tag: TagKey? + val key: ResourceLocation? + val matter: ImpreciseFraction? + val complexity: Double? + val priority: Int? + + constructor(json: JsonObject) { + errorOnFailure = json["error_on_failure"]?.asBoolean ?: false + priority = json["priority"]?.asInt + + val location = json["id"]?.asString?.let { ResourceLocation.tryParse(it) } ?: throw JsonParseException("Invalid `id` value: ${json["id"]}") + + if (location.namespace == "#") { + throw JsonParseException("Invalid `id` value: $location") + } + + if (location.namespace.startsWith("#")) { + tag = ItemTags.create(ResourceLocation(location.toString().substring(1))) + key = null + } else { + key = location + tag = null + } + + try { + matter = json["matter"]?.asString?.let(::ImpreciseFraction) + + if (matter != null && !matter.isPositive) { + throw JsonParseException("Can't have non-positive matter value. To remove an entry from registry please use 'delete' action instead.") + } + } catch(err: NumberFormatException) { + throw JsonParseException("Invalid `matter` field: ${json["matter"]}", err) + } + + try { + complexity = json["complexity"]?.asString?.toDouble() + + if (complexity != null && complexity <= 0.0) { + throw JsonParseException("Can't have non-positive complexity. To remove an entry from registry please use 'delete' action instead.") + } + } catch(err: NumberFormatException) { + throw JsonParseException("Invalid `complexity` field: ${json["complexity"]}", err) + } + } + + constructor( + tag: TagKey, + matter: ImpreciseFraction?, + complexity: Double?, + priority: Int? = null, + errorOnFailure: Boolean = false + ) { + this.tag = tag + this.key = null + this.matter = matter + this.complexity = complexity + this.priority = priority + this.errorOnFailure = errorOnFailure + + if (matter != null && !matter.isPositive) { + throw IllegalArgumentException("Can't have non-positive matter value. To remove an entry from registry please use 'delete' action instead.") + } + + if (complexity != null && complexity <= 0.0) { + throw IllegalArgumentException("Can't have non-positive complexity. To remove an entry from registry please use 'delete' action instead.") + } + } + + constructor( + key: ResourceLocation, + matter: ImpreciseFraction?, + complexity: Double?, + priority: Int? = null, + errorOnFailure: Boolean = false + ) { + this.tag = null + this.key = key + this.matter = matter + this.complexity = complexity + this.priority = priority + this.errorOnFailure = errorOnFailure + + if (matter != null && !matter.isPositive) { + throw IllegalArgumentException("Can't have non-positive matter value. To remove an entry from registry please use 'delete' action instead.") + } + + if (complexity != null && complexity <= 0.0) { + throw IllegalArgumentException("Can't have non-positive complexity. To remove an entry from registry please use 'delete' action instead.") + } + } + + fun checkConditions(): Boolean { + return true + } + + abstract fun update(registry: MutableCollection, modifier: ResourceLocation) + + protected inline fun fail(reason: () -> String) { + if (errorOnFailure) { + throw JsonParseException(reason.invoke()) + } + } + + open fun toJson(): JsonObject { + return JsonObject().also { + if (key != null) + it["id"] = JsonPrimitive(key.toString()) + else + it["id"] = JsonPrimitive("#${tag!!.location}") + + if (priority != null) + it["priority"] = priority + + if (matter != null) + it["matter"] = matter.toString() + + if (complexity != null) + it["complexity"] = complexity + } + } + } + + class InsertAction : AbstractRegistryAction { + val replaceIfExists: Boolean + val comparePriority: Boolean + + constructor( + key: ResourceLocation, + matter: ImpreciseFraction?, + complexity: Double?, + priority: Int? = null, + errorOnFailure: Boolean = false, + replaceIfExists: Boolean = false, + comparePriority: Boolean = false, + ) : super(key = key, matter = matter, complexity = complexity, priority = priority, errorOnFailure = errorOnFailure) { + this.replaceIfExists = replaceIfExists + this.comparePriority = comparePriority + } + + constructor( + tag: TagKey, + matter: ImpreciseFraction?, + complexity: Double?, + priority: Int? = null, + errorOnFailure: Boolean = false, + replaceIfExists: Boolean = false, + comparePriority: Boolean = false, + ) : super(tag = tag, matter = matter, complexity = complexity, priority = priority, errorOnFailure = errorOnFailure) { + this.replaceIfExists = replaceIfExists + this.comparePriority = comparePriority + } + + constructor(json: JsonObject) : super(json) { + this.replaceIfExists = json["replace_if_exists"]?.asBoolean ?: false + this.comparePriority = json["compare_priority"]?.asBoolean ?: false + } + + override fun toJson(): JsonObject { + checkNotNull(matter) { "Missing matter value, which is required for insert action" } + checkNotNull(complexity) { "Missing complexity value, which is required for insert action" } + + return super.toJson().also { + it["action"] = "insert" + it["replace_if_exists"] = replaceIfExists + it["compare_priority"] = comparePriority + } + } + + override fun update(registry: MutableCollection, modifier: ResourceLocation) { + checkNotNull(matter) { "Missing matter value, which is required for insert action" } + checkNotNull(complexity) { "Missing complexity value, which is required for insert action" } + + check(!comparePriority || priority != null) { "If compare_priority is true then priority must not be null" } + + if (!checkConditions()) { + return + } + + if (key != null) { + if (replaceIfExists) { + // search & replace in parallel, if possible + val replaced = registry.parallelStream().anyMatch { + if (it is MutableKeyEntry && it.key == key) { + if (!comparePriority || it.priority < priority!!) { + it.matter = matter + it.complexity = complexity + it.priority = priority ?: 0 + it.modificationChain.clear() + it.modificationChain.add(modifier) + } + + return@anyMatch true + } else { + return@anyMatch false + } + } + + if (!replaced) { + registry.add(MutableKeyEntry(key, mutableListOf(modifier), matter, complexity, priority ?: 0)) + } + } else { + if (registry.parallelStream().anyMatch { it is MutableKeyEntry && it.key == key }) { + fail { "Value with key $key already exists" } + return + } + + registry.add(MutableKeyEntry(key, mutableListOf(modifier), matter, complexity, priority ?: 0)) + } + } else { + if (replaceIfExists) { + // search & replace in parallel, if possible + val replaced = registry.parallelStream().anyMatch { + if (it is MutableTagEntry && it.tag == tag) { + if (!comparePriority || it.priority < priority!!) { + it.matter = matter + it.complexity = complexity + it.priority = priority ?: 0 + it.modificationChain.clear() + it.modificationChain.add(modifier) + } + + return@anyMatch true + } else { + return@anyMatch false + } + } + + if (!replaced) { + registry.add(MutableTagEntry(tag!!, mutableListOf(modifier), matter, complexity, priority ?: 0)) + } + } else { + if (registry.parallelStream().anyMatch { it is MutableTagEntry && it.tag == tag }) { + fail { "Value with tag $tag already exists" } + return + } + + registry.add(MutableTagEntry(tag!!, mutableListOf(modifier), matter, complexity, priority ?: 0)) + } + } + } + } + + class DeleteAction : AbstractRegistryAction { + constructor( + tag: TagKey, + errorOnFailure: Boolean = false, + ) : super(tag = tag, errorOnFailure = errorOnFailure, matter = null, complexity = null) + + constructor( + key: ResourceLocation, + errorOnFailure: Boolean = false, + ) : super(key = key, errorOnFailure = errorOnFailure, matter = null, complexity = null) + + constructor(json: JsonObject) : super(json) + + override fun toJson(): JsonObject { + return super.toJson().also { + it["action"] = "delete" + } + } + + override fun update(registry: MutableCollection, modifier: ResourceLocation) { + if (!checkConditions()) { + return + } + + check(matter == null) { "Delete action can't have matter value" } + check(complexity == null) { "Delete action can't have complexity value" } + + if (key != null) { + val iterator = registry.iterator() + + for (value in iterator) { + if (value is MutableKeyEntry && value.key == key) { + iterator.remove() + return + } + } + + fail { "Could not find matter value with key $key" } + } else { + val iterator = registry.iterator() + + for (value in iterator) { + if (value is MutableTagEntry && value.tag == tag) { + iterator.remove() + return + } + } + + fail { "Could not find matter value with tag $tag" } + } + } + } + + class UpdateAction : AbstractRegistryAction { + enum class Function { + ADD { + override fun updateValue(self: Int, other: Int): Int = self + other + override fun updateValue(self: ImpreciseFraction, other: ImpreciseFraction): ImpreciseFraction = self + other + override fun updateValue(self: Double, other: Double): Double = self + other + }, + SUBTRACT { + override fun updateValue(self: Int, other: Int): Int = self - other + override fun updateValue(self: ImpreciseFraction, other: ImpreciseFraction): ImpreciseFraction = self - other + override fun updateValue(self: Double, other: Double): Double = self - other + }, + MULTIPLY { + override fun updateValue(self: Int, other: Int): Int = self * other + override fun updateValue(self: ImpreciseFraction, other: ImpreciseFraction): ImpreciseFraction = self * other + override fun updateValue(self: Double, other: Double): Double = self * other + }, + DIVIDE { + override fun updateValue(self: Int, other: Int): Int = self / other + override fun updateValue(self: ImpreciseFraction, other: ImpreciseFraction): ImpreciseFraction = self / other + override fun updateValue(self: Double, other: Double): Double = self / other + }, + DIVIDE_UP { + override fun updateValue(self: Int, other: Int): Int = integerDivisionUp(self, other) + override fun updateValue(self: ImpreciseFraction, other: ImpreciseFraction): ImpreciseFraction = throw JsonParseException("Integer division up is available only for integers") + override fun updateValue(self: Double, other: Double): Double = throw JsonParseException("Integer division up is available only for integers") + }, + DIVIDE_DOWN { + override fun updateValue(self: Int, other: Int): Int = integerDivisionDown(self, other) + override fun updateValue(self: ImpreciseFraction, other: ImpreciseFraction): ImpreciseFraction = throw JsonParseException("Integer division down is available only for integers") + override fun updateValue(self: Double, other: Double): Double = throw JsonParseException("Integer division down is available only for integers") + }, + MODULO { + override fun updateValue(self: Int, other: Int): Int = self % other + override fun updateValue(self: ImpreciseFraction, other: ImpreciseFraction): ImpreciseFraction = self % other + override fun updateValue(self: Double, other: Double): Double = self % other + }, + AT_LEAST { + override fun updateValue(self: Int, other: Int): Int = self.coerceAtLeast(other) + override fun updateValue(self: ImpreciseFraction, other: ImpreciseFraction): ImpreciseFraction = self.coerceAtLeast(other) + override fun updateValue(self: Double, other: Double): Double = self.coerceAtLeast(other) + }, + AT_MOST { + override fun updateValue(self: Int, other: Int): Int = self.coerceAtMost(other) + override fun updateValue(self: ImpreciseFraction, other: ImpreciseFraction): ImpreciseFraction = self.coerceAtMost(other) + override fun updateValue(self: Double, other: Double): Double = self.coerceAtMost(other) + }, + ; + + protected abstract fun updateValue(self: Int, other: Int): Int + protected abstract fun updateValue(self: ImpreciseFraction, other: ImpreciseFraction): ImpreciseFraction + protected abstract fun updateValue(self: Double, other: Double): Double + + fun updateValue(self: T, other: T): T { + return when (self) { + is ImpreciseFraction -> updateValue(self, other) + is Double -> updateValue(self, other) + is Int -> updateValue(self, other) + else -> throw RuntimeException("Unknown number type: ${self::class.qualifiedName}") + } + } + } + + class BoundFunction(val function: Function, val value: T) { + fun apply(self: T): T { + return function.updateValue(self, value) + } + + fun toJson(): JsonObject { + return JsonObject().also { + it["type"] = function.name + + if (value is Int || value is Double) + it["value"] = value + else + it["value"] = value.toString() + } + } + } + + companion object { + inline fun getValue(value: JsonElement): T { + return when (T::class) { + ImpreciseFraction::class -> ImpreciseFraction(value.asString) as T + Double::class -> value.asDouble as T + Int::class -> value.asInt as T + else -> throw RuntimeException("Unknown number type: ${T::class.qualifiedName}") + } + } + + inline fun deserializeFunctionTree(input: JsonElement?, name: String): List> { + if (input == null) { + return listOf() + } + + if (input !is JsonArray) { + throw JsonParseException("Expected $name to be JsonArray, ${input::class.qualifiedName} given") + } + + return input.stream().map { + if (it !is JsonObject) { + throw JsonParseException("All elements in function tree must be JsonObjects, ${it::class.qualifiedName} given") + } + + val type = it["type"]?.asString ?: throw JsonParseException("Invalid `type` value in function object") + val value = it["value"] ?: throw JsonParseException("Missing `value` value in function object") + + val parsedValue: T = getValue(value) + val fn: Function + + try { + fn = Function.valueOf(type.uppercase()) + } catch (err: NoSuchElementException) { + throw JsonParseException("No such function: $type", err) + } + + return@map BoundFunction(fn, parsedValue) + }.collect(ImmutableList.toImmutableList()) + } + + fun apply(value: T, funcs: Collection>): T { + if (funcs.isEmpty()) { + return value + } + + var newValue = value + + for (func in funcs) { + newValue = func.apply(newValue) + } + + return newValue + } + } + + val matterFunctions: List> + val complexityFunctions: List> + val priorityFunctions: List> + + constructor(json: JsonObject) : super(json) { + matterFunctions = deserializeFunctionTree(json["matter_functions"], "matter_functions") + complexityFunctions = deserializeFunctionTree(json["complexity_functions"], "complexity_functions") + priorityFunctions = deserializeFunctionTree(json["priority_functions"], "priority_functions") + } + + constructor( + tag: TagKey, + matter: ImpreciseFraction? = null, + complexity: Double? = null, + priority: Int? = null, + errorOnFailure: Boolean = false, + matterFunctions: List> = ImmutableList.of(), + complexityFunctions: List> = ImmutableList.of(), + priorityFunctions: List> = ImmutableList.of(), + ) : super(tag = tag, matter = matter, complexity = complexity, priority = priority, errorOnFailure = errorOnFailure) { + check(matter != null || complexity != null || priority != null || matterFunctions.isNotEmpty() || complexityFunctions.isNotEmpty() || priorityFunctions.isNotEmpty()) { + "Can't update existing entries without anything specified" + } + + this.matterFunctions = ImmutableList.copyOf(matterFunctions) + this.complexityFunctions = ImmutableList.copyOf(complexityFunctions) + this.priorityFunctions = ImmutableList.copyOf(priorityFunctions) + } + + constructor( + key: ResourceLocation, + matter: ImpreciseFraction? = null, + complexity: Double? = null, + priority: Int? = null, + errorOnFailure: Boolean = false, + matterFunctions: List> = ImmutableList.of(), + complexityFunctions: List> = ImmutableList.of(), + priorityFunctions: List> = ImmutableList.of(), + ) : super(key = key, matter = matter, complexity = complexity, priority = priority, errorOnFailure = errorOnFailure) { + check(matter != null || complexity != null || priority != null || matterFunctions.isNotEmpty() || complexityFunctions.isNotEmpty() || priorityFunctions.isNotEmpty()) { + "Can't update existing entries without anything specified" + } + + this.matterFunctions = ImmutableList.copyOf(matterFunctions) + this.complexityFunctions = ImmutableList.copyOf(complexityFunctions) + this.priorityFunctions = ImmutableList.copyOf(priorityFunctions) + } + + override fun toJson(): JsonObject { + return super.toJson().also { + it["action"] = "update" + + if (matterFunctions.isNotEmpty()) { + it["matter_functions"] = JsonArray().also { + for (fn in matterFunctions) { + it.add(fn.toJson()) + } + } + } + + if (complexityFunctions.isNotEmpty()) { + it["complexity_functions"] = JsonArray().also { + for (fn in complexityFunctions) { + it.add(fn.toJson()) + } + } + } + + if (priorityFunctions.isNotEmpty()) { + it["priority_functions"] = JsonArray().also { + for (fn in priorityFunctions) { + it.add(fn.toJson()) + } + } + } + } + } + + fun apply(value: MutableEntry, modifier: ResourceLocation) { + if (matterFunctions.isNotEmpty()) + value.matter = apply(value.matter, matterFunctions) + else if (matter != null) + value.matter = matter + + if (complexityFunctions.isNotEmpty()) + value.complexity = apply(value.complexity, complexityFunctions) + else if (complexity != null) + value.complexity = complexity + + if (priorityFunctions.isNotEmpty()) + value.priority = apply(value.priority, priorityFunctions) + else if (priority != null) + value.priority = priority + + value.modificationChain.add(modifier) + } + + override fun update(registry: MutableCollection, modifier: ResourceLocation) { + if(!( + matter != null || + complexity != null || + priority != null || + matterFunctions.isNotEmpty() || + complexityFunctions.isNotEmpty() || + priorityFunctions.isNotEmpty() + )) { + throw JsonParseException("Can't update existing entries without anything specified") + } + + if (matter != null && matterFunctions.isNotEmpty()) { + throw JsonParseException("Can't have both matter and matter functions specified at the same time") + } + + if (complexity != null && complexityFunctions.isNotEmpty()) { + throw JsonParseException("Can't have both complexity and complexity functions specified at the same time") + } + + if (priority != null && priorityFunctions.isNotEmpty()) { + throw JsonParseException("Can't have both priority and priority functions specified at the same time") + } + + if (!checkConditions()) { + return + } + + if (key != null) { + val iterator = registry.iterator() + + for (value in iterator) { + if (value is MutableKeyEntry && value.key == key) { + apply(value, modifier) + return + } + } + + fail { "Could not find matter value with key $key" } + } else { + val iterator = registry.iterator() + + for (value in iterator) { + if (value is MutableTagEntry && value.tag == tag) { + apply(value, modifier) + return + } + } + + fail { "Could not find matter value with tag $tag" } + } + } + } + + sealed class MutableEntry( + val modificationChain: MutableList, + var matter: ImpreciseFraction, + var complexity: Double, + var priority: Int + ) { + abstract fun freeze(): Entry + } + + class MutableTagEntry( + val tag: TagKey, + modificationChain: MutableList, + matter: ImpreciseFraction, + complexity: Double, + priority: Int + ) : MutableEntry(modificationChain, matter, complexity, priority) { + override fun freeze(): Entry { + return TagEntry(tag, ImmutableList.copyOf(modificationChain), matter, complexity, priority) + } + } + + class MutableKeyEntry( + val key: ResourceLocation, + modificationChain: MutableList, + matter: ImpreciseFraction, + complexity: Double, + priority: Int + ) : MutableEntry(modificationChain, matter, complexity, priority) { + override fun freeze(): Entry { + return KeyEntry(key, ImmutableList.copyOf(modificationChain), matter, complexity, priority) + } + } + + interface IMatterValue { + val matter: ImpreciseFraction + val complexity: Double + + val hasMatterValue: Boolean get() = matter.isPositive && complexity > 0.0 + } + + sealed class Entry( + val modificationChain: List, + final override val matter: ImpreciseFraction, + final override val complexity: Double, + val priority: Int, + ) : IMatterValue { + companion object : Entry(listOf(), ImpreciseFraction.ZERO, 0.0, Int.MIN_VALUE) + } + + class TagEntry( + val tag: TagKey, + modificationChain: List, + matter: ImpreciseFraction, + complexity: Double, + priority: Int, + ) : Entry(modificationChain, matter, complexity, priority) { + val bound by lazy { + val manager = ForgeRegistries.ITEMS.tags() ?: throw NullPointerException("Forge registry of Items has no tags!") + manager.getTag(tag) + } + } + + class KeyEntry( + val key: ResourceLocation, + modificationChain: List, + matter: ImpreciseFraction, + complexity: Double, + priority: Int, + ) : Entry(modificationChain, matter, complexity, priority) + + private var canCompute = false + private val keyEntries = HashMap() + private val tagEntries = ArrayList() + private val computedEntries = Reference2ObjectOpenHashMap() + + operator fun get(value: Item): Entry { + check(canCompute) { "Can't compute matter values yet, datapack wasn't loaded or is in progress of loading!" } + + synchronized(computedEntries) { + return computedEntries.computeIfAbsent(value, Reference2ObjectFunction { + it as Item + val key = it.registryName ?: throw NullPointerException("$it has no registry name!") + + val keyEntry = keyEntries[key] + + if (keyEntry != null) { + return@Reference2ObjectFunction keyEntry + } + + for (entry in tagEntries) { + if (entry.bound.contains(it)) { + return@Reference2ObjectFunction entry + } + } + + return@Reference2ObjectFunction Entry.Companion + }) + } + } + + fun reloadEvent(event: AddReloadListenerEvent) { + event.addListener(this) + } + + private val LOGGER = LogManager.getLogger() + + override fun prepare( + resourceManager: ResourceManager, + profilerFiller: ProfilerFiller + ): MutableMap { + val time = System.nanoTime() + LOGGER.info("Beginning preparations for loading matter values") + profilerFiller.push("otm_matter_registry_prepare") + + try { + val prep = super.prepare(resourceManager, profilerFiller) + val diff = System.nanoTime() - time + LOGGER.info("Finished preparations for loading matter values, took ${diff / 1_000_000L}ms") + return prep + } finally { + profilerFiller.pop() + } + } + + override fun apply( + map: MutableMap, + resourceManager: ResourceManager, + profilerFiller: ProfilerFiller + ) { + val time = System.nanoTime() + LOGGER.info("Beginning loading matter values") + profilerFiller.push("otm_matter_registry") + + try { + canCompute = false + + keyEntries.clear() + tagEntries.clear() + computedEntries.clear() + + val add = ArrayList>() + val addOrReplace = ArrayList>() + val update = ArrayList>() + val delete = ArrayList>() + + for ((key, json) in map) { + if (json !is JsonObject) { + throw JsonParseException("Matter value $key has invalid type: ${json::class.qualifiedName} ($json)") + } + + when (val action = json["action"]?.asString?.lowercase() ?: throw JsonParseException("Missing `action` json element in $key. Possible values are: (INSERT, DELETE and UPDATE (case-insensitive))")) { + "insert" -> { + val value = InsertAction(json) + + if (value.replaceIfExists) { + addOrReplace.add(value to key) + } else { + add.add(value to key) + } + } + + "update" -> update.add(UpdateAction(json) to key) + "delete" -> delete.add(DeleteAction(json) to key) + else -> throw JsonParseException("Unknown action $action of $key") + } + } + + val registry = ArrayList() + + for (action in add) + action.first.update(registry, action.second) + + for (action in addOrReplace) + action.first.update(registry, action.second) + + for (action in delete) + action.first.update(registry, action.second) + + for (action in update) + action.first.update(registry, action.second) + + val tags = HashMap, MutableTagEntry>() + val keys = HashMap() + + for (entry in registry) { + if (entry is MutableTagEntry) { + val existing = tags[entry.tag] + + if (existing == null || existing.priority < entry.priority) { + tags[entry.tag] = entry + } + } else if (entry is MutableKeyEntry) { + val existing = keys[entry.key] + + if (existing == null || existing.priority < entry.priority) { + keys[entry.key] = entry + } + } + } + + for (key in keys.values) { + keyEntries[key.key] = key.freeze() as KeyEntry + } + + for (tag in tags.values) { + tagEntries.add(tag.freeze() as TagEntry) + } + + tagEntries.sortBy { it.priority } + + canCompute = true + } finally { + profilerFiller.pop() + } + + val diff = System.nanoTime() - time + LOGGER.info("Finished loading matter values, took ${diff / 1_000_000L}ms") + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/matter/MatterRegistry.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/matter/MatterRegistry.kt index a86db1b69..ef153e37a 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/matter/MatterRegistry.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/matter/MatterRegistry.kt @@ -131,14 +131,14 @@ fun getMatterValue(item: Item): MatterTuple { if (item is IMatterItem) return item.getMatterValue(ItemStack.EMPTY) ?: MatterTuple.ZERO - return rootEntries[item] ?: derivedEntries[item] ?: MatterTuple.ZERO + return MatterManager[item].let { MatterTuple(it.matter, it.complexity) } } fun getMatterValueNullable(item: Item): MatterTuple? { if (item is IMatterItem) return item.getMatterValue(ItemStack.EMPTY) - return rootEntries[item] ?: derivedEntries[item] + return MatterManager[item].let { MatterTuple(it.matter, it.complexity) } } fun hasMatterValue(item: Item): Boolean {