From 2750941f8d249b5ab5e48e2ba73f9ced931a8208 Mon Sep 17 00:00:00 2001 From: KevinWoodWL Date: Tue, 19 May 2026 00:31:02 +0800 Subject: [PATCH 1/2] Cache display item names to eliminate per-search skull construction Problem: FilterType.BY_DISPLAY_ITEM_NAME called getDisplayRecipes() live on every Slimefun machine during each player search. getDisplayRecipes() clones SlimefunItemStacks, which triggers CraftMetaSkull.applyToItem and CraftPlayerProfile. for every skull-based display item. CraftPlayerProfile construction fires HTTP requests to Mojang sessionserver, causing 429 rate-limit errors and TPS drops on the server thread. Spark profiler confirmed CraftPlayerProfile. (91 nodes) and CraftMetaSkull (390 nodes) were all rooted in SearchGroup.filterItems via the BY_DISPLAY_ITEM_NAME filter. Fix: - SearchGroup: add DISPLAY_ITEM_NAMES_CACHE (Map>), a hard-reference map from Slimefun item ID to lower-cased display names from getDisplayRecipes(). Populated once during init() alongside CACHE2. - FilterType.BY_DISPLAY_ITEM_NAME: replace live getDisplayRecipes() calls with a lookup against DISPLAY_ITEM_NAMES_CACHE. No ItemStack clone, no CraftMetaSkull, no CraftPlayerProfile, no Mojang HTTP request at search. - Removed unused imports from FilterType (AContainer, MultiBlockMachine, RecipeDisplayItem, SpecialMenuProvider, Debug). - pom.xml: bump Lombok 1.18.34 -> 1.18.38 for JDK 25 compatibility. --- pom.xml | 4 +- .../balugaq/jeg/api/groups/SearchGroup.java | 51 ++++++++++++++----- .../jeg/api/objects/enums/FilterType.java | 49 ++++-------------- 3 files changed, 49 insertions(+), 55 deletions(-) diff --git a/pom.xml b/pom.xml index 277ccc37..2159e0f5 100644 --- a/pom.xml +++ b/pom.xml @@ -82,7 +82,7 @@ org.projectlombok lombok - 1.18.34 + 1.18.38 @@ -247,7 +247,7 @@ org.projectlombok lombok - 1.18.34 + 1.18.38 provided diff --git a/src/main/java/com/balugaq/jeg/api/groups/SearchGroup.java b/src/main/java/com/balugaq/jeg/api/groups/SearchGroup.java index 8e538a9d..c6cf60b5 100644 --- a/src/main/java/com/balugaq/jeg/api/groups/SearchGroup.java +++ b/src/main/java/com/balugaq/jeg/api/groups/SearchGroup.java @@ -113,6 +113,22 @@ public class SearchGroup extends BaseGroup { new Char2ObjectOpenHashMap<>(); // fast way for by display item name public static final Map>> SPECIAL_CACHE = new HashMap<>(); + /** + * Pre-built cache of display item names per Slimefun item ID. + * Populated once during {@link #init()} alongside CACHE2. + *

+ * Key: Slimefun item ID.
+ * Value: unmodifiable list of lower-cased display names from + * {@code getDisplayRecipes()}. Uses a hard reference (not SoftReference) + * so the cache is never evicted under memory pressure — the strings are + * cheap compared to retaining the live ItemStack objects. + *

+ * Consumed by {@link com.balugaq.jeg.api.objects.enums.FilterType#BY_DISPLAY_ITEM_NAME} + * to match display-item names without ever calling {@code getDisplayRecipes()} + * at search time. + */ + public static final Map> DISPLAY_ITEM_NAMES_CACHE = new ConcurrentHashMap<>(5000); + public static final Boolean SHOW_HIDDEN_ITEM_GROUPS = Slimefun.getConfigManager().isShowHiddenItemGroupsInSearch(); public static final Integer DEFAULT_HASH_SIZE = 5000; @@ -686,28 +702,37 @@ else if (Orecipes instanceof List recipes) { } } if (displayRecipes != null) { + List displayNames = new ArrayList<>(); for (ItemStack itemStack : displayRecipes) { if (itemStack != null) { String name2 = ChatColor.stripColor( ItemStackHelper.getDisplayName(itemStack)); - for (char c : name2.toCharArray()) { - char d = Character.toLowerCase(c); - CACHE2.putIfAbsent(d, new SoftReference<>(new HashSet<>())); - Reference> ref = CACHE2.get(d); - if (ref != null) { - Set set = ref.get(); - if (set == null) { - set = new HashSet<>(); - CACHE2.put(d, new SoftReference<>(set)); - } - if (!inBanlist(slimefunItem) - && !inBlacklist(slimefunItem)) { - set.add(slimefunItem); + if (!name2.isEmpty()) { + // Populate DISPLAY_ITEM_NAMES_CACHE for fast string-only lookup. + displayNames.add(name2.toLowerCase(Locale.ROOT)); + // Also populate the character-index CACHE2 as before. + for (char c : name2.toCharArray()) { + char d = Character.toLowerCase(c); + CACHE2.putIfAbsent(d, new SoftReference<>(new HashSet<>())); + Reference> ref = CACHE2.get(d); + if (ref != null) { + Set set = ref.get(); + if (set == null) { + set = new HashSet<>(); + CACHE2.put(d, new SoftReference<>(set)); + } + if (!inBanlist(slimefunItem) + && !inBlacklist(slimefunItem)) { + set.add(slimefunItem); + } } } } } } + if (!displayNames.isEmpty()) { + DISPLAY_ITEM_NAMES_CACHE.put(slimefunItem.getId(), List.copyOf(displayNames)); + } } String id = slimefunItem.getId(); diff --git a/src/main/java/com/balugaq/jeg/api/objects/enums/FilterType.java b/src/main/java/com/balugaq/jeg/api/objects/enums/FilterType.java index 0b43c9d0..1e829e37 100644 --- a/src/main/java/com/balugaq/jeg/api/objects/enums/FilterType.java +++ b/src/main/java/com/balugaq/jeg/api/objects/enums/FilterType.java @@ -29,15 +29,10 @@ import com.balugaq.jeg.api.groups.SearchGroup; import com.balugaq.jeg.api.objects.collection.Pair; -import com.balugaq.jeg.utils.Debug; import com.balugaq.jeg.utils.LocalHelper; -import com.balugaq.jeg.utils.SpecialMenuProvider; import io.github.thebusybiscuit.slimefun4.api.SlimefunAddon; import io.github.thebusybiscuit.slimefun4.api.items.SlimefunItem; -import io.github.thebusybiscuit.slimefun4.core.attributes.RecipeDisplayItem; -import io.github.thebusybiscuit.slimefun4.core.multiblocks.MultiBlockMachine; import lombok.Getter; -import me.mrCookieSlime.Slimefun.Objects.SlimefunItem.abstractItems.AContainer; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; @@ -91,44 +86,18 @@ public enum FilterType { BY_DISPLAY_ITEM_NAME( Set.of("%", "产"), (player, item, lowerFilterValue, pinyin) -> { - List display = null; - if (item instanceof AContainer ac) { - // Fix: Cannot search item when SlimeCustomizer crashed - try { - display = ac.getDisplayRecipes(); - } catch (Exception ignored) { - } - } else if (item instanceof MultiBlockMachine mb) { - // Fix: NullPointerException occurred when searching items from SlimeFood - try { - display = mb.getDisplayRecipes(); - } catch (Exception ignored) { - } - } else { - try { - if (SpecialMenuProvider.ENABLED_LogiTech - && SpecialMenuProvider.classLogiTech_CustomSlimefunItem != null - && SpecialMenuProvider.classLogiTech_CustomSlimefunItem.isInstance(item) - && item instanceof RecipeDisplayItem rdi) { - display = rdi.getDisplayRecipes(); - } - } catch (Exception e) { - Debug.trace(e, "searching"); - return false; - } - } - if (display != null) { - try { - for (ItemStack itemStack : display) { - if (SearchGroup.isSearchFilterApplicable(itemStack, lowerFilterValue, false)) { - return true; - } - } - } catch (Exception ignored) { - return false; + // Use the pre-built name cache populated during SearchGroup.init(). + // This avoids calling getDisplayRecipes() at search time, which would + // clone SlimefunItemStacks, construct CraftMetaSkull/CraftPlayerProfile + // objects, and ultimately fire Mojang sessionserver HTTP requests. + List cached = SearchGroup.DISPLAY_ITEM_NAMES_CACHE.get(item.getId()); + if (cached != null) { + for (String name : cached) { + if (name.contains(lowerFilterValue)) return true; } } + // SPECIAL_CACHE: addons may register extra searchable strings at runtime. String id = item.getId(); Reference> ref = SearchGroup.SPECIAL_CACHE.get(id); if (ref != null) { From 6f21affbc6a26c72e30dbf2bf798c80fc66adbe3 Mon Sep 17 00:00:00 2001 From: KevinWoodWL Date: Tue, 19 May 2026 01:35:52 +0800 Subject: [PATCH 2/2] Upgrade anvilgui 1.10.12-SNAPSHOT -> 1.10.13-SNAPSHOT Fixes NoClassDefFoundError on Leaf 1.21.11 (modern Paper, no versioned CraftBukkit packages). 1.10.13-SNAPSHOT ships Wrapper26_R1 which uses the un-versioned org.bukkit.craftbukkit.* import path that Leaf uses. --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 2159e0f5..fc45838b 100644 --- a/pom.xml +++ b/pom.xml @@ -365,7 +365,7 @@ net.wesjd anvilgui - 1.10.12-SNAPSHOT + 1.10.13-SNAPSHOT compile