Skip to content

Commit

Permalink
Expose a way to iterate internal stacks of a QIO Frequency to the API…
Browse files Browse the repository at this point in the history
… to allow for slightly better performance in some use cases
  • Loading branch information
pupnewfster committed Jan 5, 2023
1 parent c2ea3e4 commit 25459b3
Show file tree
Hide file tree
Showing 22 changed files with 225 additions and 92 deletions.
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2017-2022 Aidan C. Brady
Copyright (c) 2017-2023 Aidan C. Brady

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ If you would like to help translate Mekanism, you can do so through [Crowdin](ht

Mekanism is licensed under the MIT license. You may use it in modpacks, reviews, or any other form as long as you abide by the terms below.

Copyright 2017-2022 Aidan C. Brady
Copyright 2017-2023 Aidan C. Brady

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

Expand Down
4 changes: 2 additions & 2 deletions src/api/java/mekanism/api/gear/config/ModuleEnumData.java
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ public ModuleEnumData(TYPE def, int selectableCount) {
*
* @deprecated since 10.3.2, use the version ({@link #ModuleEnumData(Enum)}) that figures out the class automatically.
*/
@Deprecated(forRemoval = true)
@Deprecated(forRemoval = true, since = "10.3.2")
public ModuleEnumData(Class<TYPE> enumClass, TYPE def) {
this(def);
Objects.requireNonNull(enumClass, "Enum Class cannot be null.");
Expand All @@ -81,7 +81,7 @@ public ModuleEnumData(Class<TYPE> enumClass, TYPE def) {
*
* @deprecated since 10.3.2, use the version ({@link #ModuleEnumData(Enum, int)}) that figures out the class automatically.
*/
@Deprecated(forRemoval = true)
@Deprecated(forRemoval = true, since = "10.3.2")
public ModuleEnumData(Class<TYPE> enumClass, int selectableCount, TYPE def) {
this(def, selectableCount);
Objects.requireNonNull(enumClass, "Enum Class cannot be null.");
Expand Down
64 changes: 64 additions & 0 deletions src/api/java/mekanism/api/inventory/IHashedItem.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package mekanism.api.inventory;

import net.minecraft.MethodsReturnNonnullByDefault;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import org.jetbrains.annotations.Nullable;

/**
* Represents an "item type" for comparing {@link ItemStack ItemStack's} without size.
*
* @since 10.3.6
*/
@MethodsReturnNonnullByDefault
public interface IHashedItem {

/**
* Gets the internal {@link ItemStack} that backs this item type. It is <strong>IMPORTANT</strong> to not modify the returned value.
*
* @return The internal {@link ItemStack} that backs this item type.
*
* @apiNote Do not modify the returned value. This is only exposed for cases where the stack is needed for performance reasons and mutation is not needed.
*/
ItemStack getInternalStack();

/**
* Creates a mutable {@link ItemStack} of this type with the given size.
*
* @param size Size of the stack to create.
*
* @return A new {@link ItemStack} of this type.
*/
ItemStack createStack(int size);

/**
* Helper to get the {@link Item} that this item type represents.
*
* @return The {@link Item} that this item type represents.
*/
default Item getItem() {
return getInternalStack().getItem();
}

/**
* Helper to get the max stack size of the {@link Item} that this item type represents.
*
* @return Max stack size of the {@link Item} that this item type represents.
*/
default int getMaxStackSize() {
return getInternalStack().getMaxStackSize();
}

/**
* Helper to get the tag of the internal {@link ItemStack} that backs this item type. It is <strong>IMPORTANT</strong> to not modify the returned tag.
*
* @return Tag of the internal {@link ItemStack} that backs this item type.
*
* @apiNote Do not modify the returned tag.
*/
@Nullable
default CompoundTag getInternalTag() {
return getInternalStack().getTag();
}
}
11 changes: 11 additions & 0 deletions src/api/java/mekanism/api/inventory/qio/IQIOFrequency.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import java.util.function.ObjLongConsumer;
import mekanism.api.Action;
import mekanism.api.IFrequency;
import mekanism.api.inventory.IHashedItem;
import net.minecraft.world.item.ItemStack;

/**
Expand All @@ -29,6 +30,16 @@ public interface IQIOFrequency extends IFrequency {
*/
void forAllStored(ObjLongConsumer<ItemStack> consumer);

/**
* Performs the given action for every item type stored in this QIO Frequency. Each action will be provided with the stored {@link IHashedItem} representing the type,
* and a long representing the amount of that item type that is stored.
*
* @param consumer Action to be performed.
*
* @since 10.3.6
*/
void forAllHashedStored(ObjLongConsumer<IHashedItem> consumer);

/**
* Attempts to insert a given item type into this QIO Frequency.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import mekanism.common.MekanismLang;
import mekanism.common.inventory.ISlotClickHandler;
import mekanism.common.inventory.ISlotClickHandler.IScrollableSlot;
import mekanism.common.lib.inventory.HashedItem;
import mekanism.common.util.MekanismUtils;
import mekanism.common.util.MekanismUtils.ResourceType;
import mekanism.common.util.text.TextUtils;
Expand Down Expand Up @@ -137,7 +138,7 @@ private void renderSlot(PoseStack matrix, IScrollableSlot slot, int slotX, int s
if (isSlotEmpty(slot)) {
return;
}
gui().renderItemWithOverlay(matrix, slot.getItem().getStack(), slotX + 1, slotY + 1, 1, "");
gui().renderItemWithOverlay(matrix, slot.getItem().getInternalStack(), slotX + 1, slotY + 1, 1, "");
if (slot.getCount() > 1) {
renderSlotText(matrix, getCountText(slot.getCount()), slotX + 1, slotY + 1);
}
Expand All @@ -148,7 +149,7 @@ private void renderSlotTooltip(PoseStack matrix, IScrollableSlot slot, int slotX
if (isSlotEmpty(slot)) {
return;
}
ItemStack stack = slot.getItem().getStack();
ItemStack stack = slot.getItem().getInternalStack();
long count = slot.getCount();
if (count < 10_000) {
gui().renderItemTooltip(matrix, stack, slotX, slotY);
Expand All @@ -160,7 +161,10 @@ private void renderSlotTooltip(PoseStack matrix, IScrollableSlot slot, int slotX
}

private boolean isSlotEmpty(IScrollableSlot slot) {
return slot.getItem() == null || slot.getItem().getStack().isEmpty();
//Slot's item is not null in default impl, but check in case we make it null at some point
// and also validate if the internal stack is empty in case it is raw and there is some edge case
HashedItem item = slot.getItem();
return item == null || item.getInternalStack().isEmpty();
}

private void renderSlotText(PoseStack matrix, String text, int x, int y) {
Expand Down Expand Up @@ -203,6 +207,6 @@ private List<IScrollableSlot> getSlotList() {
@Override
public Object getIngredient(double mouseX, double mouseY) {
IScrollableSlot slot = getSlot(mouseX, mouseY);
return slot == null ? null : slot.getItem().getStack();
return slot == null ? null : slot.getItem().getInternalStack();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,10 @@ protected void addGuiElements() {
.tooltip(GuiQIOFilterHandler.getFrequencyTooltip(tile)));
addRenderableWidget(new GuiInnerScreen(this, 27, 30, imageWidth - 27 - 8, 64, () -> {
List<Component> list = new ArrayList<>();
list.add(tile.getItemType().isEmpty() ? MekanismLang.QIO_ITEM_TYPE_UNDEFINED.translate() : tile.getItemType().getHoverName());
ItemStack itemType = tile.getItemType();
list.add(itemType.isEmpty() ? MekanismLang.QIO_ITEM_TYPE_UNDEFINED.translate() : itemType.getHoverName());
list.add(MekanismLang.QIO_TRIGGER_COUNT.translate(TextUtils.format(tile.getCount())));
if (!tile.getItemType().isEmpty() && tile.getQIOFrequency() != null) {
if (!itemType.isEmpty() && tile.getQIOFrequency() != null) {
list.add(MekanismLang.QIO_STORED_COUNT.translate(TextUtils.format(tile.getStoredCount())));
}
list.add(MekanismLang.QIO_FUZZY_MODE.translate(tile.getFuzzyMode()));
Expand All @@ -80,9 +81,7 @@ private void setCount() {
protected void drawForegroundText(@NotNull PoseStack matrix, int mouseX, int mouseY) {
renderTitleText(matrix);
drawString(matrix, playerInventoryTitle, inventoryLabelX, inventoryLabelY, titleTextColor());
if (tile.getItemType() != null) {
renderItem(matrix, tile.getItemType(), 8, 31);
}
renderItem(matrix, tile.getItemType(), 8, 31);
super.drawForegroundText(matrix, mouseX, mouseY);
}

Expand Down
26 changes: 13 additions & 13 deletions src/main/java/mekanism/client/jei/QIOCraftingTransferHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.function.Function;
import mekanism.api.inventory.IInventorySlot;
import mekanism.api.math.MathUtils;
import mekanism.common.Mekanism;
Expand Down Expand Up @@ -62,12 +63,12 @@ public class QIOCraftingTransferHandler<CONTAINER extends QIOItemViewerContainer

private final IRecipeTransferHandlerHelper handlerHelper;
private final Class<CONTAINER> containerClass;
private final IStackHelper stackHelper;
private final Function<HashedItem, String> recipeUUIDFunction;

public QIOCraftingTransferHandler(IRecipeTransferHandlerHelper handlerHelper, IStackHelper stackHelper, Class<CONTAINER> containerClass) {
this.handlerHelper = handlerHelper;
this.stackHelper = stackHelper;
this.containerClass = containerClass;
this.recipeUUIDFunction = hashed -> stackHelper.getUniqueIdentifierForStack(hashed.getInternalStack(), UidContext.Recipe);
}

@Override
Expand Down Expand Up @@ -162,7 +163,9 @@ record TrackedIngredients(IRecipeSlotView view, Set<HashedItem> representations)
//Then add all valid ingredients in the order they appear in JEI. Because we are using a set
// we will just end up merging with the displayed ingredient when we get to it as a valid ingredient
for (ItemStack validIngredient : validIngredients) {
representations.add(HashedItem.raw(validIngredient));
if (!validIngredient.isEmpty()) {//Shouldn't be empty but validate it just in case
representations.add(HashedItem.raw(validIngredient));
}
}
hashedIngredients.put((byte) index, new TrackedIngredients(slotView, representations));
}
Expand Down Expand Up @@ -217,24 +220,22 @@ record TrackedIngredients(IRecipeSlotView view, Set<HashedItem> representations)
if (source.hasMoreRemaining()) {
//Only look at the source if we still have more items available in it
HashedItem storedHashedItem = entry.getKey();
ItemStack storedItem = storedHashedItem.getStack();
Item storedItemType = storedItem.getItem();
Item storedItemType = storedHashedItem.getItem();
String storedItemUUID = null;
for (ByteIterator missingIterator = missingSlots.iterator(); missingIterator.hasNext(); ) {
byte index = missingIterator.nextByte();
for (HashedItem validIngredient : hashedIngredients.get(index).representations()) {
//Compare the raw item types
if (storedItemType == validIngredient.getStack().getItem()) {
if (storedItemType == validIngredient.getItem()) {
//If they match, compute the identifiers for both stacks as needed
if (storedItemUUID == null) {
//If we haven't retrieved a UUID for the stored stack yet because none of our previous ingredients
// matched the basic item type, retrieve it
storedItemUUID = stackHelper.getUniqueIdentifierForStack(storedItem, UidContext.Recipe);
storedItemUUID = recipeUUIDFunction.apply(storedHashedItem);
}
//Next compute the UUID for the ingredient we are missing if we haven't already calculated it
// either in a previous iteration or for a different slot
String ingredientUUID = cachedIngredientUUIDs.computeIfAbsent(validIngredient,
ingredient -> stackHelper.getUniqueIdentifierForStack(ingredient.getStack(), UidContext.Recipe));
String ingredientUUID = cachedIngredientUUIDs.computeIfAbsent(validIngredient, recipeUUIDFunction);
if (storedItemUUID.equals(ingredientUUID)) {
//If the items are equivalent, reduce how much of the item we have as an input
source.matchFound();
Expand Down Expand Up @@ -280,7 +281,7 @@ record TrackedIngredients(IRecipeSlotView view, Set<HashedItem> representations)
//If something went wrong, and we don't actually have the item we think we do, error
return invalidSource(hashedItem);
}
int maxStack = hashedItem.getStack().getMaxStackSize();
int maxStack = hashedItem.getMaxStackSize();
//If we have something that only stacks to one, such as a bucket. Don't limit the max stack size
// of other items to one
long max = maxStack == 1 ? maxToTransfer : Math.min(maxToTransfer, maxStack);
Expand All @@ -306,7 +307,7 @@ record TrackedIngredients(IRecipeSlotView view, Set<HashedItem> representations)
// and other stuff with it. This only actually matters if the max stack size is one, due to
// the logic done above when calculating how much to transfer, but we do this regardless here
// as there is no reason not to and then if we decide to widen it up we only have to change one spot
int transferAmount = Math.min(toTransfer, hashedItem.getStack().getMaxStackSize());
int transferAmount = Math.min(toTransfer, hashedItem.getMaxStackSize());
for (byte slot : entry.getValue()) {
//Try to use the item and figure out where it is coming from
List<SingularHashedItemSource> actualSources = source.use(transferAmount);
Expand Down Expand Up @@ -341,8 +342,7 @@ record TrackedIngredients(IRecipeSlotView view, Set<HashedItem> representations)
}

private IRecipeTransferError invalidSource(@NotNull HashedItem type) {
ItemStack stack = type.getStack();
Mekanism.logger.warn("Error finding source for: {} with nbt: {}. This should not be possible happen.", stack.getItem(), stack.getTag());
Mekanism.logger.warn("Error finding source for: {} with nbt: {}. This should not be possible.", type.getItem(), type.getInternalTag());
return handlerHelper.createInternalError();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ protected BaseSimulatedInventory(List<HotBarSlot> hotBarSlots, List<MainInventor
inventorySlot = mainInventorySlots.get(slot - hotBarSize);
}
ItemStack stack = inventorySlot.getItem();
int remaining = getRemaining(slot, stack);
int remaining = stack.isEmpty() ? 0 : getRemaining(slot, stack);
if (remaining == 0) {
//If there is nothing "available" in the slot anymore that means the slot is "empty"
stack = ItemStack.EMPTY;
Expand Down Expand Up @@ -318,7 +318,7 @@ public int shuffleItem(HashedItem type, int amount) {
if (amount == 0) {
return 0;
}
ItemStack stack = type.getStack();
ItemStack stack = type.getInternalStack();
//Start by checking for slots it can stack with
for (int slot = 0; slot < inventory.length; slot++) {
int currentAmount = stackSizes[slot];
Expand All @@ -344,6 +344,8 @@ public int shuffleItem(HashedItem type, int amount) {
// track of the actual backing slot we could check it if needed, though that would have a chance
// of giving incorrect information anyway if it returns false for mayPlace based oen what was
// stored and is no longer stored
//Note: We also can use the raw backing stack in this array as we do not do any mutations,
// and this allows us to then avoid an extra copy call
inventory[slot] = stack;
slotLimits[slot] = max = Math.min(max, stack.getMaxStackSize());
int toPlace = stackSizes[slot] = Math.min(amount, max);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -784,6 +784,10 @@ public void findEquivalentItem(Level world, @NotNull QIOFrequency frequency, Cra
// in attempting to find a replacement
if (usedIngredient.test(used)) {
for (ItemStack item : usedIngredient.getItems()) {
if (item.isEmpty()) {
//If for some reason the ingredient returns empty stacks, just skip those
continue;
}
//If the ingredient is not vanilla, we start by checking against the exact stack it has stored as an item
// Note: We can use a raw hashed item here as we don't store it anywhere, and just use it as a lookup
if (!usedIngredient.isVanilla() && testEquivalentItem(world, frequency, slot, index, usedIngredient, HashedItem.raw(item))) {
Expand All @@ -808,7 +812,7 @@ public void findEquivalentItem(Level world, @NotNull QIOFrequency frequency, Cra

private boolean testEquivalentItem(Level world, @NotNull QIOFrequency frequency, CraftingWindowInventorySlot slot, int index, Ingredient usedIngredient,
HashedItem replacementType) {
if (!frequency.isStoring(replacementType) || !usedIngredient.test(replacementType.getStack())) {
if (!frequency.isStoring(replacementType) || !usedIngredient.test(replacementType.getInternalStack())) {
//Our frequency doesn't actually have the item stored we are trying to use or the type we are trying
// doesn't actually match the ingredient we have for that slot
return false;
Expand Down
Loading

0 comments on commit 25459b3

Please sign in to comment.