From 8afa32108f3725de0fe8a1c4db599663bca5dd92 Mon Sep 17 00:00:00 2001 From: XxFran10xX <318299142+XxFran10xX@users.noreply.github.com> Date: Thu, 29 Oct 2026 14:05:55 +0100 Subject: [PATCH] perf: read the artifact key through the stack's data view The TLibs item scan calls ArtifactAttuneScanHandler.matches() on every non-air slot ten times a second. ArtifactIds.hasKey copied the whole item meta to test one key; it now reads the key through the read-only ItemStack#getPersistentDataContainer view, with the same answers for items with and without meta. Co-Authored-By: Claude Opus 5.5 --- .../magic/artifact/ArtifactIds.java | 6 +- .../magic/ChargeEdgeCoverageTest.java | 2 + .../artifact/ArtifactIdsKeyViewTest.java | 91 +++++++++++++++++++ 3 files changed, 96 insertions(+), 3 deletions(-) create mode 100644 src/test/java/net/tfminecraft/magic/artifact/ArtifactIdsKeyViewTest.java diff --git a/src/main/java/net/tfminecraft/magic/artifact/ArtifactIds.java b/src/main/java/net/tfminecraft/magic/artifact/ArtifactIds.java index bf7c93f..b8db9ab 100644 --- a/src/main/java/net/tfminecraft/magic/artifact/ArtifactIds.java +++ b/src/main/java/net/tfminecraft/magic/artifact/ArtifactIds.java @@ -10,12 +10,12 @@ public final class ArtifactIds { private ArtifactIds() {} + // The item scan runs this on every slot: read the key through the stack's data view, not a full meta copy. public static boolean hasKey(ItemStack stack) { - if (stack == null || stack.getType().isAir() || !stack.hasItemMeta()) { + if (stack == null || stack.getType().isAir()) { return false; } - ItemMeta meta = stack.getItemMeta(); - return meta.getPersistentDataContainer().has( + return stack.getPersistentDataContainer().has( ArtifactKeys.artifactId(), PersistentDataType.STRING); } diff --git a/src/test/java/net/tfminecraft/magic/ChargeEdgeCoverageTest.java b/src/test/java/net/tfminecraft/magic/ChargeEdgeCoverageTest.java index 3385354..3aa1041 100644 --- a/src/test/java/net/tfminecraft/magic/ChargeEdgeCoverageTest.java +++ b/src/test/java/net/tfminecraft/magic/ChargeEdgeCoverageTest.java @@ -46,6 +46,8 @@ ItemStack chargeItem() { void identifiersRejectEmptyMetadataMalformedIdsAndZeroTiers() { var noMeta = mock(ItemStack.class); when(noMeta.getType()).thenReturn(Material.STONE); + var emptyData = new ItemStack(Material.STONE).getPersistentDataContainer(); + when(noMeta.getPersistentDataContainer()).thenReturn(emptyData); assertFalse(ArtifactIds.hasKey(noMeta)); assertFalse(ArtifactIds.hasKey(new ItemStack(Material.AIR))); assertFalse(ChargeIds.isCharge(noMeta)); diff --git a/src/test/java/net/tfminecraft/magic/artifact/ArtifactIdsKeyViewTest.java b/src/test/java/net/tfminecraft/magic/artifact/ArtifactIdsKeyViewTest.java new file mode 100644 index 0000000..57a7615 --- /dev/null +++ b/src/test/java/net/tfminecraft/magic/artifact/ArtifactIdsKeyViewTest.java @@ -0,0 +1,91 @@ +package net.tfminecraft.magic.artifact; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; + +import net.tfminecraft.magic.Magic; +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.persistence.PersistentDataContainer; +import org.bukkit.persistence.PersistentDataType; +import org.junit.jupiter.api.*; +import org.mockbukkit.mockbukkit.MockBukkit; + +/** The scan's artifact check reads the stack's data view and answers as the old full-meta read did. */ +class ArtifactIdsKeyViewTest { + + @BeforeEach + void setup() { + MockBukkit.mock(); + Magic.plugin = mock(Magic.class); + when(Magic.plugin.namespace()).thenReturn("magic"); + } + + @AfterEach + void cleanup() { + Magic.plugin = null; + MockBukkit.unmock(); + } + + // origin/main ArtifactIds.hasKey + private static boolean oldHasKey(ItemStack stack) { + if (stack == null || stack.getType().isAir() || !stack.hasItemMeta()) { + return false; + } + ItemMeta meta = stack.getItemMeta(); + return meta.getPersistentDataContainer().has(ArtifactKeys.artifactId(), PersistentDataType.STRING); + } + + private static ItemStack stack(Material material, Consumer edit) { + ItemStack stack = new ItemStack(material); + ItemMeta meta = stack.getItemMeta(); + edit.accept(meta.getPersistentDataContainer()); + stack.setItemMeta(meta); + return stack; + } + + @Test + void hasKeyMatchesExactlyWhatTheFullMetaReadMatched() { + Map samples = new LinkedHashMap<>(); + samples.put("null", null); + samples.put("air", new ItemStack(Material.AIR)); + for (Material material : List.of(Material.STONE, Material.AMETHYST_SHARD, Material.PAPER)) { + String name = material.name().toLowerCase(); + samples.put(name + " without meta", new ItemStack(material)); + ItemStack unbreakable = new ItemStack(material); + ItemMeta meta = unbreakable.getItemMeta(); + meta.setUnbreakable(true); + unbreakable.setItemMeta(meta); + samples.put(name + " with meta but no data", unbreakable); + ItemStack written = new ItemStack(material); + ArtifactIds.writeNew(written); + samples.put(name + " artifact", written); + samples.put(name + " blank id", stack(material, + d -> d.set(ArtifactKeys.artifactId(), PersistentDataType.STRING, " "))); + samples.put(name + " id stored as a number", stack(material, + d -> d.set(ArtifactKeys.artifactId(), PersistentDataType.INTEGER, 4))); + samples.put(name + " other artifact key only", stack(material, + d -> d.set(ArtifactKeys.artifactSpent(), PersistentDataType.STRING, "old"))); + samples.put(name + " id under another plugin", stack(material, + d -> d.set(new NamespacedKey("other", "artifact_id"), PersistentDataType.STRING, "x"))); + } + + List matched = new ArrayList<>(); + var handler = new ArtifactAttuneScanHandler(); + samples.forEach((name, stack) -> { + assertEquals(oldHasKey(stack), ArtifactIds.hasKey(stack), name); + assertEquals(ArtifactIds.hasKey(stack), handler.matches(stack), name); + if (ArtifactIds.hasKey(stack)) matched.add(name); + }); + assertEquals(List.of("stone artifact", "stone blank id", "amethyst_shard artifact", + "amethyst_shard blank id", "paper artifact", "paper blank id"), matched); + } +}