Skip to content

Commit

Permalink
Add API support for batch-rendering ingredients (#3711)
Browse files Browse the repository at this point in the history
Add basic batch-rendering for ItemStacks
  • Loading branch information
mezz committed Sep 1, 2024
1 parent a3876f4 commit f4d3577
Show file tree
Hide file tree
Showing 33 changed files with 621 additions and 315 deletions.
50 changes: 26 additions & 24 deletions Common/src/main/java/mezz/jei/common/util/ErrorUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import net.minecraft.CrashReportCategory;
import net.minecraft.client.Minecraft;
import net.minecraft.core.NonNullList;
import net.minecraft.core.Registry;
import net.minecraft.core.registries.Registries;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.resources.ResourceLocation;
Expand Down Expand Up @@ -49,17 +50,7 @@ public static String getItemStackInfo(@Nullable ItemStack itemStack) {
.map(ResourceLocation::toString)
.orElseGet(() -> {
if (item instanceof BlockItem blockItem) {
final String blockName;
Block block = blockItem.getBlock();
//noinspection ConstantValue
if (block == null) {
blockName = "null";
} else {
IPlatformRegistry<Block> blockRegistry = Services.PLATFORM.getRegistry(Registries.BLOCK);
blockName = blockRegistry.getRegistryName(block)
.map(ResourceLocation::toString)
.orElseGet(() -> block.getClass().getName());
}
final String blockName = getBlockName(blockItem);
return "BlockItem(" + blockName + ")";
} else {
return item.getClass().getName();
Expand All @@ -73,6 +64,21 @@ public static String getItemStackInfo(@Nullable ItemStack itemStack) {
return itemStack + " " + itemName;
}

@SuppressWarnings("ConstantValue")
private static String getBlockName(BlockItem blockItem) {
Block block = blockItem.getBlock();
if (block == null) {
return "null";
}
Registry<Block> blockRegistry = RegistryUtil.getRegistry(Registries.BLOCK);
ResourceLocation key = blockRegistry.getKey(block);
if (key != null) {
return key.toString();
}
return block.getClass().getName();
}


@SuppressWarnings("ConstantConditions")
public static void checkNotEmpty(ItemStack itemStack) {
if (itemStack == null) {
Expand Down Expand Up @@ -164,28 +170,24 @@ public static <T> void validateRecipes(RecipeType<T> recipeType, Iterable<? exte
}

public static <T> CrashReport createIngredientCrashReport(Throwable throwable, String title, IIngredientManager ingredientManager, ITypedIngredient<T> typedIngredient) {
CrashReport crashReport = CrashReport.forThrowable(throwable, title);

IIngredientType<T> ingredientType = typedIngredient.getType();
IIngredientHelper<T> ingredientHelper = ingredientManager.getIngredientHelper(ingredientType);
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, ingredientHelper);
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");
IIngredientType<T> ingredientType = typedIngredient.getType();
IIngredientHelper<T> ingredientHelper = ingredientManager.getIngredientHelper(ingredientType);
setIngredientCategoryDetails(category, typedIngredient, ingredientHelper);
setIngredientCategoryDetails(category, ingredientType, ingredient, ingredientManager);
LOGGER.error(crashReportToString(throwable, title, category));
}

private static <T> void setIngredientCategoryDetails(CrashReportCategory category, ITypedIngredient<T> typedIngredient, IIngredientHelper<T> ingredientHelper) {
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);
IPlatformModHelper modHelper = Services.PLATFORM.getModHelper();

category.setDetail("Name", () -> ingredientHelper.getDisplayName(ingredient));
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,17 +1,17 @@
package mezz.jei.api.ingredients;

import java.util.Collection;
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.Collection;
import java.util.List;

/**
* Renders a type of ingredient in JEI's item list and recipes.
Expand Down Expand Up @@ -51,6 +51,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 15.16.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 15.16.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;
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,6 @@ IRecipeSlotDrawable createRecipeSlotDrawable(
*
* @see RecipeType#getUid()
* @since 11.2.3
* @deprecated use {@link #getRecipeType(ResourceLocation, Class)}
*/
Optional<RecipeType<?>> getRecipeType(ResourceLocation recipeUid);
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,6 @@ public void setBookmarkEnabled(boolean value) {

@Override
public void addEditModeToggleListener(IEditModeListener listener) {

}
}
20 changes: 4 additions & 16 deletions Gui/src/main/java/mezz/jei/gui/bookmarks/RecipeBookmark.java
Original file line number Diff line number Diff line change
@@ -1,18 +1,14 @@
package mezz.jei.gui.bookmarks;

import mezz.jei.api.gui.IRecipeLayoutDrawable;
import mezz.jei.api.gui.drawable.IDrawable;
import mezz.jei.api.gui.ingredient.IRecipeSlotView;
import mezz.jei.api.gui.ingredient.IRecipeSlotsView;
import mezz.jei.api.helpers.IGuiHelper;
import mezz.jei.api.ingredients.ITypedIngredient;
import mezz.jei.api.recipe.IRecipeManager;
import mezz.jei.api.recipe.RecipeIngredientRole;
import mezz.jei.api.recipe.category.IRecipeCategory;
import mezz.jei.api.runtime.IIngredientManager;
import mezz.jei.gui.overlay.elements.IElement;
import mezz.jei.gui.overlay.elements.RecipeBookmarkElement;
import mezz.jei.gui.recipes.RecipeCategoryIconUtil;
import net.minecraft.resources.ResourceLocation;

import java.util.List;
Expand All @@ -28,9 +24,7 @@ public class RecipeBookmark<R, I> implements IBookmark {

public static <T> Optional<RecipeBookmark<T, ?>> create(
IRecipeLayoutDrawable<T> recipeLayoutDrawable,
IIngredientManager ingredientManager,
IRecipeManager recipeManager,
IGuiHelper guiHelper
IIngredientManager ingredientManager
) {
T recipe = recipeLayoutDrawable.getRecipe();
IRecipeCategory<T> recipeCategory = recipeLayoutDrawable.getRecipeCategory();
Expand All @@ -47,13 +41,8 @@ public class RecipeBookmark<R, I> implements IBookmark {
continue;
}
ITypedIngredient<?> output = outputOptional.get();
IDrawable icon = RecipeCategoryIconUtil.create(
recipeCategory,
recipeManager,
guiHelper
);
output = ingredientManager.normalizeTypedIngredient(output);
return Optional.of(new RecipeBookmark<>(recipeCategory, recipe, recipeUid, output, icon));
return Optional.of(new RecipeBookmark<>(recipeCategory, recipe, recipeUid, output));
}
return Optional.empty();
}
Expand All @@ -62,14 +51,13 @@ public RecipeBookmark(
IRecipeCategory<R> recipeCategory,
R recipe,
ResourceLocation recipeUid,
ITypedIngredient<I> recipeOutput,
IDrawable icon
ITypedIngredient<I> recipeOutput
) {
this.recipeCategory = recipeCategory;
this.recipe = recipe;
this.recipeUid = recipeUid;
this.recipeOutput = recipeOutput;
this.element = new RecipeBookmarkElement<>(this, icon);
this.element = new RecipeBookmarkElement<>(this);
}

public IRecipeCategory<R> getRecipeCategory() {
Expand Down
4 changes: 2 additions & 2 deletions Gui/src/main/java/mezz/jei/gui/config/BookmarkConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ public void saveBookmarks(
getPath(jeiConfigurationDir)
.ifPresent(path -> {
TypedIngredientSerializer ingredientSerializer = new TypedIngredientSerializer(ingredientManager);
RecipeBookmarkSerializer recipeBookmarkSerializer = new RecipeBookmarkSerializer(recipeManager, focusFactory, ingredientSerializer, guiHelper);
RecipeBookmarkSerializer recipeBookmarkSerializer = new RecipeBookmarkSerializer(recipeManager, focusFactory, ingredientSerializer);

List<String> strings = new ArrayList<>();
for (IBookmark bookmark : bookmarks) {
Expand Down Expand Up @@ -123,7 +123,7 @@ public void loadBookmarks(
}

TypedIngredientSerializer ingredientSerializer = new TypedIngredientSerializer(ingredientManager);
RecipeBookmarkSerializer recipeBookmarkSerializer = new RecipeBookmarkSerializer(recipeManager, focusFactory, ingredientSerializer, guiHelper);
RecipeBookmarkSerializer recipeBookmarkSerializer = new RecipeBookmarkSerializer(recipeManager, focusFactory, ingredientSerializer);

Collection<IIngredientType<?>> otherIngredientTypes = ingredientManager.getRegisteredIngredientTypes()
.stream()
Expand Down
Loading

0 comments on commit f4d3577

Please sign in to comment.