feat: Allow breaking itemstacks even further for faster repo reloads

This commit is contained in:
Linnea Gräf
2025-06-22 16:09:43 +02:00
parent 7c45e48050
commit cbc8eff63a
19 changed files with 128 additions and 25 deletions

View File

@@ -13,18 +13,17 @@ import snownee.jade.api.ui.IElementHelper
import snownee.jade.impl.ui.ItemStackElement
import snownee.jade.impl.ui.TextElement
import kotlin.jvm.optionals.getOrDefault
import net.minecraft.item.ItemStack
import net.minecraft.item.Items
import net.minecraft.text.Text
import net.minecraft.util.Identifier
import net.minecraft.util.math.Vec2f
import moe.nea.firmament.Firmament
import moe.nea.firmament.repo.ItemCache.asItemStack
import moe.nea.firmament.repo.ExpensiveItemCacheApi
import moe.nea.firmament.repo.RepoManager
import moe.nea.firmament.repo.SBItemStack
import moe.nea.firmament.util.MC
class DrillToolProvider : IBlockComponentProvider {
@OptIn(ExpensiveItemCacheApi::class)
override fun appendTooltip(
tooltip: ITooltip,
accessor: BlockAccessor,

View File

@@ -29,6 +29,7 @@ import moe.nea.firmament.compat.rei.recipes.SBShopRecipe
import moe.nea.firmament.events.HandledScreenPushREIEvent
import moe.nea.firmament.features.inventory.CraftingOverlay
import moe.nea.firmament.features.inventory.storageoverlay.StorageOverlayScreen
import moe.nea.firmament.repo.ExpensiveItemCacheApi
import moe.nea.firmament.repo.RepoManager
import moe.nea.firmament.repo.SBItemStack
import moe.nea.firmament.repo.recipes.SBCraftingRecipeRenderer
@@ -44,6 +45,7 @@ import moe.nea.firmament.util.unformattedString
class FirmamentReiPlugin : REIClientPlugin {
companion object {
@ExpensiveItemCacheApi
fun EntryStack<SBItemStack>.asItemEntry(): EntryStack<ItemStack> {
return EntryStack.of(VanillaEntryTypes.ITEM, value.asImmutableItemStack())
}
@@ -51,6 +53,7 @@ class FirmamentReiPlugin : REIClientPlugin {
val SKYBLOCK_ITEM_TYPE_ID = Identifier.of("firmament", "skyblockitems")
}
@OptIn(ExpensiveItemCacheApi::class)
override fun registerTransferHandlers(registry: TransferHandlerRegistry) {
registry.register(TransferHandler { context ->
val screen = context.containerScreen

View File

@@ -17,10 +17,13 @@ import me.shedaniel.rei.api.common.entry.EntryStack
import net.fabricmc.fabric.api.client.item.v1.ItemTooltipCallback
import net.minecraft.client.MinecraftClient
import net.minecraft.client.gui.DrawContext
import net.minecraft.item.ItemStack
import net.minecraft.item.Items
import net.minecraft.item.tooltip.TooltipType
import net.minecraft.text.Text
import moe.nea.firmament.compat.rei.FirmamentReiPlugin.Companion.asItemEntry
import moe.nea.firmament.events.ItemTooltipEvent
import moe.nea.firmament.repo.ExpensiveItemCacheApi
import moe.nea.firmament.repo.ItemCache
import moe.nea.firmament.repo.RepoManager
import moe.nea.firmament.repo.SBItemStack
import moe.nea.firmament.util.ErrorUtil
@@ -32,6 +35,7 @@ import moe.nea.firmament.util.mc.loreAccordingToNbt
// TODO: make this re implement BatchedEntryRenderer, if possible (likely not, due to no-alloc rendering)
// Also it is probably not even that much faster now, with render layers.
object NEUItemEntryRenderer : EntryRenderer<SBItemStack> {
@OptIn(ExpensiveItemCacheApi::class)
override fun render(
entry: EntryStack<SBItemStack>,
context: DrawContext,
@@ -40,13 +44,20 @@ object NEUItemEntryRenderer : EntryRenderer<SBItemStack> {
mouseY: Int,
delta: Float
) {
val neuItem = entry.value.neuItem
val itemToRender = if(RepoManager.Config.perfectRenders < RepoManager.PerfectRender.RENDER && !entry.value.isWarm() && neuItem != null) {
ItemCache.recacheSoon(neuItem)
ItemStack(Items.PAINTING)
} else {
entry.value.asImmutableItemStack()
}
context.matrices.push()
context.matrices.translate(bounds.centerX.toFloat(), bounds.centerY.toFloat(), 0F)
context.matrices.scale(bounds.width.toFloat() / 16F, bounds.height.toFloat() / 16F, 1f)
val item = entry.asItemEntry().value
context.drawItemWithoutEntity(item, -8, -8)
context.drawItemWithoutEntity(itemToRender, -8, -8)
context.drawStackOverlay(
minecraft.textRenderer, item, -8, -8,
minecraft.textRenderer, itemToRender, -8, -8,
if (entry.value.getStackSize() > 1000) FirmFormatters.shortFormat(
entry.value.getStackSize()
.toDouble()
@@ -59,8 +70,9 @@ object NEUItemEntryRenderer : EntryRenderer<SBItemStack> {
val minecraft = MinecraftClient.getInstance()
var canUseVanillaTooltipEvents = true
@OptIn(ExpensiveItemCacheApi::class)
override fun getTooltip(entry: EntryStack<SBItemStack>, tooltipContext: TooltipContext): Tooltip? {
if (!entry.value.isWarm() && !RepoManager.Config.perfectTooltips) {
if (!entry.value.isWarm() && RepoManager.Config.perfectRenders < RepoManager.PerfectRender.RENDER_AND_TEXT) {
val neuItem = entry.value.neuItem
if (neuItem != null) {
val lore = mutableListOf<Text>()

View File

@@ -15,6 +15,7 @@ import net.minecraft.registry.tag.TagKey
import net.minecraft.text.Text
import net.minecraft.util.Identifier
import moe.nea.firmament.compat.rei.FirmamentReiPlugin.Companion.asItemEntry
import moe.nea.firmament.repo.ExpensiveItemCacheApi
import moe.nea.firmament.repo.RepoManager
import moe.nea.firmament.repo.SBItemStack
import moe.nea.firmament.util.SkyblockId
@@ -24,6 +25,7 @@ object SBItemEntryDefinition : EntryDefinition<SBItemStack> {
return o1.skyblockId == o2.skyblockId && o1.getStackSize() == o2.getStackSize()
}
@OptIn(ExpensiveItemCacheApi::class)
override fun cheatsAs(entry: EntryStack<SBItemStack>?, value: SBItemStack): ItemStack {
return value.asCopiedItemStack()
}
@@ -41,9 +43,10 @@ object SBItemEntryDefinition : EntryDefinition<SBItemStack> {
return Stream.empty()
}
@OptIn(ExpensiveItemCacheApi::class)
override fun asFormattedText(entry: EntryStack<SBItemStack>, value: SBItemStack): Text {
val neuItem = entry.value.neuItem
return if (RepoManager.Config.perfectTooltips || entry.value.isWarm() || neuItem == null) {
return if (RepoManager.Config.perfectRenders < RepoManager.PerfectRender.RENDER_AND_TEXT || entry.value.isWarm() || neuItem == null) {
VanillaEntryTypes.ITEM.definition.asFormattedText(entry.asItemEntry(), value.asImmutableItemStack())
} else {
Text.literal(neuItem.displayName)

View File

@@ -1,3 +1,5 @@
@file:OptIn(ExpensiveItemCacheApi::class)
package moe.nea.firmament.compat.rei.recipes
import java.util.Optional
@@ -27,6 +29,7 @@ import moe.nea.firmament.Firmament
import moe.nea.firmament.compat.rei.EntityWidget
import moe.nea.firmament.compat.rei.SBItemEntryDefinition
import moe.nea.firmament.gui.entity.EntityRenderer
import moe.nea.firmament.repo.ExpensiveItemCacheApi
import moe.nea.firmament.repo.Reforge
import moe.nea.firmament.repo.ReforgeStore
import moe.nea.firmament.repo.RepoItemTypeCache

View File

@@ -6,6 +6,7 @@ import net.minecraft.item.ItemStack
import net.minecraft.nbt.NbtCompound
import net.minecraft.util.Identifier
import moe.nea.firmament.Firmament
import moe.nea.firmament.repo.ExpensiveItemCacheApi
import moe.nea.firmament.repo.ItemCache
import moe.nea.firmament.util.MC
@@ -57,6 +58,7 @@ object LegacyItemData {
val enchantmentLut = enchantmentData.associateBy { Identifier.ofVanilla(it.name) }
val itemDat = getLegacyData<List<ItemData>>("items")
@OptIn(ExpensiveItemCacheApi::class) // This is fine, we get loaded in a thread.
val itemLut = itemDat.flatMap { item ->
item.allVariants().map { legacyItemType ->
val nbt = ItemCache.convert189ToModern(NbtCompound().apply {

View File

@@ -15,6 +15,7 @@ import moe.nea.firmament.events.WorldReadyEvent
import moe.nea.firmament.features.FirmamentFeature
import moe.nea.firmament.gui.config.ManagedConfig
import moe.nea.firmament.gui.hud.MoulConfigHud
import moe.nea.firmament.repo.ExpensiveItemCacheApi
import moe.nea.firmament.repo.ItemNameLookup
import moe.nea.firmament.repo.SBItemStack
import moe.nea.firmament.util.MC
@@ -202,7 +203,8 @@ object AnniversaryFeatures : FirmamentFeature {
SBItemStack(SkyblockId.NULL)
}
@Bind
@OptIn(ExpensiveItemCacheApi::class)
@Bind
fun name(): String {
return when (backedBy) {
is Reward.Coins -> "Coins"

View File

@@ -8,6 +8,7 @@ import moe.nea.firmament.annotations.Subscribe
import moe.nea.firmament.events.ScreenChangeEvent
import moe.nea.firmament.events.SlotRenderEvents
import moe.nea.firmament.features.FirmamentFeature
import moe.nea.firmament.repo.ExpensiveItemCacheApi
import moe.nea.firmament.repo.SBItemStack
import moe.nea.firmament.util.MC
import moe.nea.firmament.util.skyblockId
@@ -45,6 +46,7 @@ object CraftingOverlay : FirmamentFeature {
override val identifier: String
get() = "crafting-overlay"
@OptIn(ExpensiveItemCacheApi::class)
@Subscribe
fun onSlotRender(event: SlotRenderEvents.After) {
val slot = event.slot

View File

@@ -3,6 +3,7 @@ package moe.nea.firmament.features.inventory
import moe.nea.firmament.annotations.Subscribe
import moe.nea.firmament.events.HandledScreenKeyPressedEvent
import moe.nea.firmament.gui.config.ManagedConfig
import moe.nea.firmament.repo.ExpensiveItemCacheApi
import moe.nea.firmament.repo.HypixelStaticData
import moe.nea.firmament.repo.ItemCache
import moe.nea.firmament.repo.ItemCache.asItemStack
@@ -18,6 +19,7 @@ object ItemHotkeys {
val openGlobalTradeInterface by keyBindingWithDefaultUnbound("global-trade-interface")
}
@OptIn(ExpensiveItemCacheApi::class)
@Subscribe
fun onHandledInventoryPress(event: HandledScreenKeyPressedEvent) {
if (!event.matches(TConfig.openGlobalTradeInterface)) {

View File

@@ -11,6 +11,7 @@ import net.minecraft.command.argument.ItemStackArgumentType
import net.minecraft.item.ItemStack
import net.minecraft.resource.featuretoggle.FeatureFlags
import net.minecraft.util.Identifier
import moe.nea.firmament.repo.ExpensiveItemCacheApi
import moe.nea.firmament.repo.ItemCache.asItemStack
import moe.nea.firmament.repo.RepoManager
import moe.nea.firmament.util.MC
@@ -40,6 +41,7 @@ data class InventoryButton(
}
val dimensions = Dimension(18, 18)
val getItemForName = ::getItemForName0.memoize(1024)
@OptIn(ExpensiveItemCacheApi::class)
fun getItemForName0(icon: String): ItemStack {
val repoItem = RepoManager.getNEUItem(SkyblockId(icon))
var itemStack = repoItem.asItemStack(idHint = SkyblockId(icon))

View File

@@ -8,6 +8,7 @@ import net.minecraft.entity.LivingEntity
import net.minecraft.item.Item
import net.minecraft.item.ItemStack
import net.minecraft.item.Items
import moe.nea.firmament.repo.ExpensiveItemCacheApi
import moe.nea.firmament.repo.SBItemStack
import moe.nea.firmament.util.SkyblockId
import moe.nea.firmament.util.mc.setEncodedSkullOwner
@@ -31,6 +32,7 @@ object ModifyEquipment : EntityModifier {
return entity
}
@OptIn(ExpensiveItemCacheApi::class)
private fun createItem(item: String): ItemStack {
val split = item.split("#")
if (split.size != 2) return SBItemStack(SkyblockId(item)).asImmutableItemStack()

View File

@@ -0,0 +1,8 @@
package moe.nea.firmament.repo
/**
* Marker for functions that could potentially invoke DFU. Please do not call on a lot of objects at once, or try to make sure the item is cached and fall back to a more gentle function call using [SBItemStack.isWarm] and similar functions.
*/
@RequiresOptIn
@Retention(AnnotationRetention.BINARY)
annotation class ExpensiveItemCacheApi

View File

@@ -8,8 +8,14 @@ import java.text.NumberFormat
import java.util.UUID
import java.util.concurrent.ConcurrentHashMap
import org.apache.logging.log4j.LogManager
import kotlinx.coroutines.Job
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlin.jvm.optionals.getOrNull
import net.minecraft.SharedConstants
import net.minecraft.component.DataComponentTypes
@@ -30,6 +36,7 @@ import moe.nea.firmament.repo.RepoManager.initialize
import moe.nea.firmament.util.LegacyFormattingCode
import moe.nea.firmament.util.LegacyTagParser
import moe.nea.firmament.util.MC
import moe.nea.firmament.util.MinecraftDispatcher
import moe.nea.firmament.util.SkyblockId
import moe.nea.firmament.util.TestUtil
import moe.nea.firmament.util.directLiteralStringContent
@@ -40,6 +47,7 @@ import moe.nea.firmament.util.mc.loreAccordingToNbt
import moe.nea.firmament.util.mc.modifyLore
import moe.nea.firmament.util.mc.setCustomName
import moe.nea.firmament.util.mc.setSkullOwner
import moe.nea.firmament.util.skyblockId
import moe.nea.firmament.util.transformEachRecursively
object ItemCache : IReloadable {
@@ -56,7 +64,10 @@ object ItemCache : IReloadable {
putShort("Damage", damage.toShort())
}
@ExpensiveItemCacheApi
private fun NbtCompound.transformFrom10809ToModern() = convert189ToModern(this@transformFrom10809ToModern)
@ExpensiveItemCacheApi
fun convert189ToModern(nbtComponent: NbtCompound): NbtCompound? =
try {
df.update(
@@ -126,6 +137,7 @@ object ItemCache : IReloadable {
return base
}
@ExpensiveItemCacheApi
private fun NEUItem.asItemStackNow(): ItemStack {
try {
val oldItemTag = get10809CompoundTag()
@@ -150,6 +162,7 @@ object ItemCache : IReloadable {
return skyblockId.neuItem in cache
}
@ExpensiveItemCacheApi
fun NEUItem?.asItemStack(idHint: SkyblockId? = null, loreReplacements: Map<String, String>? = null): ItemStack {
if (this == null) return brokenItemStack(null, idHint)
var s = cache[this.skyblockItemId]
@@ -183,22 +196,43 @@ object ItemCache : IReloadable {
}
}
var job: Job? = null
var itemRecacheScope: CoroutineScope? = null
override fun reload(repository: NEURepository) {
val j = job
if (j != null && j.isActive) {
j.cancel()
private var recacheSoonSubmitted = mutableSetOf<SkyblockId>()
@OptIn(ExpensiveItemCacheApi::class)
fun recacheSoon(neuItem: NEUItem) {
itemRecacheScope?.launch {
if (!withContext(MinecraftDispatcher) {
recacheSoonSubmitted.add(neuItem.skyblockId)
}) {
return@launch
}
neuItem.asItemStack()
}
}
@OptIn(ExpensiveItemCacheApi::class)
override fun reload(repository: NEURepository) {
val j = itemRecacheScope
j?.cancel("New reload invoked")
cache.clear()
isFlawless = true
if (TestUtil.isInTest) return
job = Firmament.coroutineScope.launch {
val items = repository.items?.items ?: return@launch
items.values.forEach {
it.asItemStack() // Rebuild cache
}
val newScope =
CoroutineScope(Firmament.coroutineScope.coroutineContext + SupervisorJob(Firmament.globalJob) + Dispatchers.Default)
val items = repository.items?.items
newScope.launch {
val items = items ?: return@launch
items.values.chunked(500).map { chunk ->
async {
chunk.forEach {
it.asItemStack() // Rebuild cache
}
}
}.awaitAll()
}
itemRecacheScope = newScope
}
fun coinItem(coinAmount: Int): ItemStack {

View File

@@ -81,6 +81,7 @@ class MiningRepoData : IReloadable {
) {
@Transient
val dropItem = baseDrop?.let(::SBItemStack)
@OptIn(ExpensiveItemCacheApi::class)
private val labeledStack by lazy {
dropItem?.asCopiedItemStack()?.also(::markItemStack)
}
@@ -110,6 +111,7 @@ class MiningRepoData : IReloadable {
fun isActiveIn(location: SkyBlockIsland) = onlyIn == null || location in onlyIn
@OptIn(ExpensiveItemCacheApi::class)
private fun convertToModernBlock(): Block? {
// TODO: this should be in a shared util, really
val newCompound = ItemCache.convert189ToModern(NbtCompound().apply {

View File

@@ -11,6 +11,7 @@ import kotlinx.coroutines.launch
import net.minecraft.client.MinecraftClient
import net.minecraft.network.packet.s2c.play.SynchronizeRecipesS2CPacket
import net.minecraft.recipe.display.CuttingRecipeDisplay
import net.minecraft.util.StringIdentifiable
import moe.nea.firmament.Firmament
import moe.nea.firmament.Firmament.logger
import moe.nea.firmament.events.ReloadRegistrationEvent
@@ -46,7 +47,16 @@ object RepoManager {
}
val alwaysSuperCraft by toggle("enable-super-craft") { true }
var warnForMissingItemListMod by toggle("warn-for-missing-item-list-mod") { true }
val perfectTooltips by toggle("perfect-tooltips") { false }
val perfectRenders by choice("perfect-renders") { PerfectRender.RENDER }
}
enum class PerfectRender(val label: String) : StringIdentifiable {
NOTHING("nothing"),
RENDER("render"),
RENDER_AND_TEXT("text"),
;
override fun asString(): String? = label
}
val currentDownloadedSha by RepoDownloadManager::latestSavedVersionHash
@@ -136,8 +146,10 @@ object RepoManager {
} catch (exc: NEURepositoryException) {
ErrorUtil.softError("Failed to reload repository", exc)
MC.sendChat(
tr("firmament.repo.reloadfail",
"Failed to reload repository. This will result in some mod features not working.")
tr(
"firmament.repo.reloadfail",
"Failed to reload repository. This will result in some mod features not working."
)
)
}
}

View File

@@ -353,7 +353,9 @@ data class SBItemStack constructor(
}
// TODO: avoid instantiating the item stack here
@ExpensiveItemCacheApi
val itemType: ItemType? get() = ItemType.fromItemStack(asImmutableItemStack())
@ExpensiveItemCacheApi
val rarity: Rarity? get() = Rarity.fromItem(asImmutableItemStack())
private var itemStack_: ItemStack? = null
@@ -364,6 +366,7 @@ data class SBItemStack constructor(
group("power").toInt()
} ?: 0
@ExpensiveItemCacheApi
private val itemStack: ItemStack
get() {
val itemStack = itemStack_ ?: run {
@@ -437,15 +440,18 @@ data class SBItemStack constructor(
return false
}
@OptIn(ExpensiveItemCacheApi::class)
fun asLazyImmutableItemStack(): ItemStack? {
if (isWarm()) return asImmutableItemStack()
return null
}
fun asImmutableItemStack(): ItemStack {
@ExpensiveItemCacheApi
fun asImmutableItemStack(): ItemStack { // TODO: add a "or fallback to painting" option to asLazyImmutableItemStack to be used in more places.
return itemStack
}
@ExpensiveItemCacheApi
fun asCopiedItemStack(): ItemStack {
return itemStack.copy()
}

View File

@@ -9,6 +9,7 @@ import net.minecraft.text.Text
import net.minecraft.util.Identifier
import moe.nea.firmament.Firmament
import moe.nea.firmament.repo.EssenceRecipeProvider
import moe.nea.firmament.repo.ExpensiveItemCacheApi
import moe.nea.firmament.repo.RepoManager
import moe.nea.firmament.repo.SBItemStack
import moe.nea.firmament.util.SkyblockId
@@ -62,6 +63,7 @@ object SBEssenceUpgradeRecipeRenderer : GenericRecipeRenderer<EssenceRecipeProvi
return listOfNotNull(SBItemStack(recipe.itemId))
}
@OptIn(ExpensiveItemCacheApi::class)
override val icon: ItemStack get() = SBItemStack(SkyblockId("ESSENCE_WITHER")).asImmutableItemStack()
override val title: Text = tr("firmament.category.essence", "Essence Upgrades")
override val identifier: Identifier = Firmament.identifier("essence_upgrade")

View File

@@ -22,6 +22,7 @@ import net.minecraft.network.codec.PacketCodec
import net.minecraft.network.codec.PacketCodecs
import net.minecraft.util.Identifier
import moe.nea.firmament.repo.ExpLadders
import moe.nea.firmament.repo.ExpensiveItemCacheApi
import moe.nea.firmament.repo.ItemCache.asItemStack
import moe.nea.firmament.repo.set
import moe.nea.firmament.util.collections.WeakCache
@@ -86,6 +87,7 @@ value class SkyblockId(val neuItem: String) : Comparable<SkyblockId> {
val NEUItem.skyblockId get() = SkyblockId(skyblockItemId)
val NEUIngredient.skyblockId get() = SkyblockId(itemId)
@ExpensiveItemCacheApi
fun NEUItem.guessRecipeId(): String? {
if (!skyblockItemId.contains(";")) return skyblockItemId
val item = this.asItemStack()

View File

@@ -248,6 +248,11 @@
"firmament.config.repo.disable-item-groups.description": "Disabling item groups can increase performance, but will no longer collect similar items (like minions, enchantments) together.",
"firmament.config.repo.enable-super-craft": "Always use Super Craft",
"firmament.config.repo.enable-super-craft.description": "Always use super craft when clicking the craft button in REI, instead of just when holding shift.",
"firmament.config.repo.perfect-renders": "Perfect Render",
"firmament.config.repo.perfect-renders.choice.nothing": "Broken (Fastest)",
"firmament.config.repo.perfect-renders.choice.render": "Fixed Visual (Fast)",
"firmament.config.repo.perfect-renders.choice.render_and_text": "Perfect (Slowest)",
"firmament.config.repo.perfect-renders.description": "Speed up item list loading by allowing items to be loaded in partially incorrectly at first. They will be corrected down the line when the background reload completes.",
"firmament.config.repo.redownload": "Redownload Item List",
"firmament.config.repo.redownload.description": "Force re-download the item list. This is automatically done on restart.",
"firmament.config.repo.reload": "Reload Item List",