diff --git a/src/main/java/mezz/jei/gui/overlay/collapsible/GuiCollapsibleGroups.java b/src/main/java/mezz/jei/gui/overlay/collapsible/GuiCollapsibleGroups.java index 3def3922..464c75de 100644 --- a/src/main/java/mezz/jei/gui/overlay/collapsible/GuiCollapsibleGroups.java +++ b/src/main/java/mezz/jei/gui/overlay/collapsible/GuiCollapsibleGroups.java @@ -118,15 +118,24 @@ private void rebuildCards() { ? (List>) (List) Internal.getIngredientFilter().getIngredientList("") : Collections.emptyList(); + // Precompute UIDs once for all ingredients. + String[] elementUids = new String[ingredientList.size()]; + for (int i = 0; i < ingredientList.size(); i++) { + IIngredientListElement e = ingredientList.get(i); + @SuppressWarnings({"unchecked", "rawtypes"}) + String uid = ((mezz.jei.api.ingredients.IIngredientHelper) e.getIngredientHelper()).getUniqueId(e.getIngredient()); + elementUids[i] = uid; + } + for (CollapsibleGroup group : allGroups) { CollapsedGroupIngredient ingredient = group.getIngredient(); List> previewItems = new ArrayList<>(); int itemCount = 0; - for (IIngredientListElement element : ingredientList) { - if (ingredient.matches(element)) { + for (int i = 0; i < ingredientList.size(); i++) { + if (ingredient.matchesUid(elementUids[i])) { itemCount++; if (previewItems.size() < PREVIEW_FETCH_MAX) { - previewItems.add(element); + previewItems.add(ingredientList.get(i)); } } } @@ -584,14 +593,6 @@ private void renderIngredientTooltip(IIngredientListElement element, int } } - /** - * Called when returning from the editor screen to refresh the card list. - */ - public void onEditorClosed() { - rebuildCards(); - rebuildPageButtons(); - } - private static class GroupCardEntry { final String id; final String displayName; diff --git a/src/main/java/mezz/jei/gui/overlay/collapsible/GuiCustomGroupEditor.java b/src/main/java/mezz/jei/gui/overlay/collapsible/GuiCustomGroupEditor.java index 220d7fe4..a1e6c358 100644 --- a/src/main/java/mezz/jei/gui/overlay/collapsible/GuiCustomGroupEditor.java +++ b/src/main/java/mezz/jei/gui/overlay/collapsible/GuiCustomGroupEditor.java @@ -8,6 +8,7 @@ import mezz.jei.gui.ingredients.IIngredientListElement; import mezz.jei.ingredients.IngredientFilter; import mezz.jei.ingredients.group.CollapsedGroupIngredient; +import mezz.jei.ingredients.group.CollapsibleGroup; import mezz.jei.util.Translator; import net.minecraft.client.gui.GuiButton; import net.minecraft.client.gui.GuiScreen; @@ -90,11 +91,14 @@ public class GuiCustomGroupEditor extends GuiScreen { // Maps each right-panel element to its stored UID (exact or wildcard ending in ":*") private final Map, String> selectedStackToStoredUid = new HashMap<>(); - // Index of OTHER custom groups — used to tint items that already belong to a different group. + // Index of OTHER groups (any source: DEFAULT, MOD, CUSTOM) — used to tint items that already belong to a different group. // Built once in initGui(); key = exact UID, value = list of display names. private Map> otherGroupExactUids = new HashMap<>(); // [prefix, displayName] pairs for wildcard entries in other groups private List otherGroupWildcardPrefixes = new ArrayList<>(); + // Pre-computed set of UIDs (from filteredItems) that belong to another group — used for O(1) + // per-slot lookup in drawLeftGrid() instead of calling getOtherGroupNames() every frame. + private Set otherGroupUidCache = new HashSet<>(); // Drag-select state private boolean isDragging = false; @@ -219,17 +223,19 @@ private boolean isUidSelected(String normalUid) { } /** - * Builds a reverse index of all other custom groups for fast membership lookup + * Builds a reverse index of all other groups (DEFAULT, MOD, and CUSTOM) for fast membership lookup. */ private void buildOtherGroupIndex() { otherGroupExactUids.clear(); otherGroupWildcardPrefixes.clear(); - CustomGroupsConfig cfg = Config.getCustomGroupsConfig(); - if (cfg == null) return; - for (CustomGroupsConfig.CustomGroup g : cfg.getCustomGroups()) { - if (g.id.equals(group.id) || g.itemUids == null) continue; - String name = (g.displayName != null && !g.displayName.isEmpty()) ? g.displayName : g.id; - for (String uid : g.itemUids) { + otherGroupUidCache.clear(); + Map allGroups = Internal.getCollapsedGroupRegistry().getAllGroups(); + for (Map.Entry entry : allGroups.entrySet()) { + if (entry.getKey().equals(group.id)) continue; + CollapsedGroupIngredient ingredient = entry.getValue().getIngredient(); + String name = ingredient.getDisplayName(); + if (name == null || name.isEmpty()) name = entry.getKey(); + for (String uid : ingredient.getUids()) { if (uid.endsWith(":*")) { otherGroupWildcardPrefixes.add(new String[]{uid.substring(0, uid.length() - 2), name}); } else { @@ -237,13 +243,31 @@ private void buildOtherGroupIndex() { } } } + rebuildOtherGroupUidCache(); } - /** Returns the names of other custom groups that contain the given normal UID, or an empty list. */ + private void rebuildOtherGroupUidCache() { + otherGroupUidCache.clear(); + for (IIngredientListElement element : filteredItems) { + String uid = getIngredientUid(element.getIngredient()); + if (!otherGroupExactUids.isEmpty() && otherGroupExactUids.containsKey(uid)) { + otherGroupUidCache.add(uid); + } else { + for (String[] entry : otherGroupWildcardPrefixes) { + if (uid.startsWith(entry[0]) && + (uid.length() == entry[0].length() || uid.charAt(entry[0].length()) == ':')) { + otherGroupUidCache.add(uid); + break; + } + } + } + } + } private List getOtherGroupNames(String normalUid) { List names = new ArrayList<>(otherGroupExactUids.getOrDefault(normalUid, Collections.emptyList())); for (String[] entry : otherGroupWildcardPrefixes) { - if (normalUid.equals(entry[0]) || normalUid.startsWith(entry[0] + ":")) { + if (normalUid.startsWith(entry[0]) && + (normalUid.length() == entry[0].length() || normalUid.charAt(entry[0].length()) == ':')) { names.add(entry[1]); } } @@ -345,6 +369,7 @@ private void updateFilteredItems() { if (leftPage >= leftTotalPages) { leftPage = leftTotalPages - 1; } + rebuildOtherGroupUidCache(); } private void updateSelectedStacks() { @@ -443,7 +468,6 @@ private void saveAndClose() { filter.notifyListenersOfChange(); } } - parentScreen.onEditorClosed(); this.mc.displayGuiScreen(parentScreen); } @@ -560,7 +584,7 @@ private void drawLeftGrid(int mouseX, int mouseY) { // Orange tint if this item belongs to another custom group String uid = getIngredientUid(ingredient); - if (!getOtherGroupNames(uid).isEmpty()) { + if (otherGroupUidCache.contains(uid)) { RenderHelper.disableStandardItemLighting(); GlStateManager.disableDepth(); GlStateManager.colorMask(true, true, true, false); diff --git a/src/main/java/mezz/jei/ingredients/group/CollapsedGroupIngredient.java b/src/main/java/mezz/jei/ingredients/group/CollapsedGroupIngredient.java index 0135dc2a..d2209ad3 100644 --- a/src/main/java/mezz/jei/ingredients/group/CollapsedGroupIngredient.java +++ b/src/main/java/mezz/jei/ingredients/group/CollapsedGroupIngredient.java @@ -43,6 +43,8 @@ public enum GroupSource { private final GroupSource source; private final List> filterElements; private final Set uids; + /** Pre-extracted from {@code uids}: the prefix before {@code :*} for each wildcard entry. */ + private final List wildcardPrefixes; private final int backgroundColor; private final int borderColor; @@ -61,6 +63,17 @@ public CollapsedGroupIngredient(String id, String langKey, int backgroundColor, this.filterElements = new ArrayList<>(uids.size()); this.backgroundColor = backgroundColor; this.borderColor = borderColor; + // Pre-extract wildcard prefixes so matches() doesn't scan all UIDs on every call + List wc = null; + for (String uid : uids) { + if (uid.endsWith(":*")) { + if (wc == null) { + wc = new ArrayList<>(); + } + wc.add(uid.substring(0, uid.length() - 2)); + } + } + this.wildcardPrefixes = wc != null ? Collections.unmodifiableList(wc) : Collections.emptyList(); } public String getId() { @@ -97,15 +110,21 @@ public void toggleExpanded() { public boolean matches(IIngredientListElement element) { String uid = element.getIngredientHelper().getUniqueId(element.getIngredient()); + return matchesUid(uid); + } + + /** + * Matches against a pre-computed UID string. + * Prefer this overload when checking many elements to avoid recomputing the UID per group. + */ + public boolean matchesUid(String uid) { if (this.uids.contains(uid)) { return true; } - for (String stored : this.uids) { - if (stored.endsWith(":*")) { - String prefix = stored.substring(0, stored.length() - 2); - if (uid.equals(prefix) || uid.startsWith(prefix + ":")) { - return true; - } + for (String prefix : this.wildcardPrefixes) { + if (uid.startsWith(prefix) && + (uid.length() == prefix.length() || uid.charAt(prefix.length()) == ':')) { + return true; } } return false;