Skip to content

Commit

Permalink
Optimize performance of "can craft" checks in ME Terminals (for EMI/J…
Browse files Browse the repository at this point in the history
…EI/REI)

Also introduce a read-only ItemStack in AEItemKey to further optimize rendering.
  • Loading branch information
shartte committed Mar 14, 2024
1 parent 9f0e44c commit b990a09
Show file tree
Hide file tree
Showing 12 changed files with 81 additions and 79 deletions.
26 changes: 18 additions & 8 deletions src/main/java/appeng/api/stacks/AEItemKey.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@
import net.minecraft.tags.TagKey;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.Ingredient;
import net.minecraft.world.level.ItemLike;
import net.minecraft.world.level.Level;

import appeng.api.storage.AEKeyFilter;
import appeng.core.AELog;
import appeng.util.Platform;

public final class AEItemKey extends AEKey {
private static final Logger LOG = LoggerFactory.getLogger(AEItemKey.class);
Expand All @@ -46,8 +46,8 @@ private static CompoundTag serializeStackCaps(ItemStack stack) {
private final int hashCode;
private final int cachedDamage;
/**
* A lazily initialized itemstack used for display and ingredient testing purposes.
* This should never be modified and will always have amount 1.
* A lazily initialized itemstack used for display and ingredient testing purposes. This should never be modified
* and will always have amount 1.
*/
@Nullable
private ItemStack readOnlyStack;
Expand Down Expand Up @@ -135,15 +135,25 @@ public boolean matches(ItemStack stack) {
&& Objects.equals(serializeStackCaps(stack), internedCaps.tag);
}

public boolean matches(Ingredient ingredient) {
return ingredient.test(getReadOnlyStack());
}

/**
* @return The ItemStack represented by this key. <strong>NEVER MUTATE THIS</strong>
*/
public ItemStack getReadOnlyStack() {
if (readOnlyStack == null) {
readOnlyStack = new ItemStack(item, 1, internedCaps.tag);
readOnlyStack.setTag(internedTag.tag);
} else {
if (readOnlyStack.getCount() != 1) {
AELog.error("");
if (readOnlyStack.isEmpty()) {
LOG.error("Something destroyed the read-only itemstack of {}", this);
readOnlyStack = null;
return getReadOnlyStack();
}
}
return readOnlyStack;
}

public ItemStack toStack() {
Expand Down Expand Up @@ -226,7 +236,7 @@ public int getFuzzySearchValue() {
*/
@Override
public int getFuzzySearchMaxValue() {
return item.getMaxDamage();
return getReadOnlyStack().getMaxDamage();
}

@Override
Expand Down Expand Up @@ -272,7 +282,7 @@ public void addDrops(long amount, List<ItemStack> drops, Level level, BlockPos p

@Override
protected Component computeDisplayName() {
return Platform.getItemDisplayName(item, internedTag.tag);
return getReadOnlyStack().getHoverName();
}

@SuppressWarnings("unchecked")
Expand All @@ -293,7 +303,7 @@ public int getMaxStackSize() {
int ret = maxStackSize;

if (ret == -1) {
maxStackSize = ret = toStack().getMaxStackSize();
maxStackSize = ret = getReadOnlyStack().getMaxStackSize();
}

return ret;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,15 +170,14 @@ public void cleanSide(Direction side) {
}

public void addBlot(ItemStack type, Direction side, Vec3 hitVec) {
final BlockPos p = this.worldPosition.relative(side);
PaintBallItem paintBallItem = (PaintBallItem) type.getItem();
addBlot(paintBallItem.getColor(), paintBallItem.isLumen(), side, hitVec);
}

final BlockState blk = this.level.getBlockState(p);
public void addBlot(AEColor color, boolean lit, Direction side, Vec3 hitVec) {
var p = this.worldPosition.relative(side);
var blk = this.level.getBlockState(p);
if (blk.isFaceSturdy(this.level, p, side.getOpposite())) {
final PaintBallItem ipb = (PaintBallItem) type.getItem();

final AEColor col = ipb.getColor();
final boolean lit = ipb.isLumen();

if (this.dots == null) {
this.dots = new ArrayList<>();
}
Expand All @@ -187,7 +186,7 @@ public void addBlot(ItemStack type, Direction side, Vec3 hitVec) {
this.dots.remove(0);
}

this.dots.add(new Splotch(col, lit, side, hitVec));
this.dots.add(new Splotch(color, lit, side, hitVec));

updateData();
this.markForUpdate();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -698,7 +698,7 @@ protected void renderGridInventoryEntryTooltip(GuiGraphics guiGraphics, GridInve

// Special case to support the Item API of visual tooltip components
if (entry.getWhat() instanceof AEItemKey itemKey) {
var stack = itemKey.toStack();
var stack = itemKey.getReadOnlyStack();
// By using the overload of the renderTooltip method that takes an ItemStack, we support the Forge tooltip
// event system
guiGraphics.renderTooltip(font, currentToolTip, stack.getTooltipImage(), stack, x, y);
Expand Down
54 changes: 29 additions & 25 deletions src/main/java/appeng/client/gui/me/common/Repo.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,30 @@

package appeng.client.gui.me.common;

import java.time.Instant;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;

import org.jetbrains.annotations.Nullable;

import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.world.item.crafting.Ingredient;

import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
import it.unimi.dsi.fastutil.longs.LongSet;

import appeng.api.config.SortDir;
import appeng.api.config.SortOrder;
import appeng.api.config.ViewItems;
Expand All @@ -30,26 +54,6 @@
import appeng.menu.me.common.GridInventoryEntry;
import appeng.menu.me.common.IClientRepo;
import appeng.util.prioritylist.IPartitionList;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
import it.unimi.dsi.fastutil.longs.LongSet;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.world.item.crafting.Ingredient;
import org.jetbrains.annotations.Nullable;

import java.time.Instant;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
* For showing the network content of a storage channel, this class will maintain a client-side copy of the current
Expand All @@ -59,7 +63,7 @@ public class Repo implements IClientRepo {

public static final Comparator<GridInventoryEntry> AMOUNT_ASC = Comparator
.comparingDouble((GridInventoryEntry entry) -> ((double) entry.getStoredAmount())
/ ((double) entry.getWhat().getAmountPerUnit()));
/ ((double) entry.getWhat().getAmountPerUnit()));

public static final Comparator<GridInventoryEntry> AMOUNT_DESC = AMOUNT_ASC.reversed();

Expand Down Expand Up @@ -167,7 +171,7 @@ public final void updateView() {
// First, try to find an empty/meaningless slot in the view that is visually indistinguishable
// and fill it
if (takeOverSlotOccupiedByRemovedItem(serverEntry, pinnedRowFreeSlots, pinnedRow)
|| takeOverSlotOccupiedByRemovedItem(serverEntry, viewFreeSlots, view)) {
|| takeOverSlotOccupiedByRemovedItem(serverEntry, viewFreeSlots, view)) {
continue;
}

Expand Down Expand Up @@ -243,7 +247,7 @@ private void addEntriesToView(Collection<GridInventoryEntry> entries) {
for (var pinnedKey : PinnedKeys.getPinnedKeys()) {
var info = PinnedKeys.getPinInfo(pinnedKey);
if (info.reason != PinnedKeys.PinReason.CRAFTING
&& pinnedRow.stream().noneMatch(r -> pinnedKey.equals(r.getWhat()))) {
&& pinnedRow.stream().noneMatch(r -> pinnedKey.equals(r.getWhat()))) {
this.pinnedRow.add(new GridInventoryEntry(
-1, pinnedKey, 0, 0, false));
}
Expand Down Expand Up @@ -297,7 +301,7 @@ private Map<AEKey, IntList> getFreeSlots(List<GridInventoryEntry> slots) {
}

private static boolean takeOverSlotOccupiedByRemovedItem(GridInventoryEntry serverEntry,
Map<AEKey, IntList> freeSlots, List<GridInventoryEntry> slots) {
Map<AEKey, IntList> freeSlots, List<GridInventoryEntry> slots) {
IntList freeSlotIndices = freeSlots.get(serverEntry.getWhat());
if (freeSlotIndices == null) {
return false;
Expand Down Expand Up @@ -414,7 +418,7 @@ public List<GridInventoryEntry> getByIngredient(Ingredient ingredient) {
for (int i = 0; i < ingredient.getStackingIds().size(); i++) {
var itemId = ingredient.getStackingIds().getInt(i);
for (var entry : getByItemId(itemId)) {
if (ingredient.test(((AEItemKey) entry.getWhat()).toStack())) {
if (((AEItemKey) entry.getWhat()).matches(ingredient)) {
entries.add(entry);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ public void drawFG(GuiGraphics guiGraphics, int offsetX, int offsetY, int mouseX
if (group.icon() != null) {
var renderContext = new SimpleRenderContext(LytRect.empty(), guiGraphics);
renderContext.renderItem(
group.icon().toStack(),
group.icon().getReadOnlyStack(),
GUI_PADDING_X + PATTERN_PROVIDER_NAME_MARGIN_X,
GUI_PADDING_Y + GUI_HEADER_HEIGHT + i * ROW_HEIGHT,
8,
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/appeng/core/localization/Tooltips.java
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ public static boolean shouldShowAmountTooltip(AEKey what, long amount) {
|| what.getUnitSymbol() != null
// Damaged items always get their amount shown in the tooltip because
// the amount is sometimes hard to read superimposed on the damage bar
|| what instanceof AEItemKey itemKey && itemKey.getItem().isBarVisible(itemKey.toStack());
|| what instanceof AEItemKey itemKey && itemKey.getReadOnlyStack().isBarVisible();
}

public static Component getAmountTooltip(ButtonToolTips baseText, GenericStack stack) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ private List<AEItemKey> findBestMatchingItemStack(Ingredient ingredient, IPartit
// While FuzzyMode.IGNORE_ALL will retrieve all stacks of the same Item which matches
// standard Vanilla Ingredient matching, there are NBT-matching Ingredient subclasses on Forge,
// and Mods might actually have mixed into Ingredient
.filter(e -> ingredient.test(((AEItemKey) e.getKey()).toStack()))
.filter(e -> ((AEItemKey) e.getKey()).matches(ingredient))
// Sort in descending order of availability
.sorted((a, b) -> Long.compare(b.getLongValue(), a.getLongValue()))//
.map(e -> (AEItemKey) e.getKey())//
Expand All @@ -304,7 +304,7 @@ private Optional<AEItemKey> findCraftableKey(Ingredient ingredient, ICraftingSer
return Arrays.stream(ingredient.getItems())//
.map(AEItemKey::of)//
.map(s -> (AEItemKey) craftingService.getFuzzyCraftable(s,
key -> ingredient.test(((AEItemKey) key).toStack())))//
key -> ((AEItemKey) key).matches(ingredient)))//
.filter(Objects::nonNull)//
.findAny();//
}
Expand Down
9 changes: 4 additions & 5 deletions src/main/java/appeng/init/client/InitStackRenderHandlers.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@
import net.minecraft.network.chat.Component;
import net.minecraft.util.Mth;
import net.minecraft.world.item.ItemDisplayContext;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.TooltipFlag;
import net.minecraft.world.level.Level;
import net.neoforged.neoforge.client.extensions.common.IClientFluidTypeExtensions;
Expand Down Expand Up @@ -62,7 +61,7 @@ public void drawInGui(Minecraft minecraft, GuiGraphics guiGraphics, int x, int y
var poseStack = guiGraphics.pose();
poseStack.pushPose();

ItemStack displayStack = stack.toStack();
var displayStack = stack.getReadOnlyStack();
guiGraphics.renderItem(displayStack, x, y);
guiGraphics.renderItemDecorations(minecraft.font, displayStack, x, y, "");

Expand All @@ -82,20 +81,20 @@ public void drawOnBlockFace(PoseStack poseStack, MultiBufferSource buffers, AEIt
// Rotate the normal matrix a little for nicer lighting.
poseStack.last().normal().rotateX(Mth.DEG_TO_RAD * -45f);

Minecraft.getInstance().getItemRenderer().renderStatic(what.toStack(), ItemDisplayContext.GUI,
Minecraft.getInstance().getItemRenderer().renderStatic(what.getReadOnlyStack(), ItemDisplayContext.GUI,
combinedLight, OverlayTexture.NO_OVERLAY, poseStack, buffers, level, 0);

poseStack.popPose();
}

@Override
public Component getDisplayName(AEItemKey stack) {
return stack.toStack().getHoverName();
return stack.getDisplayName();
}

@Override
public List<Component> getTooltip(AEItemKey stack) {
return stack.toStack().getTooltipLines(Minecraft.getInstance().player,
return stack.getReadOnlyStack().getTooltipLines(Minecraft.getInstance().player,
Minecraft.getInstance().options.advancedItemTooltips ? TooltipFlag.Default.ADVANCED
: TooltipFlag.Default.NORMAL);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ private static NonNullList<ItemStack> findGoodTemplateItems(Recipe<?> recipe, ME
// player doesn't actually have
var stack = ingredientPriorities.entrySet()
.stream()
.filter(e -> e.getKey() instanceof AEItemKey itemKey && ingredient.test(itemKey.toStack()))
.filter(e -> e.getKey() instanceof AEItemKey itemKey && itemKey.matches(ingredient))
.max(Comparator.comparingInt(Map.Entry::getValue))
.map(e -> ((AEItemKey) e.getKey()).toStack())
.orElse(ingredient.getItems()[0]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ public static void encodeCraftingRecipe(PatternEncodingTermMenu menu,
// network inventory. We'll find all network inventory entries that it matches and sort them
// according to their suitability for encoding a pattern
var bestNetworkIngredient = prioritizedNetworkInv.entrySet().stream()
.filter(ni -> ni.getKey() instanceof AEItemKey itemKey && ingredient.test(itemKey.toStack()))
.filter(ni -> ni.getKey() instanceof AEItemKey itemKey && itemKey.matches(ingredient))
.max(Comparator.comparingInt(Map.Entry::getValue))
.map(entry -> entry.getKey() instanceof AEItemKey itemKey ? itemKey.toStack() : null);

Expand Down
Loading

0 comments on commit b990a09

Please sign in to comment.