瀏覽代碼

Fix #385 and optimise favourites rendering

Signed-off-by: shedaniel <daniel@shedaniel.me>
shedaniel 5 年之前
父節點
當前提交
59d9c8f1bb

+ 1 - 1
gradle.properties

@@ -1,5 +1,5 @@
 org.gradle.jvmargs=-Xmx3G
-mod_version=4.10.1
+mod_version=4.10.2
 supported_version=1.16.x
 minecraft_version=1.16.1
 yarn_version=1.16.1+build.4+legacy.20w09a+build.8

+ 3 - 2
src/main/java/me/shedaniel/rei/api/RecipeHelper.java

@@ -134,10 +134,11 @@ public interface RecipeHelper {
      * @param display            the recipe display
      * @deprecated Use {@link RecipeHelper#registerDisplay(RecipeDisplay)}
      */
-    @ApiStatus.Internal
     @ApiStatus.ScheduledForRemoval
     @Deprecated
-    void registerDisplay(Identifier categoryIdentifier, RecipeDisplay display);
+    default void registerDisplay(Identifier categoryIdentifier, RecipeDisplay display) {
+        registerDisplay(display);
+    }
     
     Map<RecipeCategory<?>, List<RecipeDisplay>> buildMapFor(ClientHelper.ViewSearchBuilder builder);
     

+ 41 - 12
src/main/java/me/shedaniel/rei/gui/ContainerScreenOverlay.java

@@ -690,35 +690,57 @@ public class ContainerScreenOverlay extends WidgetWithBounds implements REIOverl
     }
     
     @Override
-    public boolean mouseClicked(double double_1, double double_2, int int_1) {
+    public boolean mouseClicked(double mouseX, double mouseY, int button) {
+        if (ConfigObject.getInstance().getHideKeybind().matchesMouse(button)) {
+            ScreenHelper.toggleOverlayVisible();
+            return true;
+        }
+        EntryStack stack = RecipeHelper.getInstance().getScreenFocusedStack(MinecraftClient.getInstance().currentScreen);
+        if (stack != null && !stack.isEmpty()) {
+            stack = stack.copy();
+            if (ConfigObject.getInstance().getRecipeKeybind().matchesMouse(button)) {
+                return ClientHelper.getInstance().openView(ClientHelper.ViewSearchBuilder.builder().addRecipesFor(stack).setOutputNotice(stack).fillPreferredOpenedCategory());
+            } else if (ConfigObject.getInstance().getUsageKeybind().matchesMouse(button)) {
+                return ClientHelper.getInstance().openView(ClientHelper.ViewSearchBuilder.builder().addUsagesFor(stack).setInputNotice(stack).fillPreferredOpenedCategory());
+            } else if (ConfigObject.getInstance().getFavoriteKeyCode().matchesMouse(button)) {
+                stack.setAmount(127);
+                if (!CollectionUtils.anyMatchEqualsEntryIgnoreAmount(ConfigObject.getInstance().getFavorites(), stack))
+                    ConfigObject.getInstance().getFavorites().add(stack);
+                ConfigManager.getInstance().saveConfig();
+                FavoritesListWidget favoritesListWidget = ContainerScreenOverlay.getFavoritesListWidget();
+                if (favoritesListWidget != null)
+                    favoritesListWidget.updateSearch(ContainerScreenOverlay.getEntryListWidget(), ScreenHelper.getSearchField().getText());
+                return true;
+            }
+        }
         if (!ScreenHelper.isOverlayVisible())
             return false;
-        if (wrappedSubsetsMenu != null && wrappedSubsetsMenu.mouseClicked(double_1, double_2, int_1)) {
+        if (wrappedSubsetsMenu != null && wrappedSubsetsMenu.mouseClicked(mouseX, mouseY, button)) {
             this.setFocused(wrappedSubsetsMenu);
-            if (int_1 == 0)
+            if (button == 0)
                 this.setDragging(true);
             ScreenHelper.getSearchField().setFocused(false);
             return true;
         }
         if (wrappedWeatherMenu != null) {
-            if (wrappedWeatherMenu.mouseClicked(double_1, double_2, int_1)) {
+            if (wrappedWeatherMenu.mouseClicked(mouseX, mouseY, button)) {
                 this.setFocused(wrappedWeatherMenu);
-                if (int_1 == 0)
+                if (button == 0)
                     this.setDragging(true);
                 ScreenHelper.getSearchField().setFocused(false);
                 return true;
-            } else if (!wrappedWeatherMenu.containsMouse(double_1, double_2) && !weatherButton.containsMouse(double_1, double_2)) {
+            } else if (!wrappedWeatherMenu.containsMouse(mouseX, mouseY) && !weatherButton.containsMouse(mouseX, mouseY)) {
                 removeWeatherMenu();
             }
         }
         if (wrappedGameModeMenu != null) {
-            if (wrappedGameModeMenu.mouseClicked(double_1, double_2, int_1)) {
+            if (wrappedGameModeMenu.mouseClicked(mouseX, mouseY, button)) {
                 this.setFocused(wrappedGameModeMenu);
-                if (int_1 == 0)
+                if (button == 0)
                     this.setDragging(true);
                 ScreenHelper.getSearchField().setFocused(false);
                 return true;
-            } else if (!wrappedGameModeMenu.containsMouse(double_1, double_2) && !gameModeButton.containsMouse(double_1, double_2)) {
+            } else if (!wrappedGameModeMenu.containsMouse(mouseX, mouseY) && !gameModeButton.containsMouse(mouseX, mouseY)) {
                 removeGameModeMenu();
             }
         }
@@ -726,21 +748,28 @@ public class ContainerScreenOverlay extends WidgetWithBounds implements REIOverl
             ContainerScreen<?> containerScreen = (ContainerScreen<?>) MinecraftClient.getInstance().currentScreen;
             for (RecipeHelper.ScreenClickArea area : RecipeHelper.getInstance().getScreenClickAreas())
                 if (area.getScreenClass().equals(containerScreen.getClass()))
-                    if (area.getRectangle().contains(double_1 - containerScreen.x, double_2 - containerScreen.y)) {
+                    if (area.getRectangle().contains(mouseX - containerScreen.x, mouseY - containerScreen.y)) {
                         ClientHelper.getInstance().executeViewAllRecipesFromCategories(Arrays.asList(area.getCategories()));
                         MinecraftClient.getInstance().getSoundManager().play(PositionedSoundInstance.master(SoundEvents.UI_BUTTON_CLICK, 1.0F));
                         return true;
                     }
         }
         for (Element element : widgets)
-            if (element != wrappedSubsetsMenu && element != wrappedWeatherMenu && element != wrappedGameModeMenu && element.mouseClicked(double_1, double_2, int_1)) {
+            if (element != wrappedSubsetsMenu && element != wrappedWeatherMenu && element != wrappedGameModeMenu && element.mouseClicked(mouseX, mouseY, button)) {
                 this.setFocused(element);
-                if (int_1 == 0)
+                if (button == 0)
                     this.setDragging(true);
                 if (!(element instanceof OverlaySearchField))
                     ScreenHelper.getSearchField().setFocused(false);
                 return true;
             }
+        if (ConfigObject.getInstance().getFocusSearchFieldKeybind().matchesMouse(button)) {
+            ScreenHelper.getSearchField().setFocused(true);
+            setFocused(ScreenHelper.getSearchField());
+            ScreenHelper.getSearchField().keybindFocusTime = -1;
+            ScreenHelper.getSearchField().keybindFocusKey = -1;
+            return true;
+        }
         return false;
     }
     

+ 31 - 11
src/main/java/me/shedaniel/rei/gui/RecipeViewingScreen.java

@@ -431,7 +431,7 @@ public class RecipeViewingScreen extends Screen implements RecipeScreen {
         ScreenHelper.getLastOverlay().lateRender(matrices, mouseX, mouseY, delta);
         {
             ModifierKeyCode export = ConfigObject.getInstance().getExportImageKeybind();
-            if (export.matchesCurrentKey()) {
+            if (export.matchesCurrentKey() || export.matchesCurrentMouse()) {
                 for (Map.Entry<Rectangle, List<Widget>> entry : recipeBounds.entrySet()) {
                     Rectangle bounds = entry.getKey();
                     setZOffset(470);
@@ -506,14 +506,25 @@ public class RecipeViewingScreen extends Screen implements RecipeScreen {
     }
     
     @Override
-    public boolean mouseReleased(double double_1, double double_2, int int_1) {
+    public boolean mouseReleased(double mouseX, double mouseY, int button) {
         if (choosePageActivated) {
-            return recipeChoosePageWidget.mouseReleased(double_1, double_2, int_1);
+            return recipeChoosePageWidget.mouseReleased(mouseX, mouseY, button);
+        } else {
+            ModifierKeyCode export = ConfigObject.getInstance().getExportImageKeybind();
+            if (export.matchesMouse(button)) {
+                for (Map.Entry<Rectangle, List<Widget>> entry : recipeBounds.entrySet()) {
+                    Rectangle bounds = entry.getKey();
+                    if (bounds.contains(PointHelper.ofMouse())) {
+                        RecipeDisplayExporter.exportRecipeDisplay(bounds, entry.getValue());
+                        break;
+                    }
+                }
+            }
         }
         for (Element entry : children())
-            if (entry.mouseReleased(double_1, double_2, int_1))
+            if (entry.mouseReleased(mouseX, mouseY, button))
                 return true;
-        return super.mouseReleased(double_1, double_2, int_1);
+        return super.mouseReleased(mouseX, mouseY, button);
     }
     
     @Override
@@ -537,19 +548,28 @@ public class RecipeViewingScreen extends Screen implements RecipeScreen {
     }
     
     @Override
-    public boolean mouseClicked(double double_1, double double_2, int int_1) {
-        if (choosePageActivated)
-            if (recipeChoosePageWidget.containsMouse(double_1, double_2)) {
-                return recipeChoosePageWidget.mouseClicked(double_1, double_2, int_1);
+    public boolean mouseClicked(double mouseX, double mouseY, int button) {
+        if (choosePageActivated) {
+            if (recipeChoosePageWidget.containsMouse(mouseX, mouseY)) {
+                return recipeChoosePageWidget.mouseClicked(mouseX, mouseY, button);
             } else {
                 choosePageActivated = false;
                 init();
                 return false;
             }
+        } else if (ConfigObject.getInstance().getNextPageKeybind().matchesMouse(button)) {
+            if (recipeNext.isEnabled())
+                recipeNext.onClick();
+            return recipeNext.isEnabled();
+        } else if (ConfigObject.getInstance().getPreviousPageKeybind().matchesMouse(button)) {
+            if (recipeBack.isEnabled())
+                recipeBack.onClick();
+            return recipeBack.isEnabled();
+        }
         for (Element entry : children())
-            if (entry.mouseClicked(double_1, double_2, int_1)) {
+            if (entry.mouseClicked(mouseX, mouseY, button)) {
                 setFocused(entry);
-                if (int_1 == 0)
+                if (button == 0)
                     setDragging(true);
                 return true;
             }

+ 19 - 5
src/main/java/me/shedaniel/rei/gui/VillagerRecipeViewingScreen.java

@@ -253,19 +253,32 @@ public class VillagerRecipeViewingScreen extends Screen implements RecipeScreen
     }
     
     @Override
-    public boolean mouseClicked(double mouseX, double mouseY, int int_1) {
-        if (scrolling.updateDraggingState(mouseX, mouseY, int_1)) {
+    public boolean mouseClicked(double mouseX, double mouseY, int button) {
+        if (scrolling.updateDraggingState(mouseX, mouseY, button)) {
             scrollBarAlpha = 1;
             return true;
         }
+        if (ConfigObject.getInstance().getNextPageKeybind().matchesMouse(button)) {
+            selectedRecipeIndex++;
+            if (selectedRecipeIndex >= categoryMap.get(categories.get(selectedCategoryIndex)).size())
+                selectedRecipeIndex = 0;
+            init();
+            return true;
+        } else if (ConfigObject.getInstance().getPreviousPageKeybind().matchesMouse(button)) {
+            selectedRecipeIndex--;
+            if (selectedRecipeIndex < 0)
+                selectedRecipeIndex = categoryMap.get(categories.get(selectedCategoryIndex)).size() - 1;
+            init();
+            return true;
+        }
         for (Element entry : children())
-            if (entry.mouseClicked(mouseX, mouseY, int_1)) {
+            if (entry.mouseClicked(mouseX, mouseY, button)) {
                 setFocused(entry);
-                if (int_1 == 0)
+                if (button == 0)
                     setDragging(true);
                 return true;
             }
-        return super.mouseClicked(mouseX, mouseY, int_1);
+        return super.mouseClicked(mouseX, mouseY, button);
     }
     
     @Override
@@ -296,6 +309,7 @@ public class VillagerRecipeViewingScreen extends Screen implements RecipeScreen
                 if (selectedRecipeIndex >= categoryMap.get(categories.get(selectedCategoryIndex)).size())
                     selectedRecipeIndex = 0;
                 init();
+                return true;
             } else if (categoryMap.get(categories.get(selectedCategoryIndex)).size() > 1) {
                 selectedRecipeIndex--;
                 if (selectedRecipeIndex < 0)

+ 114 - 175
src/main/java/me/shedaniel/rei/gui/widget/EntryListWidget.java

@@ -64,6 +64,7 @@ import java.util.Set;
 import java.util.concurrent.*;
 import java.util.function.Supplier;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 @ApiStatus.Internal
 public class EntryListWidget extends WidgetWithBounds {
@@ -94,6 +95,11 @@ public class EntryListWidget extends WidgetWithBounds {
     };
     protected int blockedCount;
     private boolean debugTime;
+    private double lastAverageDebugTime;
+    private double averageDebugTime;
+    private double lastTotalDebugTime;
+    private double totalDebugTime;
+    private float totalDebugTimeDelta;
     private Rectangle bounds, innerBounds;
     private List<EntryStack> allStacks = null;
     private List<EntryListEntry> entries = Collections.emptyList();
@@ -111,10 +117,10 @@ public class EntryListWidget extends WidgetWithBounds {
         for (OverlayDecider decider : DisplayHelper.getInstance().getSortedOverlayDeciders(instance.currentScreen.getClass())) {
             ActionResult fit = decider.isInZone(left, top);
             if (fit == ActionResult.FAIL)
-                return fit == ActionResult.SUCCESS;
+                return false;
             ActionResult fit2 = decider.isInZone(left + 18, top + 18);
             if (fit2 == ActionResult.FAIL)
-                return fit == ActionResult.SUCCESS;
+                return false;
             if (fit == ActionResult.SUCCESS && fit2 == ActionResult.SUCCESS)
                 return true;
         }
@@ -170,199 +176,132 @@ public class EntryListWidget extends WidgetWithBounds {
         return MathHelper.ceil(allStacks.size() / (float) entries.size());
     }
     
+    public static void renderEntries(boolean debugTime, int[] size, long[] time, boolean fastEntryRendering, MatrixStack matrices, int mouseX, int mouseY, float delta, List<EntryListEntryWidget> entries) {
+        if (entries.isEmpty()) return;
+        EntryListEntryWidget firstWidget = entries.get(0);
+        EntryStack first = firstWidget.getCurrentEntry();
+        if (fastEntryRendering && first instanceof OptimalEntryStack) {
+            OptimalEntryStack firstStack = (OptimalEntryStack) first;
+            firstStack.optimisedRenderStart(matrices, delta);
+            long l = debugTime ? System.nanoTime() : 0;
+            VertexConsumerProvider.Immediate immediate = MinecraftClient.getInstance().getBufferBuilders().getEntityVertexConsumers();
+            for (EntryListEntryWidget listEntry : entries) {
+                EntryStack currentEntry = listEntry.getCurrentEntry();
+                currentEntry.setZ(100);
+                listEntry.drawBackground(matrices, mouseX, mouseY, delta);
+                ((OptimalEntryStack) currentEntry).optimisedRenderBase(matrices, immediate, listEntry.getInnerBounds(), mouseX, mouseY, delta);
+                if (debugTime && !currentEntry.isEmpty()) size[0]++;
+            }
+            immediate.draw();
+            for (EntryListEntryWidget listEntry : entries) {
+                EntryStack currentEntry = listEntry.getCurrentEntry();
+                ((OptimalEntryStack) currentEntry).optimisedRenderOverlay(matrices, listEntry.getInnerBounds(), mouseX, mouseY, delta);
+                if (listEntry.containsMouse(mouseX, mouseY)) {
+                    listEntry.queueTooltip(matrices, mouseX, mouseY, delta);
+                    listEntry.drawHighlighted(matrices, mouseX, mouseY, delta);
+                }
+            }
+            if (debugTime) time[0] += (System.nanoTime() - l);
+            firstStack.optimisedRenderEnd(matrices, delta);
+        } else {
+            for (EntryListEntryWidget entry : entries) {
+                if (entry.getCurrentEntry().isEmpty())
+                    continue;
+                if (debugTime) {
+                    size[0]++;
+                    long l = System.nanoTime();
+                    entry.render(matrices, mouseX, mouseY, delta);
+                    time[0] += (System.nanoTime() - l);
+                } else entry.render(matrices, mouseX, mouseY, delta);
+            }
+        }
+    }
+    
     @Override
     public void render(MatrixStack matrices, int mouseX, int mouseY, float delta) {
+        int[] size = {0};
+        long[] time = {0};
+        long totalTimeStart = debugTime ? System.nanoTime() : 0;
+        boolean fastEntryRendering = ConfigObject.getInstance().doesFastEntryRendering();
         if (ConfigObject.getInstance().isEntryListWidgetScrolled()) {
             for (EntryListEntry entry : entries)
                 entry.clearStacks();
             ScissorsHandler.INSTANCE.scissor(bounds);
+            
             int skip = Math.max(0, MathHelper.floor(scrolling.scrollAmount / (float) entrySize()));
             int nextIndex = skip * innerBounds.width / entrySize();
-            int i = nextIndex;
+            int[] i = {nextIndex};
             blockedCount = 0;
-            if (debugTime) {
-                long totalTimeStart = System.nanoTime();
-                int size = 0;
-                long time = 0;
-                back:
-                for (; i < allStacks.size(); i++) {
-                    EntryStack stack = allStacks.get(i);
-                    while (true) {
-                        EntryListEntry entry = entries.get(nextIndex);
-                        entry.getBounds().y = (int) (entry.backupY - scrolling.scrollAmount);
-                        if (entry.getBounds().y > bounds.getMaxY())
-                            break back;
-                        if (notSteppingOnExclusionZones(entry.getBounds().x, entry.getBounds().y, innerBounds)) {
-                            entry.entry(stack);
-                            if (!entry.getCurrentEntry().isEmpty()) {
-                                size++;
-                                long l = System.nanoTime();
-                                entry.render(matrices, mouseX, mouseY, delta);
-                                time += (System.nanoTime() - l);
-                            }
-                            nextIndex++;
-                            break;
-                        } else {
-                            blockedCount++;
-                            nextIndex++;
-                        }
+            
+            Stream<EntryListEntry> entryStream = this.entries.stream().skip(nextIndex).filter(entry -> {
+                entry.getBounds().y = (int) (entry.backupY - scrolling.scrollAmount);
+                if (entry.getBounds().y > bounds.getMaxY()) return false;
+                if (notSteppingOnExclusionZones(entry.getBounds().x, entry.getBounds().y, innerBounds)) {
+                    EntryStack stack = allStacks.get(i[0]++);
+                    if (!stack.isEmpty()) {
+                        entry.entry(stack);
+                        return true;
                     }
+                } else {
+                    blockedCount++;
                 }
-                long totalTime = System.nanoTime() - totalTimeStart;
-                int z = getZ();
-                setZ(500);
-                Text debugText = new LiteralText(String.format("%d entries, avg. %.0fns, ttl. %.0fms, %s fps", size, time / (double) size, totalTime / 1000000d, minecraft.fpsDebugString.split(" ")[0]));
-                fillGradient(matrices, bounds.x, bounds.y, bounds.x + font.getWidth(debugText) + 2, bounds.y + font.fontHeight + 2, -16777216, -16777216);
-                VertexConsumerProvider.Immediate immediate = VertexConsumerProvider.immediate(Tessellator.getInstance().getBuffer());
-                matrices.push();
-                matrices.translate(0.0D, 0.0D, getZ());
-                Matrix4f matrix = matrices.peek().getModel();
-                font.draw(debugText, bounds.x + 2, bounds.y + 2, -1, false, matrix, immediate, false, 0, 15728880);
-                immediate.draw();
-                setZ(z);
-                matrices.pop();
+                return false;
+            }).limit(allStacks.size() - i[0]);
+            
+            if (fastEntryRendering) {
+                entryStream.collect(Collectors.groupingBy(entryListEntry -> OptimalEntryStack.groupingHashFrom(entryListEntry.getCurrentEntry()))).forEach((integer, entries) -> {
+                    renderEntries(debugTime, size, time, fastEntryRendering, matrices, mouseX, mouseY, delta, (List) entries);
+                });
             } else {
-                back:
-                for (; i < allStacks.size(); i++) {
-                    EntryStack stack = allStacks.get(i);
-                    while (true) {
-                        EntryListEntry entry = entries.get(nextIndex);
-                        entry.getBounds().y = (int) (entry.backupY - scrolling.scrollAmount);
-                        if (entry.getBounds().y > bounds.getMaxY())
-                            break back;
-                        if (notSteppingOnExclusionZones(entry.getBounds().x, entry.getBounds().y, innerBounds)) {
-                            entry.entry(stack);
-                            entry.render(matrices, mouseX, mouseY, delta);
-                            nextIndex++;
-                            break;
-                        } else {
-                            blockedCount++;
-                            nextIndex++;
-                        }
-                    }
-                }
+                renderEntries(debugTime, size, time, fastEntryRendering, matrices, mouseX, mouseY, delta, entryStream.collect(Collectors.toList()));
             }
+            
             updatePosition(delta);
             ScissorsHandler.INSTANCE.removeLastScissor();
             scrolling.renderScrollBar(0, 1, REIHelper.getInstance().isDarkThemeEnabled() ? 0.8f : 1f);
         } else {
-            if (debugTime) {
-                int[] size = {0};
-                long[] time = {0};
-                for (Widget widget : renders) {
-                    widget.render(matrices, mouseX, mouseY, delta);
-                }
-                long totalTimeStart = System.nanoTime();
-                if (ConfigObject.getInstance().doesFastEntryRendering()) {
-                    entries.stream().collect(Collectors.groupingBy(entryListEntry -> OptimalEntryStack.groupingHashFrom(entryListEntry.getCurrentEntry()))).forEach((integer, entries) -> {
-                        if (entries.isEmpty()) return;
-                        EntryListEntry firstWidget = entries.get(0);
-                        EntryStack first = firstWidget.getCurrentEntry();
-                        if (first instanceof OptimalEntryStack) {
-                            OptimalEntryStack firstStack = (OptimalEntryStack) first;
-                            firstStack.optimisedRenderStart(matrices, delta);
-                            long l = System.nanoTime();
-                            VertexConsumerProvider.Immediate immediate = MinecraftClient.getInstance().getBufferBuilders().getEntityVertexConsumers();
-                            for (EntryListEntry listEntry : entries) {
-                                EntryStack currentEntry = listEntry.getCurrentEntry();
-                                currentEntry.setZ(100);
-                                listEntry.drawBackground(matrices, mouseX, mouseY, delta);
-                                ((OptimalEntryStack) currentEntry).optimisedRenderBase(matrices, immediate, listEntry.getInnerBounds(), mouseX, mouseY, delta);
-                                if (!currentEntry.isEmpty())
-                                    size[0]++;
-                            }
-                            immediate.draw();
-                            for (EntryListEntry listEntry : entries) {
-                                EntryStack currentEntry = listEntry.getCurrentEntry();
-                                ((OptimalEntryStack) currentEntry).optimisedRenderOverlay(matrices, listEntry.getInnerBounds(), mouseX, mouseY, delta);
-                                if (listEntry.containsMouse(mouseX, mouseY)) {
-                                    listEntry.queueTooltip(matrices, mouseX, mouseY, delta);
-                                    listEntry.drawHighlighted(matrices, mouseX, mouseY, delta);
-                                }
-                            }
-                            time[0] += (System.nanoTime() - l);
-                            firstStack.optimisedRenderEnd(matrices, delta);
-                        } else {
-                            for (EntryListEntry listEntry : entries) {
-                                if (listEntry.getCurrentEntry().isEmpty())
-                                    continue;
-                                size[0]++;
-                                long l = System.nanoTime();
-                                listEntry.render(matrices, mouseX, mouseY, delta);
-                                time[0] += (System.nanoTime() - l);
-                            }
-                        }
-                    });
-                } else {
-                    for (EntryListEntry entry : entries) {
-                        if (entry.getCurrentEntry().isEmpty())
-                            continue;
-                        size[0]++;
-                        long l = System.nanoTime();
-                        entry.render(matrices, mouseX, mouseY, delta);
-                        time[0] += (System.nanoTime() - l);
-                    }
-                }
-                long totalTime = System.nanoTime() - totalTimeStart;
-                int z = getZ();
-                setZ(500);
-                Text debugText = new LiteralText(String.format("%d entries, avg. %.0fns, ttl. %.0fms, %s fps", size[0], time[0] / (double) size[0], totalTime / 1000000d, minecraft.fpsDebugString.split(" ")[0]));
-                int stringWidth = font.getWidth(debugText);
-                fillGradient(matrices, Math.min(bounds.x, minecraft.currentScreen.width - stringWidth - 2), bounds.y, bounds.x + stringWidth + 2, bounds.y + font.fontHeight + 2, -16777216, -16777216);
-                VertexConsumerProvider.Immediate immediate = VertexConsumerProvider.immediate(Tessellator.getInstance().getBuffer());
-                matrices.push();
-                matrices.translate(0.0D, 0.0D, getZ());
-                Matrix4f matrix = matrices.peek().getModel();
-                font.draw(debugText, Math.min(bounds.x + 2, minecraft.currentScreen.width - stringWidth), bounds.y + 2, -1, false, matrix, immediate, false, 0, 15728880);
-                immediate.draw();
-                setZ(z);
-                matrices.pop();
+            for (Widget widget : renders) {
+                widget.render(matrices, mouseX, mouseY, delta);
+            }
+            if (fastEntryRendering) {
+                entries.stream().collect(Collectors.groupingBy(entryListEntry -> OptimalEntryStack.groupingHashFrom(entryListEntry.getCurrentEntry()))).forEach((integer, entries) -> {
+                    renderEntries(debugTime, size, time, fastEntryRendering, matrices, mouseX, mouseY, delta, (List) entries);
+                });
             } else {
-                for (Widget widget : renders) {
-                    widget.render(matrices, mouseX, mouseY, delta);
-                }
-                if (ConfigObject.getInstance().doesFastEntryRendering()) {
-                    entries.stream().collect(Collectors.groupingBy(entryListEntry -> OptimalEntryStack.groupingHashFrom(entryListEntry.getCurrentEntry()))).forEach((integer, entries) -> {
-                        if (entries.isEmpty()) return;
-                        EntryListEntry firstWidget = entries.get(0);
-                        EntryStack first = firstWidget.getCurrentEntry();
-                        if (first instanceof OptimalEntryStack) {
-                            OptimalEntryStack firstStack = (OptimalEntryStack) first;
-                            firstStack.optimisedRenderStart(matrices, delta);
-                            VertexConsumerProvider.Immediate immediate = MinecraftClient.getInstance().getBufferBuilders().getEntityVertexConsumers();
-                            for (EntryListEntry listEntry : entries) {
-                                EntryStack currentEntry = listEntry.getCurrentEntry();
-                                currentEntry.setZ(100);
-                                listEntry.drawBackground(matrices, mouseX, mouseY, delta);
-                                ((OptimalEntryStack) currentEntry).optimisedRenderBase(matrices, immediate, listEntry.getInnerBounds(), mouseX, mouseY, delta);
-                            }
-                            immediate.draw();
-                            for (EntryListEntry listEntry : entries) {
-                                EntryStack currentEntry = listEntry.getCurrentEntry();
-                                ((OptimalEntryStack) currentEntry).optimisedRenderOverlay(matrices, listEntry.getInnerBounds(), mouseX, mouseY, delta);
-                                if (listEntry.containsMouse(mouseX, mouseY)) {
-                                    listEntry.queueTooltip(matrices, mouseX, mouseY, delta);
-                                    listEntry.drawHighlighted(matrices, mouseX, mouseY, delta);
-                                }
-                            }
-                            firstStack.optimisedRenderEnd(matrices, delta);
-                        } else {
-                            for (EntryListEntry listEntry : entries) {
-                                if (listEntry.getCurrentEntry().isEmpty())
-                                    continue;
-                                listEntry.render(matrices, mouseX, mouseY, delta);
-                            }
-                        }
-                    });
-                } else {
-                    for (EntryListEntry listEntry : entries) {
-                        if (listEntry.getCurrentEntry().isEmpty())
-                            continue;
-                        listEntry.render(matrices, mouseX, mouseY, delta);
-                    }
-                }
+                renderEntries(debugTime, size, time, fastEntryRendering, matrices, mouseX, mouseY, delta, (List) entries);
             }
         }
+        
+        if (debugTime) {
+            long totalTime = System.nanoTime() - totalTimeStart;
+            averageDebugTime += (time[0] / (double) size[0]) * delta;
+            totalDebugTime += totalTime / 1000000d * delta;
+            totalDebugTimeDelta += delta;
+            if (totalDebugTimeDelta >= 20) {
+                lastAverageDebugTime = averageDebugTime / totalDebugTimeDelta;
+                lastTotalDebugTime = totalDebugTime / totalDebugTimeDelta;
+                averageDebugTime = 0;
+                totalDebugTime = 0;
+                totalDebugTimeDelta = 0;
+            } else if (lastAverageDebugTime == 0) {
+                lastAverageDebugTime = time[0] / (double) size[0];
+                totalDebugTime = totalTime / 1000000d;
+            }
+            int z = getZ();
+            setZ(500);
+            Text debugText = new LiteralText(String.format("%d entries, avg. %.0fns, ttl. %.2fms, %s fps", size[0], lastAverageDebugTime, lastTotalDebugTime, minecraft.fpsDebugString.split(" ")[0]));
+            int stringWidth = font.getWidth(debugText);
+            fillGradient(matrices, Math.min(bounds.x, minecraft.currentScreen.width - stringWidth - 2), bounds.y, bounds.x + stringWidth + 2, bounds.y + font.fontHeight + 2, -16777216, -16777216);
+            VertexConsumerProvider.Immediate immediate = VertexConsumerProvider.immediate(Tessellator.getInstance().getBuffer());
+            matrices.push();
+            matrices.translate(0.0D, 0.0D, getZ());
+            Matrix4f matrix = matrices.peek().getModel();
+            font.draw(debugText, Math.min(bounds.x + 2, minecraft.currentScreen.width - stringWidth), bounds.y + 2, -1, false, matrix, immediate, false, 0, 15728880);
+            immediate.draw();
+            setZ(z);
+            matrices.pop();
+        }
+        
         if (containsMouse(mouseX, mouseY) && ClientHelper.getInstance().isCheating() && !minecraft.player.inventory.getCursorStack().isEmpty() && RoughlyEnoughItemsCore.canDeleteItems()) {
             EntryStack stack = EntryStack.create(minecraft.player.inventory.getCursorStack().copy());
             if (stack.getType() == EntryStack.Type.FLUID) {

+ 19 - 2
src/main/java/me/shedaniel/rei/gui/widget/EntryWidget.java

@@ -36,6 +36,7 @@ import me.shedaniel.rei.impl.ScreenHelper;
 import me.shedaniel.rei.utils.CollectionUtils;
 import net.minecraft.client.gui.Element;
 import net.minecraft.client.resource.language.I18n;
+import net.minecraft.client.util.InputUtil;
 import net.minecraft.client.util.math.MatrixStack;
 import net.minecraft.text.LiteralText;
 import net.minecraft.util.Identifier;
@@ -353,9 +354,25 @@ public class EntryWidget extends Slot {
         if (!interactable)
             return false;
         if (containsMouse(mouseX, mouseY)) {
-            if (button == 0)
+            if (interactableFavorites && ConfigObject.getInstance().isFavoritesEnabled() && containsMouse(PointHelper.ofMouse()) && !getCurrentEntry().isEmpty()) {
+                ModifierKeyCode keyCode = ConfigObject.getInstance().getFavoriteKeyCode();
+                EntryStack entry = getCurrentEntry().copy();
+                entry.setAmount(127);
+                if (keyCode.matchesMouse(button)) {
+                    if (reverseFavoritesAction())
+                        ConfigObject.getInstance().getFavorites().removeIf(entry::equalsIgnoreAmount);
+                    else if (!CollectionUtils.anyMatchEqualsEntryIgnoreAmount(ConfigObject.getInstance().getFavorites(), entry))
+                        ConfigObject.getInstance().getFavorites().add(entry);
+                    ConfigManager.getInstance().saveConfig();
+                    FavoritesListWidget favoritesListWidget = ContainerScreenOverlay.getFavoritesListWidget();
+                    if (favoritesListWidget != null)
+                        favoritesListWidget.updateSearch(ContainerScreenOverlay.getEntryListWidget(), ScreenHelper.getSearchField().getText());
+                    return true;
+                }
+            }
+            if ((ConfigObject.getInstance().getRecipeKeybind().getType() != InputUtil.Type.MOUSE && button == 0) || ConfigObject.getInstance().getRecipeKeybind().matchesMouse(button))
                 return ClientHelper.getInstance().openView(ClientHelper.ViewSearchBuilder.builder().addRecipesFor(getCurrentEntry()).setOutputNotice(getCurrentEntry()).fillPreferredOpenedCategory());
-            else if (button == 1)
+            else if ((ConfigObject.getInstance().getUsageKeybind().getType() != InputUtil.Type.MOUSE && button == 1) || ConfigObject.getInstance().getUsageKeybind().matchesMouse(button))
                 return ClientHelper.getInstance().openView(ClientHelper.ViewSearchBuilder.builder().addUsagesFor(getCurrentEntry()).setInputNotice(getCurrentEntry()).fillPreferredOpenedCategory());
         }
         return false;

+ 24 - 16
src/main/java/me/shedaniel/rei/gui/widget/FavoritesListWidget.java

@@ -51,6 +51,8 @@ import org.jetbrains.annotations.Nullable;
 import java.util.Collections;
 import java.util.List;
 import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 import static me.shedaniel.rei.gui.widget.EntryListWidget.*;
 
@@ -106,32 +108,38 @@ public class FavoritesListWidget extends WidgetWithBounds {
     public void render(MatrixStack matrices, int mouseX, int mouseY, float delta) {
         if (bounds.isEmpty())
             return;
+        boolean fastEntryRendering = ConfigObject.getInstance().doesFastEntryRendering();
         for (EntryListEntry entry : entries)
             entry.clearStacks();
         ScissorsHandler.INSTANCE.scissor(bounds);
         int skip = Math.max(0, MathHelper.floor(scrolling.scrollAmount / (float) entrySize()));
         int nextIndex = skip * innerBounds.width / entrySize();
-        int i = nextIndex;
+        int[] i = {nextIndex};
         blockedCount = 0;
-        back:
-        for (; i < favorites.size(); i++) {
-            EntryStack stack = favorites.get(i);
-            while (true) {
-                EntryListEntry entry = entries.get(nextIndex);
-                entry.getBounds().y = (int) (entry.backupY - scrolling.scrollAmount);
-                if (entry.getBounds().y > bounds.getMaxY())
-                    break back;
-                if (notSteppingOnExclusionZones(entry.getBounds().x, entry.getBounds().y, innerBounds)) {
+        
+        Stream<EntryListEntry> entryStream = this.entries.stream().skip(nextIndex).filter(entry -> {
+            entry.getBounds().y = (int) (entry.backupY - scrolling.scrollAmount);
+            if (entry.getBounds().y > bounds.getMaxY()) return false;
+            if (notSteppingOnExclusionZones(entry.getBounds().x, entry.getBounds().y, innerBounds)) {
+                EntryStack stack = favorites.get(i[0]++);
+                if (!stack.isEmpty()) {
                     entry.entry(stack);
-                    entry.render(matrices, mouseX, mouseY, delta);
-                    nextIndex++;
-                    break;
-                } else {
-                    blockedCount++;
-                    nextIndex++;
+                    return true;
                 }
+            } else {
+                blockedCount++;
             }
+            return false;
+        }).limit(favorites.size() - i[0]);
+        
+        if (fastEntryRendering) {
+            entryStream.collect(Collectors.groupingBy(entryListEntry -> OptimalEntryStack.groupingHashFrom(entryListEntry.getCurrentEntry()))).forEach((integer, entries) -> {
+                renderEntries(false, new int[]{0}, new long[]{0}, fastEntryRendering, matrices, mouseX, mouseY, delta, (List) entries);
+            });
+        } else {
+            renderEntries(false, new int[]{0}, new long[]{0}, fastEntryRendering, matrices, mouseX, mouseY, delta, entryStream.collect(Collectors.toList()));
         }
+        
         updatePosition(delta);
         scrolling.renderScrollBar(0, 1, REIHelper.getInstance().isDarkThemeEnabled() ? 0.8f : 1f);
         ScissorsHandler.INSTANCE.removeLastScissor();

+ 3 - 2
src/main/java/me/shedaniel/rei/impl/AbstractEntryStack.java

@@ -23,16 +23,17 @@
 
 package me.shedaniel.rei.impl;
 
+import it.unimi.dsi.fastutil.objects.Reference2ObjectMap;
+import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap;
 import me.shedaniel.rei.api.EntryStack;
 import net.minecraft.client.gui.DrawableHelper;
 import org.jetbrains.annotations.ApiStatus;
 
-import java.util.HashMap;
 import java.util.Map;
 
 @ApiStatus.Internal
 public abstract class AbstractEntryStack extends DrawableHelper implements EntryStack {
-    private Map<Settings<?>, Object> settings = new HashMap<>();
+    private Reference2ObjectMap<Settings<?>, Object> settings = new Reference2ObjectOpenHashMap<>();
     
     @Override
     public <T> EntryStack setting(Settings<T> settings, T value) {

+ 0 - 1
src/main/java/me/shedaniel/rei/impl/ConfigManagerImpl.java

@@ -117,7 +117,6 @@ public class ConfigManagerImpl implements ConfigManager {
             if (field.isAnnotationPresent(ConfigEntry.Gui.Excluded.class))
                 return Collections.emptyList();
             KeyCodeEntry entry = ConfigEntryBuilder.create().startModifierKeyCodeField(new TranslatableText(i13n), getUnsafely(field, config, ModifierKeyCode.unknown())).setModifierDefaultValue(() -> getUnsafely(field, defaults)).setModifierSaveConsumer(newValue -> setUnsafely(field, config, newValue)).build();
-            entry.setAllowMouse(false);
             return Collections.singletonList(entry);
         }, field -> field.getType() == ModifierKeyCode.class);
         guiRegistry.registerAnnotationProvider((i13n, field, config, defaults, guiProvider) -> {

+ 15 - 3
src/main/java/me/shedaniel/rei/impl/InternalWidgets.java

@@ -175,15 +175,27 @@ public final class InternalWidgets {
             }
             
             @Override
-            public boolean keyPressed(int int_1, int int_2, int int_3) {
-                if (displaySupplier.get().getRecipeLocation().isPresent() && ConfigObject.getInstance().getCopyRecipeIdentifierKeybind().matchesKey(int_1, int_2) && containsMouse(PointHelper.ofMouse())) {
+            public boolean keyPressed(int keyCode, int scanCode, int modifiers) {
+                if (displaySupplier.get().getRecipeLocation().isPresent() && ConfigObject.getInstance().getCopyRecipeIdentifierKeybind().matchesKey(keyCode, scanCode) && containsMouse(PointHelper.ofMouse())) {
                     minecraft.keyboard.setClipboard(displaySupplier.get().getRecipeLocation().get().toString());
                     if (ConfigObject.getInstance().isToastDisplayedOnCopyIdentifier()) {
                         CopyRecipeIdentifierToast.addToast(I18n.translate("msg.rei.copied_recipe_id"), I18n.translate("msg.rei.recipe_id_details", displaySupplier.get().getRecipeLocation().get().toString()));
                     }
                     return true;
                 }
-                return super.keyPressed(int_1, int_2, int_3);
+                return super.keyPressed(keyCode, scanCode, modifiers);
+            }
+            
+            @Override
+            public boolean mouseClicked(double mouseX, double mouseY, int button) {
+                if (displaySupplier.get().getRecipeLocation().isPresent() && ConfigObject.getInstance().getCopyRecipeIdentifierKeybind().matchesMouse(button) && containsMouse(PointHelper.ofMouse())) {
+                    minecraft.keyboard.setClipboard(displaySupplier.get().getRecipeLocation().get().toString());
+                    if (ConfigObject.getInstance().isToastDisplayedOnCopyIdentifier()) {
+                        CopyRecipeIdentifierToast.addToast(I18n.translate("msg.rei.copied_recipe_id"), I18n.translate("msg.rei.recipe_id_details", displaySupplier.get().getRecipeLocation().get().toString()));
+                    }
+                    return true;
+                }
+                return super.mouseClicked(mouseX, mouseY, button);
             }
         };
     }

+ 27 - 40
src/main/java/me/shedaniel/rei/impl/RecipeHelperImpl.java

@@ -23,9 +23,7 @@
 
 package me.shedaniel.rei.impl;
 
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-import com.google.common.collect.Sets;
+import com.google.common.collect.*;
 import me.shedaniel.math.Rectangle;
 import me.shedaniel.rei.RoughlyEnoughItemsCore;
 import me.shedaniel.rei.api.*;
@@ -53,6 +51,7 @@ import java.util.function.Consumer;
 import java.util.function.Function;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 @ApiStatus.Internal
 @Environment(EnvType.CLIENT)
@@ -68,9 +67,8 @@ public class RecipeHelperImpl implements RecipeHelper {
     private final List<RecipeFunction> recipeFunctions = Lists.newArrayList();
     private final List<ScreenClickArea> screenClickAreas = Lists.newArrayList();
     private final int[] recipeCount = {0};
-    private final Map<Identifier, List<RecipeDisplay>> recipeCategoryListMap = Maps.newLinkedHashMap();
-    private final Map<RecipeCategory<?>, Identifier> categories = Maps.newLinkedHashMap();
-    private final Map<Identifier, RecipeCategory<?>> reversedCategories = Maps.newHashMap();
+    private final Map<Identifier, List<RecipeDisplay>> recipeDisplays = Maps.newHashMap();
+    private final BiMap<RecipeCategory<?>, Identifier> categories = HashBiMap.create();
     private final Map<Identifier, ButtonAreaSupplier> autoCraftAreaSupplierMap = Maps.newHashMap();
     private final Map<Identifier, List<List<EntryStack>>> categoryWorkingStations = Maps.newHashMap();
     private final List<DisplayVisibilityHandler> displayVisibilityHandlers = Lists.newArrayList();
@@ -81,7 +79,7 @@ public class RecipeHelperImpl implements RecipeHelper {
     @Override
     public List<EntryStack> findCraftableEntriesByItems(List<EntryStack> inventoryItems) {
         List<EntryStack> craftables = new ArrayList<>();
-        for (List<RecipeDisplay> value : recipeCategoryListMap.values())
+        for (List<RecipeDisplay> value : recipeDisplays.values())
             for (RecipeDisplay recipeDisplay : Lists.newArrayList(value)) {
                 int slotsCraftable = 0;
                 List<List<EntryStack>> requiredInput = recipeDisplay.getRequiredEntries();
@@ -113,8 +111,7 @@ public class RecipeHelperImpl implements RecipeHelper {
     @Override
     public void registerCategory(RecipeCategory<?> category) {
         categories.put(category, category.getIdentifier());
-        reversedCategories.put(category.getIdentifier(), category);
-        recipeCategoryListMap.put(category.getIdentifier(), Lists.newArrayList());
+        recipeDisplays.put(category.getIdentifier(), Lists.newArrayList());
         categoryWorkingStations.put(category.getIdentifier(), Lists.newArrayList());
     }
     
@@ -126,7 +123,7 @@ public class RecipeHelperImpl implements RecipeHelper {
     
     @Override
     public void registerWorkingStations(Identifier category, EntryStack... workingStations) {
-        categoryWorkingStations.get(category).addAll(Arrays.stream(workingStations).map(Collections::singletonList).collect(Collectors.toList()));
+        categoryWorkingStations.get(category).addAll(Stream.of(workingStations).map(Collections::singletonList).collect(Collectors.toList()));
     }
     
     @Override
@@ -134,29 +131,20 @@ public class RecipeHelperImpl implements RecipeHelper {
         return categoryWorkingStations.get(category);
     }
     
-    @Deprecated
-    @Override
-    public void registerDisplay(Identifier categoryIdentifier, RecipeDisplay display) {
-        if (!recipeCategoryListMap.containsKey(categoryIdentifier))
-            return;
-        recipeCount[0]++;
-        recipeCategoryListMap.get(categoryIdentifier).add(display);
-    }
-    
     @Override
     public void registerDisplay(RecipeDisplay display) {
         Identifier identifier = Objects.requireNonNull(display.getRecipeCategory());
-        if (!recipeCategoryListMap.containsKey(identifier))
+        if (!recipeDisplays.containsKey(identifier))
             return;
         recipeCount[0]++;
-        recipeCategoryListMap.get(identifier).add(display);
+        recipeDisplays.get(identifier).add(display);
     }
     
     private void registerDisplay(Identifier categoryIdentifier, RecipeDisplay display, int index) {
-        if (!recipeCategoryListMap.containsKey(categoryIdentifier))
+        if (!recipeDisplays.containsKey(categoryIdentifier))
             return;
         recipeCount[0]++;
-        recipeCategoryListMap.get(categoryIdentifier).add(index, display);
+        recipeDisplays.get(categoryIdentifier).add(index, display);
     }
     
     @Override
@@ -272,7 +260,7 @@ public class RecipeHelperImpl implements RecipeHelper {
     
     @Override
     public RecipeCategory<?> getCategory(Identifier identifier) {
-        return reversedCategories.get(identifier);
+        return categories.inverse().get(identifier);
     }
     
     @Override
@@ -353,9 +341,8 @@ public class RecipeHelperImpl implements RecipeHelper {
         ScreenHelper.clearLastRecipeScreenData();
         recipeCount[0] = 0;
         this.recipeManager = recipeManager;
-        this.recipeCategoryListMap.clear();
+        this.recipeDisplays.clear();
         this.categories.clear();
-        this.reversedCategories.clear();
         this.autoCraftAreaSupplierMap.clear();
         this.screenClickAreas.clear();
         this.categoryWorkingStations.clear();
@@ -488,12 +475,13 @@ public class RecipeHelperImpl implements RecipeHelper {
         
         long usedTime = Util.getMeasuringTimeMs() - startTime;
         RoughlyEnoughItemsCore.LOGGER.info("Reloaded %d stack entries, %d recipes displays, %d exclusion zones suppliers, %d overlay deciders, %d visibility handlers and %d categories (%s) in %dms.",
-                EntryRegistry.getInstance().getStacksList().size(), recipeCount[0], BaseBoundsHandler.getInstance().supplierSize(), DisplayHelper.getInstance().getAllOverlayDeciders().size(), getDisplayVisibilityHandlers().size(), categories.size(), categories.keySet().stream().map(RecipeCategory::getCategoryName).collect(Collectors.joining(", ")), usedTime);
+                EntryRegistry.getInstance().getEntryStacks().count(), recipeCount[0], BaseBoundsHandler.getInstance().supplierSize(), DisplayHelper.getInstance().getAllOverlayDeciders().size(), getDisplayVisibilityHandlers().size(), categories.size(), categories.keySet().stream().map(RecipeCategory::getCategoryName).collect(Collectors.joining(", ")), usedTime);
     }
     
     @Override
     public AutoTransferHandler registerAutoCraftingHandler(AutoTransferHandler handler) {
         autoTransferHandlers.add(handler);
+        autoTransferHandlers.sort(Comparator.comparingDouble(AutoTransferHandler::getPriority).reversed());
         return handler;
     }
     
@@ -520,7 +508,7 @@ public class RecipeHelperImpl implements RecipeHelper {
     
     @Override
     public List<AutoTransferHandler> getSortedAutoCraftingHandler() {
-        return autoTransferHandlers.stream().sorted(Comparator.comparingDouble(AutoTransferHandler::getPriority).reversed()).collect(Collectors.toList());
+        return autoTransferHandlers;
     }
     
     @Override
@@ -531,7 +519,7 @@ public class RecipeHelperImpl implements RecipeHelper {
     @Override
     @SuppressWarnings("rawtypes")
     public List<Recipe> getAllSortedRecipes() {
-        return getRecipeManager().values().stream().sorted(RECIPE_COMPARATOR).collect(Collectors.toList());
+        return getRecipeManager().values().parallelStream().sorted(RECIPE_COMPARATOR).collect(Collectors.toList());
     }
     
     @Override
@@ -545,10 +533,9 @@ public class RecipeHelperImpl implements RecipeHelper {
         for (Map.Entry<RecipeCategory<?>, Identifier> entry : categories.entrySet()) {
             RecipeCategory<?> category = entry.getKey();
             Identifier categoryId = entry.getValue();
-            List<RecipeDisplay> displays = Lists.newArrayList(recipeCategoryListMap.get(categoryId));
-            if (displays != null) {
-                if (!displays.isEmpty())
-                    result.put(category, displays);
+            List<RecipeDisplay> displays = recipeDisplays.get(categoryId);
+            if (displays != null && !displays.isEmpty()) {
+                result.put(category, displays);
             }
         }
         return result;
@@ -556,7 +543,7 @@ public class RecipeHelperImpl implements RecipeHelper {
     
     @Override
     public List<RecipeDisplay> getAllRecipesFromCategory(RecipeCategory<?> category) {
-        return Lists.newArrayList(recipeCategoryListMap.get(category.getIdentifier()));
+        return recipeDisplays.get(category.getIdentifier());
     }
     
     @Override
@@ -627,9 +614,9 @@ public class RecipeHelperImpl implements RecipeHelper {
     }
     
     private static class ScreenClickAreaImpl implements ScreenClickArea {
-        Class<? extends ContainerScreen<?>> screenClass;
-        Rectangle rectangle;
-        Identifier[] categories;
+        private Class<? extends ContainerScreen<?>> screenClass;
+        private Rectangle rectangle;
+        private Identifier[] categories;
         
         private ScreenClickAreaImpl(Class<? extends ContainerScreen<?>> screenClass, Rectangle rectangle, Identifier[] categories) {
             this.screenClass = screenClass;
@@ -652,9 +639,9 @@ public class RecipeHelperImpl implements RecipeHelper {
     
     @SuppressWarnings("rawtypes")
     private static class RecipeFunction {
-        Identifier category;
-        Predicate<Recipe> recipeFilter;
-        Function mappingFunction;
+        private Identifier category;
+        private Predicate<Recipe> recipeFilter;
+        private Function mappingFunction;
         
         public RecipeFunction(Identifier category, Predicate<Recipe> recipeFilter, Function<?, RecipeDisplay> mappingFunction) {
             this.category = category;

+ 4 - 1
src/main/java/me/shedaniel/rei/plugin/information/DefaultInformationCategory.java

@@ -39,6 +39,8 @@ import me.shedaniel.rei.gui.widget.Widget;
 import me.shedaniel.rei.gui.widget.WidgetWithBounds;
 import me.shedaniel.rei.impl.RenderingEntry;
 import me.shedaniel.rei.plugin.DefaultPlugin;
+import net.fabricmc.api.EnvType;
+import net.fabricmc.api.Environment;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.gui.Element;
 import net.minecraft.client.render.BufferBuilder;
@@ -57,6 +59,7 @@ import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
 
+@Environment(EnvType.CLIENT)
 public class DefaultInformationCategory implements RecipeCategory<DefaultInformationDisplay> {
     protected static void innerBlit(Matrix4f matrix4f, int xStart, int xEnd, int yStart, int yEnd, int z, float uStart, float uEnd, float vStart, float vEnd) {
         BufferBuilder bufferBuilder = Tessellator.getInstance().getBuffer();
@@ -112,7 +115,7 @@ public class DefaultInformationCategory implements RecipeCategory<DefaultInforma
     }
     
     @Override
-    public List<Widget> setupDisplay(DefaultInformationDisplay recipeDisplay, me.shedaniel.math.Rectangle bounds) {
+    public List<Widget> setupDisplay(DefaultInformationDisplay recipeDisplay, Rectangle bounds) {
         List<Widget> widgets = Lists.newArrayList();
         widgets.add(Widgets.createLabel(new Point(bounds.getCenterX(), bounds.y + 3), recipeDisplay.getName()).noShadow().color(0xFF404040, 0xFFBBBBBB));
         widgets.add(Widgets.createSlot(new Point(bounds.getCenterX() - 8, bounds.y + 15)).entries(recipeDisplay.getEntryStacks()));