Skip to content

Commit

Permalink
Add API support for batch-rendering ingredients
Browse files Browse the repository at this point in the history
  • Loading branch information
mezz committed Aug 28, 2024
1 parent f0369ac commit b4914f9
Show file tree
Hide file tree
Showing 19 changed files with 329 additions and 259 deletions.
15 changes: 9 additions & 6 deletions Common/src/main/java/mezz/jei/common/util/ErrorUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ private static String getItemName(Item item) {
return item.getClass().getName();
}

@SuppressWarnings("ConstantValue")
private static String getBlockName(BlockItem blockItem) {
Block block = blockItem.getBlock();
if (block == null) {
Expand Down Expand Up @@ -171,21 +172,23 @@ public static <T> void validateRecipes(RecipeType<T> recipeType, Iterable<? exte
}

public static <T> CrashReport createIngredientCrashReport(Throwable throwable, String title, IIngredientManager ingredientManager, ITypedIngredient<T> typedIngredient) {
return createIngredientCrashReport(throwable, title, ingredientManager, typedIngredient.getType(), typedIngredient.getIngredient());
}

public static <T> CrashReport createIngredientCrashReport(Throwable throwable, String title, IIngredientManager ingredientManager, IIngredientType<T> ingredientType, T ingredient) {
CrashReport crashReport = CrashReport.forThrowable(throwable, title);
CrashReportCategory category = crashReport.addCategory("Ingredient");
setIngredientCategoryDetails(category, typedIngredient, ingredientManager);
setIngredientCategoryDetails(category, ingredientType, ingredient, ingredientManager);
return crashReport;
}

public static <T> void logIngredientCrash(Throwable throwable, String title, IIngredientManager ingredientManager, ITypedIngredient<T> typedIngredient) {
public static <T> void logIngredientCrash(Throwable throwable, String title, IIngredientManager ingredientManager, IIngredientType<T> ingredientType, T ingredient) {
CrashReportCategory category = new CrashReportCategory("Ingredient");
setIngredientCategoryDetails(category, typedIngredient, ingredientManager);
setIngredientCategoryDetails(category, ingredientType, ingredient, ingredientManager);
LOGGER.error(crashReportToString(throwable, title, category));
}

private static <T> void setIngredientCategoryDetails(CrashReportCategory category, ITypedIngredient<T> typedIngredient, IIngredientManager ingredientManager) {
T ingredient = typedIngredient.getIngredient();
IIngredientType<T> ingredientType = typedIngredient.getType();
private static <T> void setIngredientCategoryDetails(CrashReportCategory category, IIngredientType<T> ingredientType, T ingredient, IIngredientManager ingredientManager) {
IIngredientHelper<T> ingredientHelper = ingredientManager.getIngredientHelper(ingredientType);
Codec<T> ingredientCodec = ingredientManager.getIngredientCodec(ingredientType);

Expand Down
87 changes: 66 additions & 21 deletions Common/src/main/java/mezz/jei/common/util/SafeIngredientUtil.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package mezz.jei.common.util;

import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.mojang.blaze3d.systems.RenderSystem;
import mezz.jei.api.gui.builder.ITooltipBuilder;
import mezz.jei.api.ingredients.IIngredientRenderer;
import mezz.jei.api.ingredients.IIngredientType;
import mezz.jei.api.ingredients.ITypedIngredient;
import mezz.jei.api.ingredients.rendering.BatchRenderElement;
import mezz.jei.api.runtime.IIngredientManager;
import mezz.jei.common.Internal;
import mezz.jei.common.config.IClientConfig;
Expand All @@ -19,18 +19,18 @@
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.MutableComponent;
import net.minecraft.world.item.TooltipFlag;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.util.concurrent.TimeUnit;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public final class SafeIngredientUtil {
private static final Cache<ITypedIngredient<?>, Boolean> CRASHING_INGREDIENT_RENDER_CACHE =
CacheBuilder.newBuilder()
.expireAfterAccess(10, TimeUnit.SECONDS)
.build();
private static final Cache<ITypedIngredient<?>, Boolean> CRASHING_INGREDIENT_TOOLTIP_CACHE =
CacheBuilder.newBuilder()
.expireAfterAccess(10, TimeUnit.SECONDS)
.build();
private static final Logger LOGGER = LogManager.getLogger();
private static final Set<IIngredientRenderer<?>> CRASHING_INGREDIENT_BATCH_RENDERERS = new HashSet<>();
private static final Set<Object> CRASHING_INGREDIENT_RENDERERS = new HashSet<>();
private static final Set<Object> CRASHING_INGREDIENT_TOOLTIPS = new HashSet<>();

private SafeIngredientUtil() {
}
Expand All @@ -49,21 +49,22 @@ public static <T> void getTooltip(
ITypedIngredient<T> typedIngredient,
TooltipFlag.Default tooltipFlag
) {
if (CRASHING_INGREDIENT_TOOLTIP_CACHE.getIfPresent(typedIngredient) == Boolean.TRUE) {
T ingredient = typedIngredient.getIngredient();

if (CRASHING_INGREDIENT_TOOLTIPS.contains(ingredient)) {
getTooltipErrorTooltip(tooltip);
return;
}

tooltip.setIngredient(typedIngredient);
T ingredient = typedIngredient.getIngredient();
try {
ingredientRenderer.getTooltip(tooltip, ingredient, tooltipFlag);
if (CRASHING_INGREDIENT_RENDER_CACHE.getIfPresent(typedIngredient) == Boolean.TRUE) {
if (CRASHING_INGREDIENT_RENDERERS.contains(ingredient)) {
getRenderErrorTooltip(tooltip);
}
} catch (RuntimeException | LinkageError e) {
CRASHING_INGREDIENT_TOOLTIP_CACHE.put(typedIngredient, Boolean.TRUE);
ErrorUtil.logIngredientCrash(e, "Caught an error getting an Ingredient's tooltip", ingredientManager, typedIngredient);
CRASHING_INGREDIENT_TOOLTIPS.add(ingredient);
ErrorUtil.logIngredientCrash(e, "Caught an error getting an Ingredient's tooltip", ingredientManager, typedIngredient.getType(), ingredient);
getTooltipErrorTooltip(tooltip);
}
}
Expand All @@ -78,30 +79,74 @@ private static void getRenderErrorTooltip(ITooltipBuilder tooltip) {
tooltip.add(crash.withStyle(ChatFormatting.RED));
}

public static <T> void renderBatch(
GuiGraphics guiGraphics,
IIngredientType<T> ingredientType,
IIngredientRenderer<T> ingredientRenderer,
List<BatchRenderElement<T>> elements
) {
if (CRASHING_INGREDIENT_BATCH_RENDERERS.contains(ingredientRenderer)) {
for (BatchRenderElement<T> element : elements) {
render(guiGraphics, ingredientRenderer, ingredientType, element);
}
return;
}

try {
ingredientRenderer.renderBatch(guiGraphics, elements);
} catch (RuntimeException | LinkageError e) {
CRASHING_INGREDIENT_BATCH_RENDERERS.add(ingredientRenderer);
LOGGER.error(
"Caught an error while rendering a batch of Ingredients with ingredient renderer: {}",
ingredientRenderer.getClass(),
e
);
}
}

public static <T> void render(
GuiGraphics guiGraphics,
IIngredientRenderer<T> ingredientRenderer,
ITypedIngredient<T> typedIngredient,
int x,
int y
) {
if (CRASHING_INGREDIENT_RENDER_CACHE.getIfPresent(typedIngredient) == Boolean.TRUE) {
render(guiGraphics, ingredientRenderer, typedIngredient.getType(), typedIngredient.getIngredient(), x, y);
}

public static <T> void render(
GuiGraphics guiGraphics,
IIngredientRenderer<T> ingredientRenderer,
IIngredientType<T> ingredientType,
BatchRenderElement<T> element
) {
render(guiGraphics, ingredientRenderer, ingredientType, element.ingredient(), element.x(), element.y());
}

public static <T> void render(
GuiGraphics guiGraphics,
IIngredientRenderer<T> ingredientRenderer,
IIngredientType<T> ingredientType,
T ingredient,
int x,
int y
) {
if (CRASHING_INGREDIENT_RENDERERS.contains(ingredient)) {
renderError(guiGraphics);
return;
}

T ingredient = typedIngredient.getIngredient();
try {
ingredientRenderer.render(guiGraphics, ingredient, x, y);
} catch (RuntimeException | LinkageError e) {
CRASHING_INGREDIENT_RENDER_CACHE.put(typedIngredient, Boolean.TRUE);
CRASHING_INGREDIENT_RENDERERS.add(ingredient);

IIngredientManager ingredientManager = Internal.getJeiRuntime().getIngredientManager();
if (shouldCatchRenderErrors()) {
ErrorUtil.logIngredientCrash(e, "Caught an error rendering an Ingredient", ingredientManager, typedIngredient);
ErrorUtil.logIngredientCrash(e, "Caught an error rendering an Ingredient", ingredientManager, ingredientType, ingredient);
renderError(guiGraphics);
} else {
CrashReport crashReport = ErrorUtil.createIngredientCrashReport(e, "Rendering ingredient", ingredientManager, typedIngredient);
CrashReport crashReport = ErrorUtil.createIngredientCrashReport(e, "Rendering ingredient", ingredientManager, ingredientType, ingredient);
throw new ReportedException(crashReport);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
package mezz.jei.api.ingredients;

import java.util.List;

import com.mojang.blaze3d.vertex.PoseStack;
import mezz.jei.api.gui.builder.ITooltipBuilder;
import mezz.jei.api.ingredients.rendering.BatchRenderElement;
import mezz.jei.api.registration.IModIngredientRegistration;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.Font;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.network.chat.Component;
import net.minecraft.world.item.TooltipFlag;

import mezz.jei.api.registration.IModIngredientRegistration;
import net.minecraft.network.chat.Component;
import java.util.List;

/**
* Renders a type of ingredient in JEI's item list and recipes.
Expand Down Expand Up @@ -50,6 +50,18 @@ default void render(GuiGraphics guiGraphics, T ingredient, int posX, int posY) {
poseStack.popPose();
}

/**
* Render a batch of ingredients.
* Implementing this is not necessary, but can be used to optimize rendering many ingredients at once.
*
* @since 19.14.0
*/
default void renderBatch(GuiGraphics guiGraphics, List<BatchRenderElement<T>> elements) {
for (BatchRenderElement<T> element : elements) {
render(guiGraphics, element.ingredient(), element.x(), element.y());
}
}

/**
* Get the tooltip text for this ingredient. JEI renders the tooltip based on this.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package mezz.jei.api.ingredients.rendering;

import mezz.jei.api.ingredients.IIngredientRenderer;
import net.minecraft.client.gui.GuiGraphics;

import java.util.List;

/**
* A single ingredient to render in a batch render operation.
*
* @see IIngredientRenderer#renderBatch(GuiGraphics, List)
*
* @since 19.14.0
*/
public record BatchRenderElement<T>(T ingredient, int x, int y) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
@ParametersAreNonnullByDefault
@MethodsReturnNonnullByDefault
package mezz.jei.api.ingredients.rendering;

import net.minecraft.MethodsReturnNonnullByDefault;

import javax.annotation.ParametersAreNonnullByDefault;
33 changes: 27 additions & 6 deletions Gui/src/main/java/mezz/jei/gui/overlay/IngredientGrid.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,16 @@

import com.mojang.blaze3d.systems.RenderSystem;
import mezz.jei.api.helpers.IColorHelper;
import mezz.jei.api.ingredients.IIngredientHelper;
import mezz.jei.api.ingredients.IIngredientRenderer;
import mezz.jei.api.ingredients.IIngredientType;
import mezz.jei.api.ingredients.ITypedIngredient;
import mezz.jei.api.runtime.IIngredientManager;
import mezz.jei.common.config.IClientConfig;
import mezz.jei.common.config.IClientToggleState;
import mezz.jei.common.config.IIngredientFilterConfig;
import mezz.jei.common.config.IIngredientGridConfig;
import mezz.jei.common.gui.JeiTooltip;
import mezz.jei.common.input.IInternalKeyMappings;
import mezz.jei.common.network.IConnectionToServer;
import mezz.jei.common.util.ImmutablePoint2i;
Expand Down Expand Up @@ -42,6 +46,7 @@ public class IngredientGrid implements IRecipeFocusSource, IIngredientGrid {
public static final int INGREDIENT_WIDTH = GuiIngredientProperties.getWidth(INGREDIENT_PADDING);
public static final int INGREDIENT_HEIGHT = GuiIngredientProperties.getHeight(INGREDIENT_PADDING);

private final IIngredientManager ingredientManager;
private final IIngredientGridConfig gridConfig;
private final IngredientListRenderer ingredientListRenderer;
private final DeleteItemInputHandler deleteItemHandler;
Expand All @@ -57,10 +62,12 @@ public IngredientGrid(
IClientToggleState toggleState,
IConnectionToServer serverConnection,
IInternalKeyMappings keyBindings,
IColorHelper colorHelper
IColorHelper colorHelper,
boolean supportsEditMode
) {
this.ingredientManager = ingredientManager;
this.gridConfig = gridConfig;
this.ingredientListRenderer = new IngredientListRenderer();
this.ingredientListRenderer = new IngredientListRenderer(ingredientManager, supportsEditMode);
this.tooltipHelper = new IngredientGridTooltipHelper(ingredientManager, ingredientFilterConfig, toggleState, keyBindings, colorHelper);
this.deleteItemHandler = new DeleteItemInputHandler(this, toggleState, clientConfig, serverConnection, ingredientManager);
}
Expand Down Expand Up @@ -145,7 +152,7 @@ public void draw(Minecraft minecraft, GuiGraphics guiGraphics, int mouseX, int m
if (!this.deleteItemHandler.shouldDeleteItemOnClick(minecraft, mouseX, mouseY)) {
ingredientListRenderer.getSlots()
.filter(s -> s.getArea().contains(mouseX, mouseY))
.filter(s -> s.getElement().isPresent())
.filter(s -> s.getOptionalElement().isPresent())
.findFirst()
.ifPresent(s -> drawHighlight(guiGraphics, s.getArea()));
}
Expand Down Expand Up @@ -176,13 +183,27 @@ public void drawTooltips(Minecraft minecraft, GuiGraphics guiGraphics, int mouse
} else {
ingredientListRenderer.getSlots()
.filter(s -> s.isMouseOver(mouseX, mouseY))
.filter(s -> s.getElement().isPresent())
.map(IngredientListSlot::getOptionalElement)
.flatMap(Optional::stream)
.findFirst()
.ifPresent(s -> s.drawTooltip(guiGraphics, mouseX, mouseY, tooltipHelper));
.ifPresent(element -> {
drawTooltip(guiGraphics, mouseX, mouseY, element);
});
}
}
}

private <T> void drawTooltip(GuiGraphics guiGraphics, int mouseX, int mouseY, IElement<T> element) {
ITypedIngredient<T> typedIngredient = element.getTypedIngredient();
IIngredientType<T> ingredientType = typedIngredient.getType();
IIngredientRenderer<T> ingredientRenderer = ingredientManager.getIngredientRenderer(ingredientType);
IIngredientHelper<T> ingredientHelper = ingredientManager.getIngredientHelper(ingredientType);

JeiTooltip tooltip = new JeiTooltip();
element.getTooltip(tooltip, tooltipHelper, ingredientRenderer, ingredientHelper);
tooltip.draw(guiGraphics, mouseX, mouseY, typedIngredient, ingredientRenderer, ingredientManager);
}

@Override
public boolean isMouseOver(double mouseX, double mouseY) {
return area.contains(mouseX, mouseY) &&
Expand Down Expand Up @@ -212,7 +233,7 @@ public Stream<IngredientListSlot> getSlots() {

public <T> Stream<T> getVisibleIngredients(IIngredientType<T> ingredientType) {
return this.ingredientListRenderer.getSlots()
.map(IngredientListSlot::getElement)
.map(IngredientListSlot::getOptionalElement)
.flatMap(Optional::stream)
.map(IElement::getTypedIngredient)
.map(i -> i.getIngredient(ingredientType))
Expand Down
Loading

0 comments on commit b4914f9

Please sign in to comment.