Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ public class GeneralConfig extends DummyConfig {
public static int guiStorageScaleMaxColumns = 32;
@ConfigurableProperty(category = "general", comment = "If the crafting grid should always be shown centrally, and not be responsive based on screen size.", isCommandable = true, configLocation = ModConfig.Type.CLIENT)
public static boolean guiStorageForceCraftingGridCenter = false;
@ConfigurableProperty(category = "general", comment = "If the automatic re-sorting of the storage terminal contents should be paused while the shift key is held down.", isCommandable = true, configLocation = ModConfig.Type.CLIENT)
public static boolean guiStoragePauseSortingWhileShifting = true;

public GeneralConfig() {
super(IntegratedTerminals._instance, "general");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
import org.cyclops.cyclopscore.ingredient.collection.diff.IngredientCollectionDiffHelpers;
import org.cyclops.integrateddynamics.api.ingredient.IIngredientComponentStorageObservable;
import org.cyclops.integrateddynamics.api.network.IPositionedAddonsNetwork;
import org.cyclops.integratedterminals.GeneralConfig;
import org.cyclops.integratedterminals.IntegratedTerminals;
import org.cyclops.integratedterminals.api.ingredient.IIngredientComponentTerminalStorageHandler;
import org.cyclops.integratedterminals.api.ingredient.IIngredientInstanceSorter;
Expand Down Expand Up @@ -68,11 +69,14 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.function.Predicate;
import java.util.stream.Collectors;
Expand Down Expand Up @@ -100,6 +104,7 @@ public class TerminalStorageTabIngredientComponentClient<T, M>

private final Int2ObjectMap<IIngredientCollapsedCollectionMutable<T, M>> ingredientsUnsortedViews;
private final Int2ObjectMap<List<InstanceWithMetadata<T>>> filteredIngredientsViews;
private final Int2ObjectMap<List<InstanceWithMetadata<T>>> lastFilteredIngredientsViews;
private final Int2ObjectMap<Collection<HandlerWrappedTerminalCraftingOption<T>>> craftingOptions;

private final Int2LongMap maxQuantities;
Expand All @@ -110,6 +115,7 @@ public class TerminalStorageTabIngredientComponentClient<T, M>
private int activeSlotQuantity;
private int activeChannel;
private int lastChangeId;
private boolean sortingPaused;

@SubscribeEvent
public static void onToolTip(ItemTooltipEvent event) {
Expand Down Expand Up @@ -144,6 +150,7 @@ public TerminalStorageTabIngredientComponentClient(ContainerTerminalStorageBase

this.ingredientsUnsortedViews = new Int2ObjectOpenHashMap<>();
this.filteredIngredientsViews = new Int2ObjectOpenHashMap<>();
this.lastFilteredIngredientsViews = new Int2ObjectOpenHashMap<>();
this.craftingOptions = new Int2ObjectOpenHashMap<>();

this.maxQuantities = new Int2LongOpenHashMap();
Expand All @@ -153,6 +160,7 @@ public TerminalStorageTabIngredientComponentClient(ContainerTerminalStorageBase
resetActiveSlot();

this.lastChangeId = 0;
this.sortingPaused = false;

}

Expand Down Expand Up @@ -226,7 +234,52 @@ public Predicate<InstanceWithMetadata<T>> getInstanceFilterMetadata() {
}

public void resetFilteredIngredientsViews(int channel) {
resetFilteredIngredientsViews(channel, true);
}

/**
* Reset the filtered ingredients views of the given channel.
* @param channel A channel id.
* @param resetPausedSortingOrder If the ingredient order that is used to keep ingredient positions stable
* while sorting is paused should be forgotten as well.
* This should only be false for changes that are not caused by the user,
* as user-triggered changes should always be applied immediately.
*/
public void resetFilteredIngredientsViews(int channel, boolean resetPausedSortingOrder) {
filteredIngredientsViews.remove(channel);
if (resetPausedSortingOrder) {
lastFilteredIngredientsViews.remove(channel);
}
}

/**
* @return If the automatic re-sorting of ingredients is currently paused.
*/
public boolean isSortingPaused() {
return GeneralConfig.guiStoragePauseSortingWhileShifting && MinecraftHelpers.isShifted();
}

/**
* Check if sorting has been paused or resumed since the last call,
* and re-sort all ingredient views when sorting has been resumed.
* @param channel A channel id.
*/
protected void updateSortingPausedState(int channel) {
boolean sortingPaused = isSortingPaused();
if (this.sortingPaused != sortingPaused) {
// Update the field before doing anything else, so that re-entrant calls become no-ops.
this.sortingPaused = sortingPaused;
if (!sortingPaused) {
// Remember the selected instance, as re-sorting might change its position.
Optional<T> lastInstance = getSlotInstance(channel, this.activeSlotId);

// Enforce a re-sorting of all views
this.filteredIngredientsViews.clear();
this.lastFilteredIngredientsViews.clear();

updateActiveInstance(lastInstance, channel);
}
}
}

@Override
Expand Down Expand Up @@ -282,6 +335,7 @@ public Collection<T> getUniqueCraftingOptionOutputs(ITerminalCraftingOption<T> c
}

protected List<InstanceWithMetadata<T>> getFilteredIngredientsView(int channel) {
updateSortingPausedState(channel);
List<InstanceWithMetadata<T>> ingredientsView = filteredIngredientsViews.get(channel);
if (ingredientsView == null) {
ingredientsView = createUnfilteredIngredientsView(channel);
Expand All @@ -295,21 +349,75 @@ protected List<InstanceWithMetadata<T>> getFilteredIngredientsView(int channel)

// Sort
Comparator<T> sorter = getInstanceSorter();
if (sorter != null) {
try {
List<InstanceWithMetadata<T>> pausedOrder = this.sortingPaused
? lastFilteredIngredientsViews.get(channel) : null;
try {
if (pausedOrder != null) {
// Sorting is paused, so keep the positions of the previously shown ingredients
sortByPreviousOrder(ingredientsView, pausedOrder, sorter);
} else if (sorter != null) {
ingredientsView.sort(InstanceWithMetadata.createComparator(sorter));
} catch (IllegalArgumentException e) {
// We deliberately ignore comparison violations
// If this would cause issues, we'll need to do a deep-copy of all ingredients, which will impact performance
// See https://github.com/CyclopsMC/IntegratedTerminals/issues/119
}
} catch (IllegalArgumentException e) {
// We deliberately ignore comparison violations
// If this would cause issues, we'll need to do a deep-copy of all ingredients, which will impact performance
// See https://github.com/CyclopsMC/IntegratedTerminals/issues/119
}

filteredIngredientsViews.put(channel, ingredientsView);
lastFilteredIngredientsViews.put(channel, ingredientsView);
}
return ingredientsView;
}

/**
* Sort the given ingredients view based on the order of a previously shown ingredients view.
*
* Ingredients that were present in the previous view keep their position, independent of their quantity,
* while new ingredients are appended at the end.
*
* @param ingredientsView The ingredients view to sort in-place.
* @param previousView A previously shown ingredients view.
* @param sorter An optional sorter that is used for ordering the new ingredients.
*/
protected void sortByPreviousOrder(List<InstanceWithMetadata<T>> ingredientsView,

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something seems to be going wrong when I test this out.
It looks like the crafting options can jump around when holding shift.
For example, I had a crafting option with 4 x planks, which first was shown after a 32 x stone stack, but then jumped before it, even though I was sorting by itemstack quantity.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, that was a real bug, fixed in 43c628e.

The positions of the previously shown ingredients were keyed by ingredient only (with the quantity ignored), so a crafting option and the stored stack of that same item shared one position. As soon as the view was rebuilt while shift was held, the crafting option was given the position of the stored stack, and jumped right next to it. In your case there must have been a stored planks stack before the stone, which the planks crafting option then jumped to.

Positions are now keyed by ingredient and crafting option (via InstanceWithMetadata.createComparator, so a stored ingredient and a crafting option for the same item are distinct keys), which keeps them in place.


Generated by Claude Code

List<InstanceWithMetadata<T>> previousView,
@Nullable Comparator<T> sorter) {
IIngredientMatcher<T, M> matcher = this.ingredientComponent.getMatcher();

// Determine the position of all previously shown ingredients, while ignoring their quantities.
// Crafting options are taken into account as well,
// as an ingredient can be shown both as a stored ingredient and as a crafting option.
Map<InstanceWithMetadata<T>, Integer> previousPositions = new TreeMap<>(
InstanceWithMetadata.createComparator(matcher));
int position = 0;
for (InstanceWithMetadata<T> instanceWithMetadata : previousView) {
previousPositions.putIfAbsent(withoutQuantity(instanceWithMetadata), position++);
}

// Assign the previous positions to the current ingredients, new ingredients are placed at the end.
Map<InstanceWithMetadata<T>, Integer> positions = new IdentityHashMap<>();
for (InstanceWithMetadata<T> instanceWithMetadata : ingredientsView) {
positions.put(instanceWithMetadata, previousPositions
.getOrDefault(withoutQuantity(instanceWithMetadata), Integer.MAX_VALUE));
}

ingredientsView.sort(Comparator
.<InstanceWithMetadata<T>>comparingInt(positions::get)
.thenComparing(InstanceWithMetadata.createComparator(sorter != null ? sorter : matcher)));
}

/**
* Create a copy of the given ingredient with a fixed quantity,
* so that it can be used as a quantity-independent key.
* @param instanceWithMetadata An ingredient with metadata.
* @return A quantity-independent copy.
*/
protected InstanceWithMetadata<T> withoutQuantity(InstanceWithMetadata<T> instanceWithMetadata) {
return new InstanceWithMetadata<>(this.ingredientComponent.getMatcher()
.withQuantity(instanceWithMetadata.getInstance(), 1), instanceWithMetadata.getCraftingOption());
}

protected Stream<InstanceWithMetadata<T>> transformIngredientsView(Stream<InstanceWithMetadata<T>> ingredientStream) {
return ingredientStream;
}
Expand Down Expand Up @@ -417,7 +525,7 @@ public synchronized void onChange(int channel, IIngredientComponentStorageObserv
IngredientCollectionDiffHelpers.applyDiff(ingredientComponent, diff, rawPersistedIngredients);

// Persist changes
resetFilteredIngredientsViews(channel);
resetFilteredIngredientsViews(channel, false);

// Update the active instance by searching for its new position in the slots
// If this becomes a performance bottleneck, we could search _around_ the previous position.
Expand Down Expand Up @@ -463,7 +571,7 @@ public synchronized void addCraftingOptions(int channel, List<HandlerWrappedTerm
}

// Persist changes
resetFilteredIngredientsViews(channel);
resetFilteredIngredientsViews(channel, false);

// Update the active instance by searching for its new position in the slots
// If this becomes a performance bottleneck, we could search _around_ the previous position.
Expand Down
Loading