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) (#7763) (#8065)

(cherry picked from commit bf1a482)
  • Loading branch information
shartte authored Jul 25, 2024
1 parent e77c547 commit e6a2825
Show file tree
Hide file tree
Showing 15 changed files with 158 additions and 69 deletions.
39 changes: 35 additions & 4 deletions src/main/java/appeng/api/stacks/AEItemKey.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
import java.util.WeakHashMap;

import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import net.minecraft.core.BlockPos;
import net.minecraft.core.registries.BuiltInRegistries;
Expand All @@ -19,15 +21,17 @@
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 net.minecraftforge.common.capabilities.CapabilityProvider;

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);

private static final MethodHandle SERIALIZE_CAPS_HANDLE;
static {
try {
Expand Down Expand Up @@ -55,6 +59,12 @@ private static CompoundTag serializeStackCaps(ItemStack stack) {
private final InternedTag internedCaps;
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.
*/
@Nullable
private ItemStack readOnlyStack;

/**
* Max stack size cache, or {@code -1} if not initialized.
Expand Down Expand Up @@ -139,6 +149,27 @@ 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.isEmpty()) {
LOG.error("Something destroyed the read-only itemstack of {}", this);
readOnlyStack = null;
return getReadOnlyStack();
}
}
return readOnlyStack;
}

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

@Override
Expand Down Expand Up @@ -250,7 +281,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 @@ -271,7 +302,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 @@ -666,7 +666,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
59 changes: 59 additions & 0 deletions src/main/java/appeng/client/gui/me/common/Repo.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@

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;
Expand All @@ -41,6 +45,7 @@
import appeng.api.config.SortDir;
import appeng.api.config.SortOrder;
import appeng.api.config.ViewItems;
import appeng.api.stacks.AEItemKey;
import appeng.api.stacks.AEKey;
import appeng.client.gui.me.search.RepoSearch;
import appeng.client.gui.widgets.IScrollSource;
Expand Down Expand Up @@ -74,6 +79,11 @@ public class Repo implements IClientRepo {
private final BiMap<Long, GridInventoryEntry> entries = HashBiMap.create();
private final ArrayList<GridInventoryEntry> view = new ArrayList<>();
private final ArrayList<GridInventoryEntry> pinnedRow = new ArrayList<>();
/**
* Entries by item ID to speed up ingredient matching.
*/
private final Int2ObjectOpenHashMap<List<GridInventoryEntry>> entriesByItemId = new Int2ObjectOpenHashMap<>();
private boolean entriesByItemIdNeedsUpdate = true;
private final RepoSearch search = new RepoSearch();
private IPartitionList partitionList;
private Runnable updateViewListener;
Expand Down Expand Up @@ -108,6 +118,7 @@ public final void handleUpdate(boolean fullUpdate, List<GridInventoryEntry> entr
}

private void handleUpdate(GridInventoryEntry serverEntry) {
entriesByItemIdNeedsUpdate = true;

var localEntry = entries.get(serverEntry.getSerial());
if (localEntry == null) {
Expand Down Expand Up @@ -346,6 +357,8 @@ public final void clear() {
this.entries.clear();
this.view.clear();
this.pinnedRow.clear();
this.entriesByItemId.clear();
this.entriesByItemIdNeedsUpdate = true;
}

public final boolean hasPinnedRow() {
Expand Down Expand Up @@ -399,6 +412,52 @@ public Set<GridInventoryEntry> getAllEntries() {
return entries.values();
}

@Override
public List<GridInventoryEntry> getByIngredient(Ingredient ingredient) {
var entries = new ArrayList<GridInventoryEntry>();
for (int i = 0; i < ingredient.getStackingIds().size(); i++) {
var itemId = ingredient.getStackingIds().getInt(i);
for (var entry : getByItemId(itemId)) {
if (((AEItemKey) entry.getWhat()).matches(ingredient)) {
entries.add(entry);
}
}
}
return entries;
}

private Collection<GridInventoryEntry> getByItemId(int itemId) {
// Build the itemid->entry map if needed
if (entriesByItemIdNeedsUpdate) {
rebuildItemIdToEntries();
entriesByItemIdNeedsUpdate = false;
}
return entriesByItemId.getOrDefault(itemId, List.of());
}

private void rebuildItemIdToEntries() {
entriesByItemId.clear();
for (var entry : getAllEntries()) {
if (entry.getWhat() instanceof AEItemKey itemKey) {
var itemId = BuiltInRegistries.ITEM.getId(itemKey.getItem());
var currentList = entriesByItemId.get(itemId);
if (currentList == null) {
// For many items without NBT, this list will only ever have one entry
entriesByItemId.put(itemId, List.of(entry));
} else if (currentList.size() == 1) {
// Convert the list from an immutable single-entry list to a mutable normal arraylist
var mutableList = new ArrayList<GridInventoryEntry>(10);
mutableList.addAll(currentList);
mutableList.add(entry);
entriesByItemId.put(itemId, mutableList);
} else {
// If it had more than 1 item, it must have been mutable already
currentList.add(entry);
}
}
}
}

public final void setUpdateViewListener(Runnable updateViewListener) {
this.updateViewListener = updateViewListener;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,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 @@ -304,7 +304,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 @@ -315,7 +315,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 @@ -37,7 +37,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.minecraftforge.client.extensions.common.IClientFluidTypeExtensions;
Expand Down Expand Up @@ -67,7 +66,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 @@ -87,21 +86,21 @@ 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) {
try {
return stack.toStack().getTooltipLines(Minecraft.getInstance().player,
return stack.getReadOnlyStack().getTooltipLines(Minecraft.getInstance().player,
Minecraft.getInstance().options.advancedItemTooltips ? TooltipFlag.Default.ADVANCED
: TooltipFlag.Default.NORMAL);
} catch (Exception e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,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 @@ -129,7 +129,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 e6a2825

Please sign in to comment.