From 9592860349c433f6e2595d86f7c954a3e8f174cd Mon Sep 17 00:00:00 2001 From: DBotThePony Date: Sat, 12 Nov 2022 23:50:26 +0700 Subject: [PATCH] Un-tangle matter system --- .../mc/otm/OverdriveThatMatters.java | 6 +- .../entity/blackhole/BlackHoleBlockEntity.kt | 2 +- .../matter/MatterDecomposerBlockEntity.kt | 2 +- .../matter/MatterReplicatorBlockEntity.kt | 2 +- .../entity/matter/MatterScannerBlockEntity.kt | 2 +- .../kotlin/ru/dbotthepony/mc/otm/core/Ext.kt | 8 + .../mc/otm/item/CreativePatternItem.kt | 8 +- .../mc/otm/matter/AbstractRegistryAction.kt | 20 +- .../mc/otm/matter/ComputeAction.kt | 70 +- .../dbotthepony/mc/otm/matter/DeleteAction.kt | 6 +- .../dbotthepony/mc/otm/matter/InsertAction.kt | 18 +- .../mc/otm/matter/MatterDataProvider.kt | 2 +- .../mc/otm/matter/MatterManager.kt | 1239 ++++++++++++----- .../mc/otm/matter/RecipeResolverManager.kt | 606 -------- .../dbotthepony/mc/otm/matter/UpdateAction.kt | 8 +- .../blasting.json | 0 .../campfire_cooking.json | 0 .../crafting.json | 0 .../plate_press.json | 0 .../smelting.json | 0 .../smithing.json | 0 .../smoking.json | 0 .../stonecutting.json | 0 23 files changed, 971 insertions(+), 1028 deletions(-) rename src/main/resources/data/overdrive_that_matters/{otm_recipe_resolver => otm_recipe_finder}/blasting.json (100%) rename src/main/resources/data/overdrive_that_matters/{otm_recipe_resolver => otm_recipe_finder}/campfire_cooking.json (100%) rename src/main/resources/data/overdrive_that_matters/{otm_recipe_resolver => otm_recipe_finder}/crafting.json (100%) rename src/main/resources/data/overdrive_that_matters/{otm_recipe_resolver => otm_recipe_finder}/plate_press.json (100%) rename src/main/resources/data/overdrive_that_matters/{otm_recipe_resolver => otm_recipe_finder}/smelting.json (100%) rename src/main/resources/data/overdrive_that_matters/{otm_recipe_resolver => otm_recipe_finder}/smithing.json (100%) rename src/main/resources/data/overdrive_that_matters/{otm_recipe_resolver => otm_recipe_finder}/smoking.json (100%) rename src/main/resources/data/overdrive_that_matters/{otm_recipe_resolver => otm_recipe_finder}/stonecutting.json (100%) diff --git a/src/main/java/ru/dbotthepony/mc/otm/OverdriveThatMatters.java b/src/main/java/ru/dbotthepony/mc/otm/OverdriveThatMatters.java index 98b613624..2f9a9c7b3 100644 --- a/src/main/java/ru/dbotthepony/mc/otm/OverdriveThatMatters.java +++ b/src/main/java/ru/dbotthepony/mc/otm/OverdriveThatMatters.java @@ -42,7 +42,6 @@ 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.MatterManager; -import ru.dbotthepony.mc.otm.matter.RecipeResolverManager; import ru.dbotthepony.mc.otm.network.*; import ru.dbotthepony.mc.otm.registry.*; import ru.dbotthepony.mc.otm.storage.*; @@ -121,7 +120,7 @@ public final class OverdriveThatMatters { var modBus = FMLJavaModLoadingContext.get().getModEventBus(); MRegistry.INSTANCE.initialize(modBus); - RecipeResolverManager.INSTANCE.initialize(modBus); + MatterManager.INSTANCE.initialize(modBus); modBus.addListener(EventPriority.HIGHEST, this::setup); modBus.addListener(EventPriority.NORMAL, this::setupClient); @@ -171,8 +170,7 @@ public final class OverdriveThatMatters { EVENT_BUS.addListener(EventPriority.NORMAL, AndroidResearchManager.INSTANCE::syncEvent); EVENT_BUS.addListener(EventPriority.NORMAL, MatterManager.INSTANCE::reloadEvent); - EVENT_BUS.addListener(EventPriority.NORMAL, RecipeResolverManager.INSTANCE::reloadEvent); - EVENT_BUS.addListener(EventPriority.NORMAL, RecipeResolverManager.INSTANCE::onServerStarted); + EVENT_BUS.addListener(EventPriority.NORMAL, MatterManager.INSTANCE::onServerStarted); EVENT_BUS.addListener(EventPriority.NORMAL, SynchronizedBlockEntity.Companion::onServerStopping); EVENT_BUS.addListener(EventPriority.NORMAL, SynchronizedBlockEntity.Companion::onLevelUnload); diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/blackhole/BlackHoleBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/blackhole/BlackHoleBlockEntity.kt index e5540acc3..1a71fd899 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/blackhole/BlackHoleBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/blackhole/BlackHoleBlockEntity.kt @@ -251,7 +251,7 @@ class BlackHoleBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : Synchro if (item.item.item === MItems.GRAVITATIONAL_DISRUPTOR) { collapse() } else { - val mass = MatterManager.getValue(item.item) + val mass = MatterManager.get(item.item) if (mass.hasMatterValue) this.mass += mass.matter diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterDecomposerBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterDecomposerBlockEntity.kt index 1c8afac12..2ed35b77b 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterDecomposerBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterDecomposerBlockEntity.kt @@ -220,7 +220,7 @@ class MatterDecomposerBlockEntity(pos: BlockPos, state: BlockState) copy.count = 1 if (MatterManager.canDecompose(copy)) { - val matter = MatterManager.getValue(copy) + val matter = MatterManager.get(copy) stack.count-- return DecomposerJob((level?.random?.nextDouble() ?: 1.0) <= 0.2, matter.matter, matter.complexity) to null diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterReplicatorBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterReplicatorBlockEntity.kt index 46feb0d7d..9e1a694bd 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterReplicatorBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterReplicatorBlockEntity.kt @@ -194,7 +194,7 @@ class MatterReplicatorBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : val graph = matterNode.graph as MatterNetworkGraph? ?: return null to null val allocation = graph.allocateTask(simulate = false) ?: return null to IdleReason.OBSERVING val stack = allocation.task.stack(1) - val matter = MatterManager.getValue(stack) + val matter = MatterManager.get(stack) // ???????? if (!matter.hasMatterValue) return null to null diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterScannerBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterScannerBlockEntity.kt index 47ae6d003..be94e36a1 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterScannerBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterScannerBlockEntity.kt @@ -198,7 +198,7 @@ class MatterScannerBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : val copy = stack.copy().also { it.count = 1 } stack.shrink(1) container.setChanged() - val complexity = MatterManager.getValue(copy).complexity + val complexity = MatterManager.get(copy).complexity return ItemJob(copy, (if (complexity > 1.0) complexity.pow(2.0) else complexity.pow(0.5)), BASE_CONSUMPTION) to null } 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 4f84cd45c..e4b002a44 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/Ext.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/Ext.kt @@ -420,3 +420,11 @@ fun Collection.probablyParallelStream(): Stream { } fun Array.stream(): Stream = Arrays.stream(this) + +fun Stream.filterNotNull(): Stream { + return filter { it != null } as Stream +} + +inline fun Stream<*>.filterIsInstance(): Stream { + return filter { it is T } as Stream +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/item/CreativePatternItem.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/item/CreativePatternItem.kt index f2f9ec76c..7e4c62548 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/item/CreativePatternItem.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/item/CreativePatternItem.kt @@ -31,18 +31,16 @@ class CreativePatternItem : Item(Properties().rarity(Rarity.EPIC).tab(OverdriveT private val resolver = LazyOptional.of { this } override val patterns: Stream - get() = MatterManager.resolveAndStream().map { PatternState(UUID(34783464838L, 4463458382L + ForgeRegistries.ITEMS.getID(it.key)), it.key, 1.0) } + get() = MatterManager.valuesList.stream().map { PatternState(UUID(34783464838L, 4463458382L + ForgeRegistries.ITEMS.getID(it.first)), it.first, 1.0) } override val patternCapacity: Int get() { - MatterManager.resolveEverything() - return MatterManager.directlyComputedEntriesSize + return MatterManager.valuesList.size } override val storedPatterns: Int get() { - MatterManager.resolveEverything() - return MatterManager.directlyComputedEntriesSize + return MatterManager.valuesList.size } override fun insertPattern( diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/matter/AbstractRegistryAction.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/matter/AbstractRegistryAction.kt index cd40b80dd..d7b95ff3b 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/matter/AbstractRegistryAction.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/matter/AbstractRegistryAction.kt @@ -11,7 +11,7 @@ import net.minecraft.world.item.Item import ru.dbotthepony.mc.otm.core.ImpreciseFraction import ru.dbotthepony.mc.otm.core.set -sealed class AbstractRegistryAction { +sealed class AbstractRegistryAction : Comparable { sealed class Condition { abstract fun test(): Boolean } @@ -120,7 +120,7 @@ sealed class AbstractRegistryAction { return true } - abstract fun update(registry: MutableCollection, modifier: ResourceLocation) + internal abstract fun update(registry: MutableCollection, modifier: ResourceLocation) protected inline fun fail(reason: () -> String) { if (errorOnFailure) { @@ -138,6 +138,22 @@ sealed class AbstractRegistryAction { return other.priority == null } + override fun compareTo(other: AbstractRegistryAction): Int { + if (other.priority == null) { + if (priority == null) { + return 0 + } + + return 1 + } + + if (priority == null) { + return -1 + } + + return priority.compareTo(other.priority) + } + open fun toJson(): JsonObject { return JsonObject().also { if (key != null) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/matter/ComputeAction.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/matter/ComputeAction.kt index 23d70f222..9e25a4895 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/matter/ComputeAction.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/matter/ComputeAction.kt @@ -54,8 +54,7 @@ class ComputeAction : AbstractRegistryAction { } } - abstract val matter: ImpreciseFraction? - abstract val complexity: Double? + internal abstract fun provideValues(): MatterManager.Result open fun toJson(): JsonObject { return JsonObject().also { @@ -64,20 +63,23 @@ class ComputeAction : AbstractRegistryAction { } } - fun update(self: IMatterValue): IMatterValue? { - val matter = matter ?: return null - val complexity = complexity ?: return null + internal fun update(self: IMatterValue): MatterManager.Result { + val grab = provideValues() - return MatterValue( - matterFunction.updateValue(self.matter, matter), - complexityFunction.updateValue(self.complexity, complexity) - ) + if (grab.value == null) { + return grab + } + + return MatterManager.Result(MatterValue( + matterFunction.updateValue(self.matter, grab.value.matter), + complexityFunction.updateValue(self.complexity, grab.value.complexity) + )) } } class Constant : Source { - override val matter: ImpreciseFraction - override val complexity: Double + val matter: ImpreciseFraction + val complexity: Double constructor( matter: ImpreciseFraction, @@ -103,6 +105,10 @@ class ComputeAction : AbstractRegistryAction { } } + override fun provideValues(): MatterManager.Result { + return MatterManager.Result(MatterValue(matter, complexity)) + } + override fun toJson(): JsonObject { return super.toJson().also { it["type"] = "constant" @@ -135,18 +141,10 @@ class ComputeAction : AbstractRegistryAction { key = ResourceLocation.tryParse(json["key"]?.asString ?: throw JsonSyntaxException("Missing key")) ?: throw JsonSyntaxException("Invalid key value: ${json["key"]}") } - // sometimes kotlin compiler is dumb - private fun key() = key - - private val matterTuple by lazy { - MatterManager.getValue(ForgeRegistries.ITEMS.getValue(key()) ?: throw NullPointerException("${key()} does not point to anything")) + override fun provideValues(): MatterManager.Result { + return MatterManager.compute(ForgeRegistries.ITEMS.getValue(key) ?: throw NullPointerException("$key does not point to anything")) } - override val matter: ImpreciseFraction - get() = matterTuple.matter - override val complexity: Double - get() = matterTuple.complexity - override fun toJson(): JsonObject { return super.toJson().also { it["type"] = "key" @@ -170,18 +168,10 @@ class ComputeAction : AbstractRegistryAction { tag = ItemTags.create(ResourceLocation.tryParse(json["tag"]?.asString ?: throw JsonSyntaxException("Missing tag")) ?: throw JsonSyntaxException("Invalid tag value: ${json["tag"]}")) } - // sometimes kotlin compiler is dumb - private fun tag() = tag - - private val matterTuple by lazy { - MatterManager.searchTagEntry(tag()) + override fun provideValues(): MatterManager.Result { + return MatterManager.compute(tag) } - override val matter: ImpreciseFraction? - get() = matterTuple?.matter - override val complexity: Double? - get() = matterTuple?.complexity - override fun toJson(): JsonObject { return super.toJson().also { it["type"] = "tag" @@ -238,16 +228,20 @@ class ComputeAction : AbstractRegistryAction { values = ImmutableList.copyOf(value) } - private fun values() = values - - val computed: IMatterValue? by lazy { + internal fun tryCompute(): MatterManager.Result { var compute: IMatterValue = IMatterValue.Companion - for (fn in values()) { - compute = fn.update(compute) ?: return@lazy null + for (fn in values) { + val result = fn.update(compute) + + if (result.value == null) { + return result + } else { + compute = result.value + } } - compute + return MatterManager.Result(compute) } constructor(json: JsonObject) : super(json) { @@ -280,7 +274,7 @@ class ComputeAction : AbstractRegistryAction { } } - override fun update(registry: MutableCollection, modifier: ResourceLocation) { + override fun update(registry: MutableCollection, modifier: ResourceLocation) { // do nothing } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/matter/DeleteAction.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/matter/DeleteAction.kt index 612d0fe27..4a9a06d57 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/matter/DeleteAction.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/matter/DeleteAction.kt @@ -25,7 +25,7 @@ class DeleteAction : AbstractRegistryAction { } } - override fun update(registry: MutableCollection, modifier: ResourceLocation) { + override fun update(registry: MutableCollection, modifier: ResourceLocation) { if (!checkConditions()) { return } @@ -37,7 +37,7 @@ class DeleteAction : AbstractRegistryAction { val iterator = registry.iterator() for (value in iterator) { - if (value is MatterManager.MutableKeyEntry && value.key == key) { + if (value is MutableKeyEntry && value.key == key) { iterator.remove() return } @@ -48,7 +48,7 @@ class DeleteAction : AbstractRegistryAction { val iterator = registry.iterator() for (value in iterator) { - if (value is MatterManager.MutableTagEntry && value.tag == tag) { + if (value is MutableTagEntry && value.tag == tag) { iterator.remove() return } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/matter/InsertAction.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/matter/InsertAction.kt index 1311d6102..e84a08ab3 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/matter/InsertAction.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/matter/InsertAction.kt @@ -54,7 +54,7 @@ class InsertAction : AbstractRegistryAction { } } - override fun update(registry: MutableCollection, modifier: ResourceLocation) { + override fun update(registry: MutableCollection, modifier: ResourceLocation) { checkNotNull(matter) { "Missing matter value, which is required for insert action (for $modifier)" } checkNotNull(complexity) { "Missing complexity value, which is required for insert action (for $modifier)" } @@ -68,7 +68,7 @@ class InsertAction : AbstractRegistryAction { if (replaceIfExists) { // search & replace in parallel, if possible val replaced = registry.probablyParallelStream().anyMatch { - if (it is MatterManager.MutableKeyEntry && it.key == key) { + if (it is MutableKeyEntry && it.key == key) { if (!comparePriority || it.priority < priority!!) { it.matter = matter it.complexity = complexity @@ -85,7 +85,7 @@ class InsertAction : AbstractRegistryAction { if (!replaced) { registry.add( - MatterManager.MutableKeyEntry( + MutableKeyEntry( key, mutableListOf(modifier), matter, @@ -95,13 +95,13 @@ class InsertAction : AbstractRegistryAction { ) } } else { - if (registry.probablyParallelStream().anyMatch { it is MatterManager.MutableKeyEntry && it.key == key }) { + if (registry.probablyParallelStream().anyMatch { it is MutableKeyEntry && it.key == key }) { fail { "Value with key $key already exists" } return } registry.add( - MatterManager.MutableKeyEntry( + MutableKeyEntry( key, mutableListOf(modifier), matter, @@ -114,7 +114,7 @@ class InsertAction : AbstractRegistryAction { if (replaceIfExists) { // search & replace in parallel, if possible val replaced = registry.probablyParallelStream().anyMatch { - if (it is MatterManager.MutableTagEntry && it.tag == tag) { + if (it is MutableTagEntry && it.tag == tag) { if (!comparePriority || it.priority < priority!!) { it.matter = matter it.complexity = complexity @@ -131,7 +131,7 @@ class InsertAction : AbstractRegistryAction { if (!replaced) { registry.add( - MatterManager.MutableTagEntry( + MutableTagEntry( tag!!, mutableListOf(modifier), matter, @@ -141,13 +141,13 @@ class InsertAction : AbstractRegistryAction { ) } } else { - if (registry.probablyParallelStream().anyMatch { it is MatterManager.MutableTagEntry && it.tag == tag }) { + if (registry.probablyParallelStream().anyMatch { it is MutableTagEntry && it.tag == tag }) { fail { "Value with tag $tag already exists" } return } registry.add( - MatterManager.MutableTagEntry( + MutableTagEntry( tag!!, mutableListOf(modifier), matter, diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/matter/MatterDataProvider.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/matter/MatterDataProvider.kt index 927bf71c7..b29a8467f 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/matter/MatterDataProvider.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/matter/MatterDataProvider.kt @@ -17,7 +17,7 @@ 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 pathProvider: DataGenerator.PathProvider = dataGenerator.createPathProvider(DataGenerator.Target.DATA_PACK, MatterManager.MATTER_DIRECTORY) protected val actions = LinkedHashMap() sealed class Configuration(val name: ResourceLocation) { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/matter/MatterManager.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/matter/MatterManager.kt index 374534a9f..496a60c48 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/matter/MatterManager.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/matter/MatterManager.kt @@ -1,6 +1,8 @@ package ru.dbotthepony.mc.otm.matter import com.google.common.collect.ImmutableList +import com.google.common.collect.ImmutableMap +import com.google.common.collect.Streams import com.google.gson.GsonBuilder import com.google.gson.JsonElement import com.google.gson.JsonObject @@ -9,17 +11,28 @@ import com.google.gson.JsonSyntaxException import com.mojang.blaze3d.platform.InputConstants import it.unimi.dsi.fastutil.objects.Reference2ObjectFunction import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap +import it.unimi.dsi.fastutil.objects.ReferenceLinkedOpenHashSet +import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet import net.minecraft.ChatFormatting +import net.minecraft.network.chat.Component +import net.minecraft.network.chat.MutableComponent import net.minecraft.resources.ResourceLocation +import net.minecraft.server.MinecraftServer import net.minecraft.server.packs.resources.ResourceManager import net.minecraft.server.packs.resources.SimpleJsonResourceReloadListener import net.minecraft.tags.TagKey import net.minecraft.util.profiling.ProfilerFiller +import net.minecraft.world.Container import net.minecraft.world.item.Item import net.minecraft.world.item.ItemStack +import net.minecraft.world.item.crafting.Recipe +import net.minecraft.world.item.crafting.RecipeType import net.minecraft.world.level.ItemLike import net.minecraftforge.event.AddReloadListenerEvent import net.minecraftforge.event.entity.player.ItemTooltipEvent +import net.minecraftforge.event.server.ServerStartedEvent +import net.minecraftforge.eventbus.api.IEventBus +import net.minecraftforge.registries.DeferredRegister import net.minecraftforge.registries.ForgeRegistries import org.apache.logging.log4j.LogManager import org.lwjgl.glfw.GLFW @@ -29,260 +42,807 @@ import ru.dbotthepony.mc.otm.capability.MatteryCapability import ru.dbotthepony.mc.otm.capability.drive.IMatteryDrive import ru.dbotthepony.mc.otm.client.minecraft import ru.dbotthepony.mc.otm.core.ImpreciseFraction +import ru.dbotthepony.mc.otm.core.TextComponent import ru.dbotthepony.mc.otm.core.TranslatableComponent +import ru.dbotthepony.mc.otm.core.filterNotNull import ru.dbotthepony.mc.otm.core.formatMatterFull import ru.dbotthepony.mc.otm.core.formatSiComponent +import ru.dbotthepony.mc.otm.core.isActuallyEmpty import ru.dbotthepony.mc.otm.core.isZero import ru.dbotthepony.mc.otm.core.orNull import ru.dbotthepony.mc.otm.core.registryName +import ru.dbotthepony.mc.otm.core.stream +import ru.dbotthepony.mc.otm.registry.RegistryDelegate import ru.dbotthepony.mc.otm.storage.ItemStackWrapper import java.math.BigInteger -import java.util.Collections +import java.util.* import java.util.stream.Stream +import kotlin.ConcurrentModificationException +import kotlin.collections.ArrayDeque +import kotlin.collections.ArrayList +import kotlin.collections.HashMap +import kotlin.collections.LinkedHashMap import kotlin.math.pow -object MatterManager : SimpleJsonResourceReloadListener(GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create(), "otm_matter") { - const val DIRECTORY = "otm_matter" +internal sealed class MutableEntry( + val modificationChain: MutableList, + var matter: ImpreciseFraction, + var complexity: Double, + var priority: Int +) { + abstract fun freeze(): Entry +} - sealed class MutableEntry( - val modificationChain: MutableList, - var matter: ImpreciseFraction, - var complexity: Double, - var priority: Int - ) { - abstract fun freeze(): Entry +internal 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 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) +internal class MutableKeyEntry( + val key: ResourceLocation, + modificationChain: MutableList, + matter: ImpreciseFraction, + complexity: Double, + priority: Int +) : MutableEntry(modificationChain, matter, complexity, priority) { + init { + if (key == AIR) { + throw JsonSyntaxException("you wot, can't modify $key") } } - class MutableKeyEntry( - val key: ResourceLocation, - modificationChain: MutableList, - matter: ImpreciseFraction, - complexity: Double, - priority: Int - ) : MutableEntry(modificationChain, matter, complexity, priority) { - init { - if (key == AIR) { - throw JsonSyntaxException("you wot, can't modify $key") + override fun freeze(): Entry { + return KeyEntry(key, ImmutableList.copyOf(modificationChain), matter, complexity, priority) + } + + companion object { + private val AIR = ResourceLocation("minecraft", "air") + } +} + +internal 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) +} + +internal 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) + } +} + +internal class KeyEntry( + val key: ResourceLocation, + modificationChain: List, + matter: ImpreciseFraction, + complexity: Double, + priority: Int, +) : Entry(modificationChain, matter, complexity, priority) + +object MatterManager { + const val MATTER_DIRECTORY = "otm_matter" + const val FINDER_DIRECTORY = "otm_recipe_finder" + private val LOGGER = LogManager.getLogger() + + private object Registry : SimpleJsonResourceReloadListener(GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create(), MATTER_DIRECTORY) { + val keyEntries = HashMap() + val tagEntries = LinkedHashMap, TagEntry>() + + val computeKeys = HashMap() + val computeTags = HashMap, ComputeAction>() + + fun direct(value: Item): IMatterValue { + val key = value.registryName ?: throw NullPointerException("$value has no registry name!") + + val keyEntry = keyEntries[key] + + if (keyEntry != null) { + return keyEntry } + + val reverse = ForgeRegistries.ITEMS.tags()!!.getReverseTag(value).orElse(null) + + if (reverse != null) { + return reverse.tagKeys + .map { tagEntries[it] } + .filterNotNull() + .sorted { o1, o2 -> o1.priority.compareTo(o2.priority) } + .findFirst() + .orElse(null) ?: IMatterValue.Companion + } + + return Entry.Companion } - override fun freeze(): Entry { - return KeyEntry(key, ImmutableList.copyOf(modificationChain), matter, complexity, priority) - } + fun indirect(value: Item): Result { + var compute = computeKeys[value.registryName!!] - companion object { - private val AIR = ResourceLocation("minecraft", "air") - } - } + if (compute == null) { + val reverse = ForgeRegistries.ITEMS.tags()!!.getReverseTag(value).orElse(null) - 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) - - var canCompute = false - private set - - private val keyEntries = HashMap() - private val tagEntries = ArrayList() - private val directlyComputedEntries = Reference2ObjectOpenHashMap() - - // only writes are synchronized, because this structure is utilized solely as cache - // so it is completely safe to not synchronize when doing reads - private val computedEntries = Reference2ObjectOpenHashMap() - private val synchronizedComputedEntries: MutableMap = Collections.synchronizedMap(computedEntries) - - private val computeKeys = HashMap() - private val computeTags = HashMap, ComputeAction>() - val directlyComputedEntriesView: Map = Collections.unmodifiableMap(directlyComputedEntries) - val computedEntriesView: Map = Collections.unmodifiableMap(computedEntries) - - @JvmStatic - fun searchKeyEntry(index: ResourceLocation): KeyEntry? { - return keyEntries[index] - } - - @JvmStatic - fun searchTagEntry(index: ResourceLocation): TagEntry? { - return tagEntries.firstOrNull { it.tag.location == index } - } - - @JvmStatic - fun searchTagEntry(index: TagKey): TagEntry? { - return tagEntries.firstOrNull { it.tag == index } - } - - /** - * Returns directly defined matter value - */ - @JvmStatic - fun getDirect(value: Item): Entry { - check(canCompute) { "MatteryManager is not ready to compute matter values" } - - val get = directlyComputedEntries.get(value) - - if (get != null) { - return get - } - - synchronized(directlyComputedEntries) { - return directlyComputedEntries.computeIfAbsent(value, Reference2ObjectFunction lazy@{ - val key = value.registryName ?: throw NullPointerException("$value has no registry name!") - - val keyEntry = keyEntries[key] - - if (keyEntry != null) { - return@lazy keyEntry - } - - for (entry in tagEntries) { - if (entry.bound.contains(value)) { - return@lazy entry - } - } - - return@lazy Entry.Companion - }) - } - } - - @JvmStatic - val directlyComputedEntriesSize: Int get() = directlyComputedEntries.size - - @JvmStatic - val computedEntriesSize: Int get() = computedEntries.size - - @JvmStatic - fun stream(): Stream> { - return computedEntriesView.entries.stream().filter { it.value.hasMatterValue } - } - - private var resolvedEverything = false - - /** - * Iterates through [ForgeRegistries.ITEMS] if it hadn't already and resolves everything - * - * THIS IS A VERY SLOW OPERATION TO PERFORM! - */ - @JvmStatic - fun resolveEverything() { - if (resolvedEverything) { - return - } - - val time = SystemTime() - LOGGER.info("resolveEverything() was called") - - for (value in ForgeRegistries.ITEMS.values) { - getValue(value) - } - - resolvedEverything = true - LOGGER.info("resolveEverything() finished in ${time.millis}ms") - } - - /** - * Iterates through [ForgeRegistries.ITEMS] if it hadn't already and resolves everything, - * then returns stream of entries - * - * THIS IS A VERY SLOW OPERATION TO PERFORM! - */ - @JvmStatic - fun resolveAndStream(): Stream> { - resolveEverything() - return stream() - } - - @JvmStatic - fun getValue(value: Item): IMatterValue { - val existing = computedEntries[value] - - if (existing != null) { - return existing - } - - val direct = getDirect(value) - - if (direct.hasMatterValue) { - synchronizedComputedEntries[value] = direct - return direct - } - - val matter = RecipeResolverManager.getDetermined(value) - - if (matter.hasMatterValue) { - synchronizedComputedEntries[value] = matter - return matter - } - - var compute = computeKeys[value.registryName!!] - - if (compute == null) { - compute = ForgeRegistries.ITEMS.tags()!! - .getReverseTag(value) - .map { - it.tagKeys + if (reverse != null) { + compute = reverse.tagKeys .map { computeTags[it] } - .filter { it != null } + .filterNotNull() + .sorted() .findFirst() .orElse(null) } - .orElse(null) + } + + if (compute != null) { + return compute.tryCompute() + } + + return Result.MISSING } - if (compute != null) { - val entry = compute.computed ?: Entry.Companion - synchronizedComputedEntries[value] = entry - return entry + override fun prepare(resourceManager: ResourceManager, profilerFiller: ProfilerFiller): MutableMap { + val time = System.nanoTime() + LOGGER.info("Slicing matter values...") + profilerFiller.push("otm_matter_registry_prepare") + + try { + val prep = super.prepare(resourceManager, profilerFiller) + val diff = System.nanoTime() - time + LOGGER.info("Slicing matter values took ${diff / 1_000_000L}ms") + return prep + } finally { + profilerFiller.pop() + } } - synchronizedComputedEntries[value] = Entry.Companion - return Entry.Companion + override fun apply(map: MutableMap, resourceManager: ResourceManager, profilerFiller: ProfilerFiller) { + val time = System.nanoTime() + LOGGER.info("Stitching matter values") + profilerFiller.push("otm_matter_registry") + + try { + keyEntries.clear() + tagEntries.clear() + + computeKeys.clear() + computeTags.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) + "compute" -> { + try { + ComputeAction(json).also { + if (it.key != null) { + computeKeys.compute(it.key) { _, existing -> if (it.canReplace(existing)) it else existing } + } else { + computeTags.compute(it.tag!!) { _, existing -> if (it.canReplace(existing)) it else existing } + } + } + } catch (err: Throwable) { + throw JsonSyntaxException("Processing $key failed", err) + } + } + 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 + } + + val tagEntries = ArrayList() + + for (tag in tags.values) { + tagEntries.add(tag.freeze() as TagEntry) + } + + tagEntries.sortBy { it.priority } + + for (entry in tagEntries) { + this.tagEntries[entry.tag] = entry + } + } finally { + profilerFiller.pop() + } + + val diff = System.nanoTime() - time + LOGGER.info("Stitching matter values took ${diff / 1_000_000L}ms") + } + } + + internal data class Result(val type: Type, val value: IMatterValue? = null) { + constructor(value: IMatterValue) : this(Type.RESOLVED, value) + + val isSkipped get() = type === Type.SKIPPED + val isMissing get() = type === Type.MISSING + + enum class Type { + RESOLVED, SKIPPED, MISSING + } + + companion object { + // recursive recipes should be ignored until we resolve everything else + val SKIPPED = Result(Type.SKIPPED) + + // missing matter values are fatal, whole recipe chain is then considered defunct + val MISSING = Result(Type.MISSING) + } + } + + private object Resolver : SimpleJsonResourceReloadListener(GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create(), FINDER_DIRECTORY) { + val delegate = RegistryDelegate("recipe_finder") { + disableSync() + disableSaving() + } + + val registrar: DeferredRegister = DeferredRegister.create(delegate.key, OverdriveThatMatters.MOD_ID) + + init { + registrar.register("simple") { + Finder { server, data -> + val location = (data["recipe_type"] ?: throw JsonSyntaxException("Missing recipe type")).let { ResourceLocation.tryParse(it.asString) } ?: throw JsonSyntaxException("Invalid recipe type: ${data["recipe_type"]}") + + if (!ForgeRegistries.RECIPE_TYPES.containsKey(location)) { + LOGGER.error("Invalid or missing recipe category: $location!") + return@Finder Stream.empty() + } + + val findRecipeType = ForgeRegistries.RECIPE_TYPES.getValue(location) as RecipeType>? ?: throw ConcurrentModificationException() + + val isCritical = data["is_critical"]?.asBoolean ?: true + val ignoreDamageables = data["ignore_damageables"]?.asBoolean ?: false + val allowBacktrack = data["allow_backtrack"]?.asBoolean ?: true + + var stream = server.recipeManager.byType(findRecipeType).values.parallelStream().filter { !it.isIncomplete } + + if (ignoreDamageables) { + stream = stream.filter { it.ingredients.stream().flatMap { it.items.stream() }.noneMatch { it.isDamageableItem } } + } + + stream.map { + ResolvedRecipe( + it.ingredients.stream() + .filter { !it.isActuallyEmpty } + .map { it.items.stream().map(::ImmutableStack) }, + ImmutableStack(it.resultItem), + isCritical = isCritical, + name = it.id, + allowBacktrack = allowBacktrack + ) + } + .filter { + if (it.inputs.isEmpty()) { + LOGGER.warn("${it.formattedName} with output '${it.output.item.registryName}' ended up with no inputs!") + false + } else { + true + } + } + } + } + } + + private var foundResolvers: Map> = ImmutableMap.of() + + override fun apply(values: Map, resourceManager: ResourceManager, profilerFiller: ProfilerFiller) { + determinedValues.clear() + commentary.clear() + + val builder = ImmutableMap.Builder>() + + for ((key, json) in values) { + if (json !is JsonObject) { + throw JsonParseException("$key is not a json object") + } + + val location = (json["type"] ?: throw JsonSyntaxException("Missing resolver type")).let { ResourceLocation.tryParse(it.asString) } ?: throw JsonSyntaxException("Invalid resolver type: ${json["type"]}") + + if (!recipeFinders.containsKey(location)) { + throw JsonParseException("Resolver type $location does not exist (in $key)") + } + + val resolver = recipeFinders.getValue(location) ?: throw ConcurrentModificationException() + builder.put(key, resolver to json) + } + + foundResolvers = builder.build() + } + + private data class Accumulator( + val input2Recipes: Reference2ObjectOpenHashMap> = Reference2ObjectOpenHashMap(), + val output2Recipes: Reference2ObjectOpenHashMap> = Reference2ObjectOpenHashMap(), + ) { + val heuristicsSize: Long + get() { + return input2Recipes.size.toLong() + output2Recipes.size.toLong() + } + + fun combine(other: Accumulator) { + for ((k, v) in other.input2Recipes) { + val existing = input2Recipes[k] + + if (existing == null) { + input2Recipes[k] = v + } else { + existing.addAll(v) + } + } + + for ((k, v) in other.output2Recipes) { + val existing = output2Recipes[k] + + if (existing == null) { + output2Recipes[k] = v + } else { + existing.addAll(v) + } + } + } + + fun add(recipe: ResolvedRecipe) { + output2Recipes.computeIfAbsent(recipe.output.item, Reference2ObjectFunction { ReferenceOpenHashSet() }).add(recipe) + + for (input1 in recipe.inputs) { + for (input in input1) { + input2Recipes.computeIfAbsent(input.item, Reference2ObjectFunction { ReferenceOpenHashSet() }).add(recipe) + } + } + } + } + + private var input2Recipes: Map> = mapOf() + private var output2Recipes: Map> = mapOf() + + private val determinedValues = Reference2ObjectOpenHashMap() + private val seenItems = ArrayDeque() + + private var changes = false + private var cachedIterationResults = Reference2ObjectOpenHashMap() + + private fun doTryToBacktrack(item: Item, makeCommentary: Boolean): Result { + val recipes = input2Recipes[item] + + if (recipes == null || recipes.isEmpty() || !recipes.all { it.allowBacktrack } || !recipes.all { it.inputs.all { it.all { it.item == item } } }) { + if (makeCommentary) + commentary[item] = TextComponent("Item '${item.registryName}' has no recipes") + + return Result.MISSING + } + + var minimal: IMatterValue? = null + var minimalMultiplier = 0.0 + var foundRecipe: ResolvedRecipe? = null + + for (recipe in recipes) { + val value = doDetermineValue(recipe.output.item) + + if (value.value != null) { + if (minimal == null || minimal < value.value) { + minimal = value.value + minimalMultiplier = recipe.output.multiplier / recipe.inputs.size + foundRecipe = recipe + } + } else if (!value.isSkipped) { + LOGGER.error("${item.registryName} cant resolve ${recipe.output.item}") + minimal = null + break + } + } + + if (minimal == null) { + if (makeCommentary) + commentary[item] = TextComponent("Item '${item.registryName}' has no valid backtracking recipes, and no output recipes") + + return Result.MISSING + } + + val result = MatterValue(minimal.matter * minimalMultiplier, minimal.complexity * minimalMultiplier) + + changes = true + determinedValues[item] = result + commentary[item] = TextComponent("Matter value backtracked from ${foundRecipe!!.formattedName}") + return Result(result) + } + + private fun tryToBacktrack(item: Item, makeCommentary: Boolean): Result { + val getResult = cachedIterationResults[item] + + if (getResult != null) { + return getResult + } + + val value = Registry.direct(item) + + if (value.hasMatterValue) { + return Result(value) + } + + val value2 = determinedValues[item] + + if (value2?.hasMatterValue == true) { + return Result(value2) + } + + val value3 = Registry.indirect(item) + + if (value3.value != null) { + return value3 + } + + if (item in seenItems && item != seenItems.last()) { + return Result.SKIPPED + } + + seenItems.addLast(item) + + try { + val result = doTryToBacktrack(item, makeCommentary) + cachedIterationResults[item] = result + return result + } finally { + seenItems.removeLast() + } + } + + private fun doDetermineValue(item: Item): Result { + var minimalMatter: ImpreciseFraction? = null + var minimalComplexity: Double? = null + + val recipes = output2Recipes[item] + + if (recipes == null || recipes.isEmpty()) { + return tryToBacktrack(item, true) + } + + var hadSkips = false + var minimalRecipe: ResolvedRecipe? = null + + recipesLoop@ for (recipe in recipes) { + if (recipe.inputs.isEmpty()) { + // TODO: should we ignore empty recipes? + commentary[item] = TextComponent("${recipe.formattedName} is empty") + + continue + } + + var accumulatedMatter: ImpreciseFraction? = null + var accumulatedComplexity: Double? = null + + inputsLoop@ for ((i, inputs) in recipe.inputs.withIndex()) { + var minimal: IMatterValue? = null + var minimalMultiplier = 0.0 + + for (input in inputs) { + val ivalue = determineValue(input.item) + + if (ivalue.isMissing) { + commentary[item] = TextComponent("Input '${input.item.registryName}' at input slot $i in ${recipe.formattedName} has no matter value") + + if (recipe.isCritical) { + return Result.MISSING + } else { + continue@recipesLoop + } + } else if (ivalue.isSkipped) { + commentary[item] = TextComponent("Input '${input.item.registryName}' at input slot $i in ${recipe.formattedName} is recursive") + hadSkips = true + continue@recipesLoop + } + + if (minimal == null || minimal > ivalue.value!!) { + minimal = ivalue.value + minimalMultiplier = input.multiplier + } + } + + if (minimal == null || !minimal.hasMatterValue) { + commentary[item] = TextComponent("'${recipe.formattedName}' has invalid input at slot $i (possible inputs: ${inputs.joinToString(", ", transform = { it.item.registryName.toString() }) }) (???)") + return Result.MISSING + } + + if (accumulatedMatter == null || accumulatedComplexity == null) { + accumulatedMatter = minimal.matter * minimalMultiplier + accumulatedComplexity = minimal.complexity * minimalMultiplier + } else { + accumulatedMatter += minimal.matter * minimalMultiplier + accumulatedComplexity += minimal.complexity * minimalMultiplier + } + } + + if (accumulatedMatter == null || accumulatedComplexity == null) { + throw RuntimeException("This piece should be unreachable") + } + + if (!accumulatedMatter.isPositive || accumulatedComplexity <= 0.0) { + commentary[item] = TextComponent("${recipe.formattedName} ended up with negative matter value and/or complexity (???)") + return Result.MISSING + } + + if (minimalMatter == null || minimalMatter > accumulatedMatter) { + minimalMatter = recipe.transformMatterValue(accumulatedMatter * recipe.output.multiplier) + minimalComplexity = recipe.transformComplexity(accumulatedComplexity * recipe.output.multiplier) + minimalRecipe = recipe + } + } + + if (minimalMatter == null || minimalComplexity == null) { + if (hadSkips) { + val backtrack = tryToBacktrack(item, false) + + if (backtrack.value == null) { + return Result.SKIPPED + } else { + return backtrack + } + } + + if (item !in commentary) + commentary[item] = TextComponent("'${item.registryName}' ended up with no valid recipes (???)") + + return Result.MISSING + } + + val result = MatterValue(minimalMatter, minimalComplexity) + + changes = true + determinedValues[item] = result + commentary[item] = TextComponent("Matter value derived from ${minimalRecipe!!.formattedName}") + + return Result(result) + } + + fun determineValue(item: Item): Result { + val getResult = cachedIterationResults[item] + + if (getResult != null) { + return getResult + } + + val value = Registry.direct(item) + + if (value.hasMatterValue) { + return Result(value) + } + + val value2 = determinedValues[item] + + if (value2?.hasMatterValue == true) { + return Result(value2) + } + + val value3 = Registry.indirect(item) + + if (value3.value != null) { + return value3 + } + + if (item in seenItems) { + return Result.SKIPPED + } + + seenItems.addLast(item) + + try { + val result = doDetermineValue(item) + cachedIterationResults[item] = result + return result + } finally { + seenItems.removeLast() + } + } + + fun resolve(server: MinecraftServer) { + var time = SystemTime() + LOGGER.info("Finding recipes...") + + determinedValues.clear() + seenItems.clear() + + val foundStreams = foundResolvers.map { + try { + it.value.first.find(server, it.value.second) + } catch(err: Throwable) { + LOGGER.fatal("Recipe resolver ${it.key} experienced internal error", err) + throw RuntimeException("Recipe resolver ${it.key} experienced internal error", err) + } + } + + val sequentialStreams = foundStreams.filter { !it.isParallel } + val parallelStreams = foundStreams.filter { it.isParallel } + + val streamA = Streams.concat(*sequentialStreams.toTypedArray()) + val streamB = Streams.concat(*parallelStreams.toTypedArray()) + + val resultA = streamA.collect(::Accumulator, Accumulator::add, Accumulator::combine) + val resultB = streamB.collect(::Accumulator, Accumulator::add, Accumulator::combine) + + val result: Accumulator + + if (resultA.heuristicsSize > resultB.heuristicsSize) { + result = resultA + resultA.combine(resultB) + } else { + result = resultB + resultB.combine(resultA) + } + + val (input2Recipes, output2Recipes) = result + + LOGGER.info("Finding recipes took ${time.millis}ms, involving ${input2Recipes.keys.size} unique inputs and ${output2Recipes.keys.size} unique outputs") + LOGGER.info("Resolving recipes...") + + this.input2Recipes = input2Recipes + this.output2Recipes = output2Recipes + + changes = true + var i = 0 + var ops = 0 + + val toDetermine = ReferenceLinkedOpenHashSet() + toDetermine.addAll(input2Recipes.keys) + toDetermine.addAll(output2Recipes.keys) + + if (LOGGER.isDebugEnabled) { + LOGGER.debug("Gonna try to search for matter values for next items:") + + val names = ArrayList() + var length = 0 + + for (value in toDetermine) { + val name = value.registryName!!.toString() + + if (length != 0 && length + name.length < 400) { + names.add(name) + length += name.length + } else if (length == 0) { + names.add(name) + length = name.length + } else { + LOGGER.debug(names.joinToString(", ")) + names.clear() + length = 0 + } + } + + if (names.isNotEmpty()) { + LOGGER.debug(names.joinToString(", ")) + } + } + + time = SystemTime() + + while (changes) { + ops += cachedIterationResults.size + cachedIterationResults = Reference2ObjectOpenHashMap() + changes = false + i++ + + val iterator = toDetermine.iterator() + + for (value in iterator) { + if (determineValue(value).value?.hasMatterValue == true) { + iterator.remove() + } + } + } + + for (item in toDetermine) { + if (item !in commentary) { + commentary[item] = TextComponent("Item ${item.registryName} was never visited") + } + } + + LOGGER.info("Resolving recipes took ${time.millis}ms in $i iterations with ~$ops operations, determined ${determinedValues.size} matter values") + } + } + + internal fun compute(value: Item): Result { + return Resolver.determineValue(value) + } + + private val seenTags = ArrayDeque>() + + internal fun compute(value: TagKey): Result { + if (value in seenTags) { + return Result.SKIPPED + } + + seenTags.addLast(value) + + try { + val result = Registry.tagEntries[value] + + if (result != null) { + return Result(result) + } + + val computed = Registry.computeTags[value] + + if (computed != null) { + return computed.tryCompute() + } + + return Result.MISSING + } finally { + seenTags.removeLast() + } } @JvmStatic fun hasMatterValue(value: Item): Boolean { - return getValue(value).hasMatterValue + return get(value).hasMatterValue } - private fun getValue(value: ItemStack, level: Int): IMatterValue { + private fun get(value: ItemStack, level: Int): IMatterValue { if (value.isEmpty) { return IMatterValue.Companion } @@ -293,7 +853,7 @@ object MatterManager : SimpleJsonResourceReloadListener(GsonBuilder().setPrettyP if (value.item is IMatterItem) matter = (value.item as IMatterItem).getMatterValue(value) ?: IMatterValue.Companion else - matter = getValue(value.item) + matter = get(value.item) if (matter.hasMatterValue && value.isDamageableItem) { val severity = 1.0 - value.damageValue.toDouble() / value.maxDamage.toDouble() @@ -314,7 +874,7 @@ object MatterManager : SimpleJsonResourceReloadListener(GsonBuilder().setPrettyP if (drive != null && drive.storageType === OverdriveThatMatters.INSTANCE.ITEM_STORAGE()) { for (item in (drive as IMatteryDrive).stacks) { - val tuple = getValue(item.stack.stack, level + 1) + val tuple = get(item.stack.stack, level + 1) if (tuple.hasMatterValue) { matter += tuple @@ -334,13 +894,13 @@ object MatterManager : SimpleJsonResourceReloadListener(GsonBuilder().setPrettyP * Matter value for itemstack, accounting for capabilities and stack size */ @JvmStatic - fun getValue(value: ItemStack): IMatterValue { - return getValue(value, 0) + fun get(value: ItemStack): IMatterValue { + return get(value, 0) } @JvmStatic fun hasMatterValue(value: ItemStack): Boolean { - return getValue(value).hasMatterValue + return get(value).hasMatterValue } @JvmStatic @@ -348,7 +908,7 @@ object MatterManager : SimpleJsonResourceReloadListener(GsonBuilder().setPrettyP if (value is IMatterItem) return value.canDecompose(ItemStack.EMPTY) - return getValue(value).hasMatterValue + return get(value).hasMatterValue } @JvmStatic @@ -361,7 +921,7 @@ object MatterManager : SimpleJsonResourceReloadListener(GsonBuilder().setPrettyP can = can && (value.getCapability(MatteryCapability.MATTER).orNull()?.storedMatter ?: ImpreciseFraction.ZERO).isZero can = can && (value.getCapability(MatteryCapability.DRIVE).orNull()?.storedCount ?: BigInteger.ZERO).isZero - return can && getValue(value).hasMatterValue + return can && get(value).hasMatterValue } @JvmStatic @@ -375,19 +935,19 @@ object MatterManager : SimpleJsonResourceReloadListener(GsonBuilder().setPrettyP @JvmStatic fun getResearchAdvance(stack: ItemStack): Double { - return getResearchAdvance(getValue(stack).complexity) + return getResearchAdvance(get(stack).complexity) } @JvmStatic fun getResearchAdvance(stack: ItemLike): Double { - return getResearchAdvance(getValue(stack.asItem()).complexity) + return getResearchAdvance(this@MatterManager.get(stack.asItem()).complexity) } fun tooltipEvent(event: ItemTooltipEvent) { val window = minecraft.window.window if (InputConstants.isKeyDown(window, GLFW.GLFW_KEY_LEFT_SHIFT) || InputConstants.isKeyDown(window, GLFW.GLFW_KEY_RIGHT_SHIFT)) { - val matter = getValue(event.itemStack) + val matter = get(event.itemStack) if (matter.hasMatterValue) { if (matter.complexity >= 1.0E-3) { @@ -402,7 +962,7 @@ object MatterManager : SimpleJsonResourceReloadListener(GsonBuilder().setPrettyP } if (event.flags.isAdvanced) { - val commentary = RecipeResolverManager.getCommentary(event.itemStack.item)?.withStyle(ChatFormatting.DARK_GRAY) + val commentary = getCommentary(event.itemStack.item)?.withStyle(ChatFormatting.DARK_GRAY) if (commentary != null) { event.toolTip.add(commentary) @@ -411,140 +971,115 @@ object MatterManager : SimpleJsonResourceReloadListener(GsonBuilder().setPrettyP } } - 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 + /** + * because it is immutable + */ + data class ImmutableStack( + val item: Item, + val multiplier: Double = 1.0 ) { - val time = System.nanoTime() - LOGGER.info("Beginning loading matter values") - profilerFiller.push("otm_matter_registry") + constructor(item: Item, count: Int) : this(item, 1.0 / count.toDouble()) + constructor(item: ItemStack) : this(item.item, item.count) + } - try { - canCompute = false - resolvedEverything = false + class ResolvedRecipe( + inputs: Stream>, + val output: ImmutableStack, + val transformMatterValue: (ImpreciseFraction) -> ImpreciseFraction = { it }, + val transformComplexity: (Double) -> Double = { it }, - keyEntries.clear() - tagEntries.clear() - directlyComputedEntries.clear() - computedEntries.clear() + /** + * if we can't resolve one of ingredient's matter value - consider output also having no matter value + */ + val isCritical: Boolean = true, + val name: ResourceLocation? = null, - val add = ArrayList>() - val addOrReplace = ArrayList>() - val update = ArrayList>() - val delete = ArrayList>() + /** + * Whenever to allow "backtracking" recipe ingredients to recipe outputs. + * + * When an item has no recipes, but takes part in recipes with output of already determined matter value + * (e.g. 4 rabbit hides -> 1 leather), we can assume that we can determine matter value of ingredients based on matter value of output + * + * "Backtrack" can happen if everything of the next holds true: + * * All recipes with item in question contains only that item; and + * * All recipes allow backtracking + */ + val allowBacktrack: Boolean = true, + ) { + val inputs: List> = inputs + .map { it.filter { it.multiplier > 0.0 }.collect(ImmutableList.toImmutableList()) } + .filter { it.isNotEmpty() } + .collect(ImmutableList.toImmutableList()) - computeKeys.clear() - computeTags.clear() + constructor( + inputs: Collection>, + output: ImmutableStack, + transformMatterValue: (ImpreciseFraction) -> ImpreciseFraction = { it }, + transformComplexity: (Double) -> Double = { it }, + ) : this(inputs.stream().map { it.stream() }, output, transformMatterValue, transformComplexity) - for ((key, json) in map) { - if (json !is JsonObject) { - throw JsonParseException("Matter value $key has invalid type: ${json::class.qualifiedName} ($json)") - } + val formattedName: String + get() = if (name == null) "One of recipes" else "Recipe '$name'" + } - 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) + fun interface Finder { + /** + * Returned stream can be either parallel or sequential + * + * Parallel streams will _greatly_ improve performance on mid-end range computers, + * but you need to make sure your stream has no side effects (such as binding late-binding tags) + * + * Internally, OTM separate parallel streams from sequential so sequential streams happen strictly on server thread + * (if you can't avoid side effects, or don't bother with synchronization). + * + * You can safely call [MatterManager.getDirect] inside returned stream, both parallel and sequential. + */ + fun find(server: MinecraftServer, json: JsonObject): Stream + } - if (value.replaceIfExists) { - addOrReplace.add(value to key) - } else { - add.add(value to key) - } - } + @JvmStatic val recipeFinders get() = Resolver.delegate.get() + @JvmStatic val recipeFindersKey get() = Resolver.delegate.key - "update" -> update.add(UpdateAction(json) to key) - "delete" -> delete.add(DeleteAction(json) to key) - "compute" -> { - try { - ComputeAction(json).also { - if (it.key != null) { - computeKeys.compute(it.key) { _, existing -> if (it.canReplace(existing)) it else existing } - } else { - computeTags.compute(it.tag!!) { _, existing -> if (it.canReplace(existing)) it else existing } - } - } - } catch (err: Throwable) { - throw JsonSyntaxException("Processing $key failed", err) - } - } - else -> throw JsonParseException("Unknown action $action of $key") - } + private val commentary = Reference2ObjectOpenHashMap() + + @JvmStatic + fun getCommentary(item: Item): MutableComponent? { + return commentary[item]?.copy() + } + + fun reloadEvent(event: AddReloadListenerEvent) { + event.addListener(Registry) + event.addListener(Resolver) + } + + fun initialize(bus: IEventBus) { + bus.addListener(Resolver.delegate::build) + Resolver.registrar.register(bus) + } + + private val matterValues = Reference2ObjectOpenHashMap() + private val validMatterValues = ArrayList>() + + val valuesMap: Map = Collections.unmodifiableMap(matterValues) + val valuesList: List> = Collections.unmodifiableList(validMatterValues) + + fun onServerStarted(event: ServerStartedEvent) { + Resolver.resolve(event.server) + + matterValues.clear() + validMatterValues.clear() + + for (item in ForgeRegistries.ITEMS) { + val value = compute(item).value ?: IMatterValue.Companion + matterValues[item] = value + + if (value.hasMatterValue) { + validMatterValues.add(item to value) } - - 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") + fun get(value: Item): IMatterValue { + return matterValues[value] ?: throw ConcurrentModificationException("$value has no matter value in registry, this should be impossible") } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/matter/RecipeResolverManager.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/matter/RecipeResolverManager.kt index f9a64dfe8..c2c26877c 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/matter/RecipeResolverManager.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/matter/RecipeResolverManager.kt @@ -39,609 +39,3 @@ import ru.dbotthepony.mc.otm.core.registryName import ru.dbotthepony.mc.otm.core.stream import ru.dbotthepony.mc.otm.registry.RegistryDelegate import java.util.stream.Stream - -/** - * This manager holds json defined recipe resolvers (they process their own) - */ -object RecipeResolverManager : SimpleJsonResourceReloadListener(GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create(), "otm_recipe_resolver") { - /** - * because it is immutable - */ - data class ImmutableStack( - val item: Item, - val multiplier: Double = 1.0 - ) { - constructor(item: Item, count: Int) : this(item, 1.0 / count.toDouble()) - constructor(item: ItemStack) : this(item.item, item.count) - } - - class ResolvedRecipe( - inputs: Stream>, - val output: ImmutableStack, - val transformMatterValue: (ImpreciseFraction) -> ImpreciseFraction = { it }, - val transformComplexity: (Double) -> Double = { it }, - - /** - * if we can't resolve one of ingredient's matter value - consider output also having no matter value - */ - val isCritical: Boolean = true, - val name: ResourceLocation? = null, - - /** - * Whenever to allow "backtracking" recipe ingredients to recipe outputs. - * - * When an item has no recipes, but takes part in recipes with output of already determined matter value - * (e.g. 4 rabbit hides -> 1 leather), we can assume that we can determine matter value of ingredients based on matter value of output - * - * "Backtrack" can happen if everything of the next holds true: - * * All recipes with item in question contains only that item; and - * * All recipes allow backtracking - */ - val allowBacktrack: Boolean = true, - ) { - val inputs: List> = inputs - .map { it.filter { it.multiplier > 0.0 }.collect(ImmutableList.toImmutableList()) } - .filter { it.isNotEmpty() } - .collect(ImmutableList.toImmutableList()) - - constructor( - inputs: Collection>, - output: ImmutableStack, - transformMatterValue: (ImpreciseFraction) -> ImpreciseFraction = { it }, - transformComplexity: (Double) -> Double = { it }, - ) : this(inputs.stream().map { it.stream() }, output, transformMatterValue, transformComplexity) - - val formattedName: String - get() = if (name == null) "One of recipes" else "Recipe '$name'" - } - - fun interface Finder { - /** - * Returned stream can be either parallel or sequential - * - * Parallel streams will _greatly_ improve performance on mid-end range computers, - * but you need to make sure your stream has no side effects (such as binding late-binding tags) - * - * Internally, OTM separate parallel streams from sequential so sequential streams happen strictly on server thread - * (if you can't avoid side effects, or don't bother with synchronization). - * - * You can safely call [MatterManager.getDirect] inside returned stream, both parallel and sequential. - */ - fun find(server: MinecraftServer, json: JsonObject): Stream - } - - private val delegate = RegistryDelegate("recipe_resolver") { - disableSync() - disableSaving() - } - - private val registrar = DeferredRegister.create(delegate.key, OverdriveThatMatters.MOD_ID) - private val LOGGER = LogManager.getLogger() - - init { - registrar.register("simple") { - Finder { server, data -> - val location = (data["recipe_type"] ?: throw JsonSyntaxException("Missing recipe type")).let { ResourceLocation.tryParse(it.asString) } ?: throw JsonSyntaxException("Invalid recipe type: ${data["recipe_type"]}") - - if (!ForgeRegistries.RECIPE_TYPES.containsKey(location)) { - LOGGER.error("Invalid or missing recipe category: $location!") - return@Finder Stream.empty() - } - - val findRecipeType = ForgeRegistries.RECIPE_TYPES.getValue(location) as RecipeType>? ?: throw ConcurrentModificationException() - - val isCritical = data["is_critical"]?.asBoolean ?: true - val ignoreDamageables = data["ignore_damageables"]?.asBoolean ?: false - val allowBacktrack = data["allow_backtrack"]?.asBoolean ?: true - - var stream = server.recipeManager.byType(findRecipeType).values.parallelStream().filter { !it.isIncomplete } - - if (ignoreDamageables) { - stream = stream.filter { it.ingredients.stream().flatMap { it.items.stream() }.noneMatch { it.isDamageableItem } } - } - - stream.map { - ResolvedRecipe( - it.ingredients.stream() - .filter { !it.isActuallyEmpty } - .map { it.items.stream().map(::ImmutableStack) }, - ImmutableStack(it.resultItem), - isCritical = isCritical, - name = it.id, - allowBacktrack = allowBacktrack - ) - } - .filter { - if (it.inputs.isEmpty()) { - LOGGER.warn("${it.formattedName} with output '${it.output.item.registryName}' ended up with no inputs!") - false - } else { - true - } - } - } - } - } - - fun initialize(bus: IEventBus) { - bus.addListener(delegate::build) - registrar.register(bus) - } - - fun reloadEvent(event: AddReloadListenerEvent) { - event.addListener(this) - } - - fun onServerStarted(event: ServerStartedEvent) { - resolve(event.server) - } - - @JvmStatic val RESOLVERS get() = delegate.get() - @JvmStatic val RESOLVERS_KEY get() = delegate.key - - /** - * it exists solely for fail-fast to find bugs, do not rely on this property - */ - var ready = false - private set - - var resolved = false - private set - - private var foundResolvers: Map> = ImmutableMap.of() - - override fun prepare( - p_10771_: ResourceManager, - p_10772_: ProfilerFiller - ): MutableMap { - ready = false - resolved = false - // determinedValues.clear() // can't clear here, it is executed on separate thread - return super.prepare(p_10771_, p_10772_) - } - - override fun apply( - map: Map, - resourceManager: ResourceManager, - profilerFiller: ProfilerFiller - ) { - ready = false - resolved = false - determinedValues.clear() - commentary.clear() - - val builder = ImmutableMap.Builder>() - - for ((key, json) in map) { - if (json !is JsonObject) { - throw JsonParseException("$key is not a json object") - } - - val location = (json["type"] ?: throw JsonSyntaxException("Missing resolver type")).let { ResourceLocation.tryParse(it.asString) } ?: throw JsonSyntaxException("Invalid resolver type: ${json["type"]}") - - if (!RESOLVERS.containsKey(location)) { - throw JsonParseException("Resolver type $location does not exist (in $key)") - } - - val resolver = RESOLVERS.getValue(location) ?: throw ConcurrentModificationException() - builder.put(key, resolver to json) - } - - foundResolvers = builder.build() - - ready = true - } - - private data class Accumulator( - val input2Recipes: Reference2ObjectOpenHashMap> = Reference2ObjectOpenHashMap(), - val output2Recipes: Reference2ObjectOpenHashMap> = Reference2ObjectOpenHashMap(), - ) { - val heuristicsSize: Long - get() { - return input2Recipes.size.toLong() + output2Recipes.size.toLong() - } - - fun combine(other: Accumulator) { - for ((k, v) in other.input2Recipes) { - val existing = input2Recipes[k] - - if (existing == null) { - input2Recipes[k] = v - } else { - existing.addAll(v) - } - } - - for ((k, v) in other.output2Recipes) { - val existing = output2Recipes[k] - - if (existing == null) { - output2Recipes[k] = v - } else { - existing.addAll(v) - } - } - } - - fun add(recipe: ResolvedRecipe) { - output2Recipes.computeIfAbsent(recipe.output.item, Reference2ObjectFunction { ReferenceOpenHashSet() }).add(recipe) - - for (input1 in recipe.inputs) { - for (input in input1) { - input2Recipes.computeIfAbsent(input.item, Reference2ObjectFunction { ReferenceOpenHashSet() }).add(recipe) - } - } - } - } - - fun getDetermined(index: Item): IMatterValue { - return determinedValues[index] ?: IMatterValue.Companion - } - - private var input2Recipes: Map> = mapOf() - private var output2Recipes: Map> = mapOf() - - private val determinedValues = Reference2ObjectOpenHashMap() - private val commentary = Reference2ObjectOpenHashMap() - private val seenItems = ArrayDeque() - - fun getCommentary(item: Item): MutableComponent? { - return commentary[item]?.copy() - } - - private data class Result(val type: Type, val value: IMatterValue? = null) { - constructor(value: IMatterValue) : this(Type.RESOLVED, value) - - val isSkipped get() = type === Type.SKIPPED - val isMissing get() = type === Type.MISSING - - enum class Type { - RESOLVED, SKIPPED, MISSING - } - - companion object { - // recursive recipes should be ignored until we resolve everything else - val SKIPPED = Result(Type.SKIPPED) - - // missing matter values are fatal, whole recipe chain is then considered defunct - val MISSING = Result(Type.MISSING) - } - } - - private var changes = false - private var cachedIterationResults = Reference2ObjectOpenHashMap() - - private fun doTryToBacktrack(item: Item, makeCommentary: Boolean): Result { - val recipes = input2Recipes[item] - - if (recipes == null || recipes.isEmpty() || !recipes.all { it.allowBacktrack } || !recipes.all { it.inputs.all { it.all { it.item == item } } }) { - if (makeCommentary) - commentary[item] = TextComponent("Item '${item.registryName}' has no recipes") - - return Result.MISSING - } - - var minimal: IMatterValue? = null - var minimalMultiplier = 0.0 - var foundRecipe: ResolvedRecipe? = null - - for (recipe in recipes) { - val value = doDetermineValue(recipe.output.item) - - if (value.value != null) { - if (minimal == null || minimal < value.value) { - minimal = value.value - minimalMultiplier = recipe.output.multiplier / recipe.inputs.size - foundRecipe = recipe - } - } else if (!value.isSkipped) { - LOGGER.error("${item.registryName} cant resolve ${recipe.output.item}") - minimal = null - break - } - } - - if (minimal == null) { - if (makeCommentary) - commentary[item] = TextComponent("Item '${item.registryName}' has no valid backtracking recipes, and no output recipes") - - return Result.MISSING - } - - val result = MatterValue(minimal.matter * minimalMultiplier, minimal.complexity * minimalMultiplier) - - changes = true - determinedValues[item] = result - commentary[item] = TextComponent("Matter value backtracked from ${foundRecipe!!.formattedName}") - return Result(result) - } - - private fun tryToBacktrack(item: Item, makeCommentary: Boolean): Result { - val getResult = cachedIterationResults[item] - - if (getResult != null) { - return getResult - } - - var value: IMatterValue? = MatterManager.getDirect(item) - - if (value?.hasMatterValue == true) { - return Result(value) - } - - value = determinedValues[item] - - if (value?.hasMatterValue == true) { - return Result(value) - } - - if (item in seenItems && item != seenItems.last()) { - return Result.SKIPPED - } - - seenItems.addLast(item) - - try { - val result = doTryToBacktrack(item, makeCommentary) - cachedIterationResults[item] = result - return result - } finally { - seenItems.removeLast() - } - } - - private fun doDetermineValue(item: Item): Result { - var minimalMatter: ImpreciseFraction? = null - var minimalComplexity: Double? = null - - val recipes = output2Recipes[item] - - if (recipes == null || recipes.isEmpty()) { - return tryToBacktrack(item, true) - } - - var hadSkips = false - var minimalRecipe: ResolvedRecipe? = null - - recipesLoop@ for (recipe in recipes) { - if (recipe.inputs.isEmpty()) { - // TODO: should we ignore empty recipes? - commentary[item] = TextComponent("${recipe.formattedName} is empty") - - continue - } - - var accumulatedMatter: ImpreciseFraction? = null - var accumulatedComplexity: Double? = null - - inputsLoop@ for ((i, inputs) in recipe.inputs.withIndex()) { - var minimal: IMatterValue? = null - var minimalMultiplier = 0.0 - - for (input in inputs) { - val ivalue = determineValue(input.item) - - if (ivalue.isMissing) { - commentary[item] = TextComponent("Input '${input.item.registryName}' at input slot $i in ${recipe.formattedName} has no matter value") - - if (recipe.isCritical) { - return Result.MISSING - } else { - continue@recipesLoop - } - } else if (ivalue.isSkipped) { - commentary[item] = TextComponent("Input '${input.item.registryName}' at input slot $i in ${recipe.formattedName} is recursive") - hadSkips = true - continue@recipesLoop - } - - if (minimal == null || minimal > ivalue.value!!) { - minimal = ivalue.value - minimalMultiplier = input.multiplier - } - } - - if (minimal == null || !minimal.hasMatterValue) { - commentary[item] = TextComponent("'${recipe.formattedName}' has invalid input at slot $i (possible inputs: ${inputs.joinToString(", ", transform = { it.item.registryName.toString() }) }) (???)") - return Result.MISSING - } - - if (accumulatedMatter == null || accumulatedComplexity == null) { - accumulatedMatter = minimal.matter * minimalMultiplier - accumulatedComplexity = minimal.complexity * minimalMultiplier - } else { - accumulatedMatter += minimal.matter * minimalMultiplier - accumulatedComplexity += minimal.complexity * minimalMultiplier - } - } - - if (accumulatedMatter == null || accumulatedComplexity == null) { - throw RuntimeException("This piece should be unreachable") - } - - if (!accumulatedMatter.isPositive || accumulatedComplexity <= 0.0) { - commentary[item] = TextComponent("${recipe.formattedName} ended up with negative matter value and/or complexity (???)") - return Result.MISSING - } - - if (minimalMatter == null || minimalMatter > accumulatedMatter) { - minimalMatter = recipe.transformMatterValue(accumulatedMatter * recipe.output.multiplier) - minimalComplexity = recipe.transformComplexity(accumulatedComplexity * recipe.output.multiplier) - minimalRecipe = recipe - } - } - - if (minimalMatter == null || minimalComplexity == null) { - if (hadSkips) { - val backtrack = tryToBacktrack(item, false) - - if (backtrack.value == null) { - return Result.SKIPPED - } else { - return backtrack - } - } - - if (item !in commentary) - commentary[item] = TextComponent("'${item.registryName}' ended up with no valid recipes (???)") - - return Result.MISSING - } - - val result = MatterValue(minimalMatter, minimalComplexity) - - changes = true - determinedValues[item] = result - commentary[item] = TextComponent("Matter value derived from ${minimalRecipe!!.formattedName}") - - return Result(result) - } - - private fun determineValue(item: Item): Result { - val getResult = cachedIterationResults[item] - - if (getResult != null) { - return getResult - } - - var value: IMatterValue? = MatterManager.getDirect(item) - - if (value?.hasMatterValue == true) { - return Result(value) - } - - value = determinedValues[item] - - if (value?.hasMatterValue == true) { - return Result(value) - } - - if (item in seenItems) { - return Result.SKIPPED - } - - seenItems.addLast(item) - - try { - val result = doDetermineValue(item) - cachedIterationResults[item] = result - return result - } finally { - seenItems.removeLast() - } - } - - private fun resolve(server: MinecraftServer) { - if (resolved) { - return - } - - check(ready) { "RecipeResolverManager is not ready to resolve matter values" } - check(MatterManager.canCompute) { "MatteryManager is not ready to compute matter values" } - - var time = SystemTime() - LOGGER.info("Finding recipes...") - - determinedValues.clear() - seenItems.clear() - commentary.clear() - - val foundStreams = foundResolvers.map { - try { - it.value.first.find(server, it.value.second) - } catch(err: Throwable) { - LOGGER.fatal("Recipe resolver ${it.key} experienced internal error", err) - throw RuntimeException("Recipe resolver ${it.key} experienced internal error", err) - } - } - - val sequentialStreams = foundStreams.filter { !it.isParallel } - val parallelStreams = foundStreams.filter { it.isParallel } - - val streamA = Streams.concat(*sequentialStreams.toTypedArray()) - val streamB = Streams.concat(*parallelStreams.toTypedArray()) - - val resultA = streamA.collect(::Accumulator, Accumulator::add, Accumulator::combine) - val resultB = streamB.collect(::Accumulator, Accumulator::add, Accumulator::combine) - - val result: Accumulator - - if (resultA.heuristicsSize > resultB.heuristicsSize) { - result = resultA - resultA.combine(resultB) - } else { - result = resultB - resultB.combine(resultA) - } - - val (input2Recipes, output2Recipes) = result - - LOGGER.info("Finding recipes took ${time.millis}ms, involving ${input2Recipes.keys.size} unique inputs and ${output2Recipes.keys.size} unique outputs") - LOGGER.info("Resolving recipes...") - - this.input2Recipes = input2Recipes - this.output2Recipes = output2Recipes - - changes = true - var i = 0 - var ops = 0 - - val toDetermine = ReferenceLinkedOpenHashSet() - toDetermine.addAll(input2Recipes.keys) - toDetermine.addAll(output2Recipes.keys) - - if (LOGGER.isDebugEnabled) { - LOGGER.debug("Gonna try to search for matter values for next items:") - - val names = ArrayList() - var length = 0 - - for (value in toDetermine) { - val name = value.registryName!!.toString() - - if (length != 0 && length + name.length < 400) { - names.add(name) - length += name.length - } else if (length == 0) { - names.add(name) - length = name.length - } else { - LOGGER.debug(names.joinToString(", ")) - names.clear() - length = 0 - } - } - - if (names.isNotEmpty()) { - LOGGER.debug(names.joinToString(", ")) - } - } - - time = SystemTime() - - while (changes) { - ops += cachedIterationResults.size - cachedIterationResults = Reference2ObjectOpenHashMap() - changes = false - i++ - - val iterator = toDetermine.iterator() - - for (value in iterator) { - if (determineValue(value).value?.hasMatterValue == true) { - iterator.remove() - } - } - } - - for (item in toDetermine) { - if (item !in commentary) { - commentary[item] = TextComponent("Item ${item.registryName} was never visited") - } - } - - resolved = true - - LOGGER.info("Resolving recipes took ${time.millis}ms in $i iterations with ~$ops operations, determined ${determinedValues.size} matter values") - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/matter/UpdateAction.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/matter/UpdateAction.kt index 63b24259d..8b906d42f 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/matter/UpdateAction.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/matter/UpdateAction.kt @@ -123,7 +123,7 @@ class UpdateAction : AbstractRegistryAction { } } - fun apply(value: MatterManager.MutableEntry, modifier: ResourceLocation) { + private fun apply(value: MutableEntry, modifier: ResourceLocation) { if (matterFunctions.isNotEmpty()) value.matter = matterFunctions.apply(value.matter) else if (matter != null) @@ -142,7 +142,7 @@ class UpdateAction : AbstractRegistryAction { value.modificationChain.add(modifier) } - override fun update(registry: MutableCollection, modifier: ResourceLocation) { + override fun update(registry: MutableCollection, modifier: ResourceLocation) { if(!( matter != null || complexity != null || @@ -174,7 +174,7 @@ class UpdateAction : AbstractRegistryAction { val iterator = registry.iterator() for (value in iterator) { - if (value is MatterManager.MutableKeyEntry && value.key == key) { + if (value is MutableKeyEntry && value.key == key) { apply(value, modifier) return } @@ -185,7 +185,7 @@ class UpdateAction : AbstractRegistryAction { val iterator = registry.iterator() for (value in iterator) { - if (value is MatterManager.MutableTagEntry && value.tag == tag) { + if (value is MutableTagEntry && value.tag == tag) { apply(value, modifier) return } diff --git a/src/main/resources/data/overdrive_that_matters/otm_recipe_resolver/blasting.json b/src/main/resources/data/overdrive_that_matters/otm_recipe_finder/blasting.json similarity index 100% rename from src/main/resources/data/overdrive_that_matters/otm_recipe_resolver/blasting.json rename to src/main/resources/data/overdrive_that_matters/otm_recipe_finder/blasting.json diff --git a/src/main/resources/data/overdrive_that_matters/otm_recipe_resolver/campfire_cooking.json b/src/main/resources/data/overdrive_that_matters/otm_recipe_finder/campfire_cooking.json similarity index 100% rename from src/main/resources/data/overdrive_that_matters/otm_recipe_resolver/campfire_cooking.json rename to src/main/resources/data/overdrive_that_matters/otm_recipe_finder/campfire_cooking.json diff --git a/src/main/resources/data/overdrive_that_matters/otm_recipe_resolver/crafting.json b/src/main/resources/data/overdrive_that_matters/otm_recipe_finder/crafting.json similarity index 100% rename from src/main/resources/data/overdrive_that_matters/otm_recipe_resolver/crafting.json rename to src/main/resources/data/overdrive_that_matters/otm_recipe_finder/crafting.json diff --git a/src/main/resources/data/overdrive_that_matters/otm_recipe_resolver/plate_press.json b/src/main/resources/data/overdrive_that_matters/otm_recipe_finder/plate_press.json similarity index 100% rename from src/main/resources/data/overdrive_that_matters/otm_recipe_resolver/plate_press.json rename to src/main/resources/data/overdrive_that_matters/otm_recipe_finder/plate_press.json diff --git a/src/main/resources/data/overdrive_that_matters/otm_recipe_resolver/smelting.json b/src/main/resources/data/overdrive_that_matters/otm_recipe_finder/smelting.json similarity index 100% rename from src/main/resources/data/overdrive_that_matters/otm_recipe_resolver/smelting.json rename to src/main/resources/data/overdrive_that_matters/otm_recipe_finder/smelting.json diff --git a/src/main/resources/data/overdrive_that_matters/otm_recipe_resolver/smithing.json b/src/main/resources/data/overdrive_that_matters/otm_recipe_finder/smithing.json similarity index 100% rename from src/main/resources/data/overdrive_that_matters/otm_recipe_resolver/smithing.json rename to src/main/resources/data/overdrive_that_matters/otm_recipe_finder/smithing.json diff --git a/src/main/resources/data/overdrive_that_matters/otm_recipe_resolver/smoking.json b/src/main/resources/data/overdrive_that_matters/otm_recipe_finder/smoking.json similarity index 100% rename from src/main/resources/data/overdrive_that_matters/otm_recipe_resolver/smoking.json rename to src/main/resources/data/overdrive_that_matters/otm_recipe_finder/smoking.json diff --git a/src/main/resources/data/overdrive_that_matters/otm_recipe_resolver/stonecutting.json b/src/main/resources/data/overdrive_that_matters/otm_recipe_finder/stonecutting.json similarity index 100% rename from src/main/resources/data/overdrive_that_matters/otm_recipe_resolver/stonecutting.json rename to src/main/resources/data/overdrive_that_matters/otm_recipe_finder/stonecutting.json