From 83d11d531db950327d8ab846f111ed124412e053 Mon Sep 17 00:00:00 2001 From: DBotThePony Date: Wed, 5 Mar 2025 15:23:27 +0700 Subject: [PATCH] Cache recipes in Matter Entangler --- .../matter/MatterEntanglerBlockEntity.kt | 63 ++++++++++++++++--- .../mc/otm/core/util/ItemStackKey.kt | 35 +++++++++++ 2 files changed, 90 insertions(+), 8 deletions(-) create mode 100644 src/main/kotlin/ru/dbotthepony/mc/otm/core/util/ItemStackKey.kt diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterEntanglerBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterEntanglerBlockEntity.kt index e66525d66..82def3e57 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterEntanglerBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterEntanglerBlockEntity.kt @@ -1,13 +1,18 @@ package ru.dbotthepony.mc.otm.block.entity.matter +import com.github.benmanes.caffeine.cache.Cache +import com.github.benmanes.caffeine.cache.Caffeine +import com.github.benmanes.caffeine.cache.Scheduler import com.mojang.serialization.Codec import com.mojang.serialization.codecs.RecordCodecBuilder +import net.minecraft.Util import net.minecraft.core.BlockPos import net.minecraft.world.entity.player.Inventory import net.minecraft.world.entity.player.Player import net.minecraft.world.inventory.AbstractContainerMenu import net.minecraft.world.item.ItemStack import net.minecraft.world.item.crafting.CraftingInput +import net.minecraft.world.item.crafting.RecipeManager import net.minecraft.world.level.Level import net.minecraft.world.level.block.state.BlockState import net.neoforged.neoforge.capabilities.Capabilities @@ -28,13 +33,21 @@ import ru.dbotthepony.mc.otm.container.IEnhancedCraftingContainer import ru.dbotthepony.mc.otm.container.slotted.AutomationFilters import ru.dbotthepony.mc.otm.container.slotted.FilteredContainerSlot import ru.dbotthepony.mc.otm.container.slotted.SlottedContainer +import ru.dbotthepony.mc.otm.core.collect.filter +import ru.dbotthepony.mc.otm.core.collect.forEach +import ru.dbotthepony.mc.otm.core.collect.map +import ru.dbotthepony.mc.otm.core.collect.toList import ru.dbotthepony.mc.otm.core.math.Decimal +import ru.dbotthepony.mc.otm.core.util.ItemStackKey +import ru.dbotthepony.mc.otm.core.util.asKey import ru.dbotthepony.mc.otm.data.codec.DecimalCodec import ru.dbotthepony.mc.otm.data.codec.minRange import ru.dbotthepony.mc.otm.graph.matter.MatterNode import ru.dbotthepony.mc.otm.menu.matter.MatterEntanglerMenu +import ru.dbotthepony.mc.otm.recipe.IMatterEntanglerRecipe import ru.dbotthepony.mc.otm.registry.game.MBlockEntities import ru.dbotthepony.mc.otm.registry.game.MRecipes +import java.time.Duration class MatterEntanglerBlockEntity(blockPos: BlockPos, blockState: BlockState) : MatteryWorkerBlockEntity(MBlockEntities.MATTER_ENTANGLER, blockPos, blockState, Job.CODEC) { class Job(itemStack: ItemStack, val matter: Decimal, ticks: Double, experience: Float) : ItemJob(itemStack, ticks, MachinesConfig.MATTER_ENTANGLER.energyConsumption, experience = experience) { @@ -60,20 +73,47 @@ class MatterEntanglerBlockEntity(blockPos: BlockPos, blockState: BlockState) : M val experience = ExperienceStorage(MachinesConfig.MATTER_ENTANGLER::maxExperienceStored).also(::addNeighbourListener) val energyConfig = ConfigurableEnergy(energy) + private var recipeCache: Collection = listOf() + private var seenRecipeManager: RecipeManager? = null + + private fun getRecipes(): Collection { + val level = level!! + val manager = level.recipeManager + + if (seenRecipeManager !== manager) { + seenRecipeManager = manager + val input = inputs.asPositionedCraftInput() + + recipeCache = manager.byType(MRecipes.MATTER_ENTANGLER) + .iterator() + .map { it.value } + .filter { it.preemptivelyMatches(input, level, 3, 3) } + .toList() + } + + return recipeCache + } + private inner class InputSlot(container: SlottedContainer, slot: Int) : FilteredContainerSlot(container, slot) { + val insertCache: Cache = Caffeine.newBuilder() + .maximumSize(1024L) + .expireAfterWrite(Duration.ofMinutes(1L)) + .scheduler(Scheduler.systemScheduler()) + .executor(Util.backgroundExecutor()) + .build() + override fun canAutomationPlaceItem(itemStack: ItemStack): Boolean { if (!super.canAutomationPlaceItem(itemStack)) return false val level = level ?: return false - val list = container.toList() - list[slot] = itemStack - val shadow = CraftingInput.ofPositioned(3, 3, list) - return level - .recipeManager - .byType(MRecipes.MATTER_ENTANGLER) - .any { it.value.preemptivelyMatches(shadow, level, 3, 3) } + return insertCache.get(itemStack.asKey()) { + val list = container.toList() + list[slot] = itemStack + val shadow = CraftingInput.ofPositioned(3, 3, list) + getRecipes().any { it.preemptivelyMatches(shadow, level, 3, 3) } + } } override fun canAutomationTakeItem(desired: Int): Boolean { @@ -84,7 +124,14 @@ class MatterEntanglerBlockEntity(blockPos: BlockPos, blockState: BlockState) : M get() = 1 } - val inputs = IEnhancedCraftingContainer.Wrapper(SlottedContainer.simple(3 * 3, ::InputSlot, ::setChanged), 3, 3) + private fun inputsChanged() { + inputs.slotIterator().forEach { (it as InputSlot).insertCache.invalidateAll() } + seenRecipeManager = null + recipeCache = listOf() + setChanged() + } + + val inputs = IEnhancedCraftingContainer.Wrapper(SlottedContainer.simple(3 * 3, ::InputSlot, ::inputsChanged), 3, 3) val output = SlottedContainer.simple(1, AutomationFilters.ONLY_OUT.unlimitedSimpleProvider, ::markDirtyFast) val itemConfig = ConfigurableItemHandler( diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/ItemStackKey.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/ItemStackKey.kt new file mode 100644 index 000000000..875a6502c --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/ItemStackKey.kt @@ -0,0 +1,35 @@ +package ru.dbotthepony.mc.otm.core.util + +import it.unimi.dsi.fastutil.HashCommon +import net.minecraft.core.component.DataComponentMap +import net.minecraft.world.item.Item +import net.minecraft.world.item.ItemStack + +class ItemStackKey(val item: Item, val components: DataComponentMap) { + // make copy of original itemstack because there is no copy() method on DataComponentMap, which is returned by ItemStack#getComponents + constructor(itemStack: ItemStack) : this(itemStack.item, itemStack.copy().components) + + private var hashComputed = false + private var hash = 0 + + override fun equals(other: Any?): Boolean { + return this === other || other is ItemStackKey && other.item === item && other.components == components + } + + override fun hashCode(): Int { + if (!hashComputed) { + hash = HashCommon.murmurHash3(item.hashCode().toLong().shl(32) or components.hashCode().toLong()).toInt() + hashComputed = true + } + + return hash + } + + override fun toString(): String { + return "ItemStackKey[$item, $components]" + } +} + +fun ItemStack.asKey(): ItemStackKey { + return ItemStackKey(this) +}