From c93345b568ba0c6231986cac30485e689212c731 Mon Sep 17 00:00:00 2001 From: shedaniel Date: Sun, 25 Dec 2022 00:10:10 +0800 Subject: Implement Unified Ingredients --- .../gui/screen/AbstractDisplayViewingScreen.java | 190 ++++++++++- .../gui/screen/CompositeDisplayViewingScreen.java | 1 + .../gui/screen/DefaultDisplayViewingScreen.java | 2 + .../rei/impl/client/gui/widget/EntryWidget.java | 96 +++--- .../client/util/AbstractIndexedCyclingList.java | 94 ++++++ .../rei/impl/client/util/ClientTickCounter.java | 40 +++ .../impl/client/util/ConcatenatedListIterator.java | 189 +++++++++++ .../rei/impl/client/util/CyclingList.java | 353 +++++++++++++++++++++ .../client/util/OriginalRetainingCyclingList.java | 176 ++++++++++ .../rei/impl/common/entry/EntryIngredientImpl.java | 67 +++- 10 files changed, 1159 insertions(+), 49 deletions(-) create mode 100644 runtime/src/main/java/me/shedaniel/rei/impl/client/util/AbstractIndexedCyclingList.java create mode 100644 runtime/src/main/java/me/shedaniel/rei/impl/client/util/ClientTickCounter.java create mode 100644 runtime/src/main/java/me/shedaniel/rei/impl/client/util/ConcatenatedListIterator.java create mode 100644 runtime/src/main/java/me/shedaniel/rei/impl/client/util/CyclingList.java create mode 100644 runtime/src/main/java/me/shedaniel/rei/impl/client/util/OriginalRetainingCyclingList.java (limited to 'runtime/src/main/java') diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/screen/AbstractDisplayViewingScreen.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/screen/AbstractDisplayViewingScreen.java index b2d26599a..91e4bc3af 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/screen/AbstractDisplayViewingScreen.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/screen/AbstractDisplayViewingScreen.java @@ -30,6 +30,9 @@ import com.mojang.datafixers.util.Pair; import com.mojang.math.Matrix4f; import dev.architectury.fluid.FluidStack; import dev.architectury.utils.value.IntValue; +import it.unimi.dsi.fastutil.longs.LongOpenHashSet; +import it.unimi.dsi.fastutil.longs.LongSet; +import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; import me.shedaniel.math.Rectangle; import me.shedaniel.rei.api.client.REIRuntime; import me.shedaniel.rei.api.client.config.ConfigObject; @@ -45,7 +48,9 @@ import me.shedaniel.rei.api.client.registry.display.DisplayCategoryView; import me.shedaniel.rei.api.client.registry.entry.EntryRegistry; import me.shedaniel.rei.api.common.category.CategoryIdentifier; import me.shedaniel.rei.api.common.display.Display; +import me.shedaniel.rei.api.common.entry.EntryIngredient; import me.shedaniel.rei.api.common.entry.EntryStack; +import me.shedaniel.rei.api.common.entry.settings.EntryIngredientSetting; import me.shedaniel.rei.api.common.entry.type.EntryType; import me.shedaniel.rei.api.common.entry.type.VanillaEntryTypes; import me.shedaniel.rei.api.common.util.CollectionUtils; @@ -53,6 +58,8 @@ import me.shedaniel.rei.impl.client.REIRuntimeImpl; import me.shedaniel.rei.impl.client.gui.widget.EntryWidget; import me.shedaniel.rei.impl.client.gui.widget.TabContainerWidget; import me.shedaniel.rei.impl.client.gui.widget.entrylist.EntryListWidget; +import me.shedaniel.rei.impl.client.util.ClientTickCounter; +import me.shedaniel.rei.impl.client.util.CyclingList; import me.shedaniel.rei.impl.display.DisplaySpec; import net.minecraft.ChatFormatting; import net.minecraft.client.Minecraft; @@ -221,13 +228,30 @@ public abstract class AbstractDisplayViewingScreen extends Screen implements Dis private static void transformNotice(int marker, List setupDisplay, List> noticeStacks) { if (noticeStacks.isEmpty()) return; + Map, LongSet> noticeSet = new HashMap<>(); for (EntryWidget widget : Widgets.walk(setupDisplay, EntryWidget.class::isInstance)) { - if (widget.getNoticeMark() == marker && widget.getEntries().size() > 1) { + List> entries = widget.getEntries(); + if (widget.getNoticeMark() == marker && entries.size() > 1) { for (EntryStack noticeStack : noticeStacks) { - EntryStack stack = CollectionUtils.findFirstOrNullEqualsExact(widget.getEntries(), noticeStack); + EntryStack stack = CollectionUtils.findFirstOrNullEqualsExact(entries, noticeStack); if (stack != null) { widget.clearStacks(); widget.entry(stack); + if (entries instanceof EntryIngredient ingredient) noticeSet.computeIfAbsent(stack, $ -> new LongOpenHashSet()) + .add(hashFocusIngredient(ingredient)); + break; + } + } + } + } + for (EntryWidget widget : Widgets.walk(setupDisplay, EntryWidget.class::isInstance)) { + List> entries = widget.getEntries(); + if (widget.getNoticeMark() != marker && entries.size() > 1 && entries instanceof EntryIngredient ingredient) { + long hashFocusIngredient = hashFocusIngredient(ingredient); + for (Map.Entry, LongSet> entry : noticeSet.entrySet()) { + if (entry.getValue().contains(hashFocusIngredient)) { + widget.clearStacks(); + widget.entry(entry.getKey()); break; } } @@ -235,18 +259,31 @@ public abstract class AbstractDisplayViewingScreen extends Screen implements Dis } } + @SuppressWarnings("RedundantCast") protected void transformFiltering(List setupDisplay) { for (EntryWidget widget : Widgets.walk(setupDisplay, EntryWidget.class::isInstance)) { if (widget.getEntries().size() > 1) { Collection> refiltered = EntryRegistry.getInstance().refilterNew(false, widget.getEntries()); - if (!refiltered.isEmpty()) { + EntryIngredient asEntryIngredient = widget.getEntries() instanceof EntryIngredient ingredient ? ingredient : null; + if (!refiltered.isEmpty() && !widget.getEntries().equals(refiltered)) { widget.clearStacks(); - widget.entries(refiltered); + EntryIngredient newIngredient = EntryIngredient.of(refiltered); + if (asEntryIngredient != null && (Object) asEntryIngredient.getSetting(EntryIngredientSetting.FOCUS_UUID) instanceof UUID uuid) { + newIngredient.setting(EntryIngredientSetting.FOCUS_UUID, + new UUID(uuid.getMostSignificantBits() ^ refiltered.size(), uuid.getLeastSignificantBits() ^ refiltered.size())); + } + widget.entries(newIngredient); } } } } + protected static long hashFocusIngredient(EntryIngredient ingredient) { + UUID uuid = ingredient.getSetting(EntryIngredientSetting.FOCUS_UUID); + if (uuid == null) return System.identityHashCode(ingredient); + return uuid.hashCode() ^ ingredient.size(); + } + protected void setupTags(List widgets) { outer: for (EntryWidget widget : Widgets.walk(widgets, EntryWidget.class::isInstance)) { @@ -283,6 +320,32 @@ public abstract class AbstractDisplayViewingScreen extends Screen implements Dis } } + protected void unifyIngredients(List widgets) { + Map> slots = new TreeMap<>(Comparator.comparingLong(AbstractDisplayViewingScreen::hashFocusIngredient)); + for (EntryWidget slot : Widgets.walk(widgets, EntryWidget.class::isInstance)) { + CyclingList> entries = slot.getBackingCyclingEntries(); + if (entries.get() instanceof EntryIngredient ingredient) { + slots.computeIfAbsent(ingredient, key -> new ArrayList<>()).add(slot); + } + } + for (Map.Entry> entry : slots.entrySet()) { + List slotList = entry.getValue(); + if (slotList.size() > 1) { + List>> all = new ArrayList<>(); + Limiter>> limiter = new TickCountLimiter<>(all); + for (EntryWidget slot : slotList) { + CyclingList> limited; + CyclingList> backing = slot.getBackingCyclingEntries(); + if (backing instanceof CyclingList.Mutable> mutable) + limited = new LimitedCyclingList.Mutable<>(mutable, limiter); + else limited = new LimitedCyclingList<>(backing, limiter); + slot.entries(limited); + all.add(backing); + } + } + } + } + private static final int MAX_WIDTH = 200; private void addCyclingTooltip(EntryWidget widget) { @@ -443,4 +506,123 @@ public abstract class AbstractDisplayViewingScreen extends Screen implements Dis public boolean charTyped(char character, int modifiers) { return super.charTyped(character, modifiers) || (getOverlay().charTyped(character, modifiers) && handleFocuses()); } + + private interface Limiter { + boolean canExecute(T t); + + List getEntries(); + } + + private static class TickCountLimiter implements Limiter { + private int ticks = -1; + private final List list; + private final Set set = new ReferenceOpenHashSet<>(); + + public TickCountLimiter(List list) { + this.list = list; + } + + @Override + public boolean canExecute(T t) { + int currentTick = ClientTickCounter.getTicks(); + if (this.ticks != currentTick) { + this.ticks = currentTick; + this.set.clear(); + } + return this.set.add(t); + } + + @Override + public List getEntries() { + return list; + } + } + + private static class LimitedCyclingList implements CyclingList { + protected final CyclingList provider; + private final Limiter> limiter; + + public LimitedCyclingList(CyclingList provider, Limiter> limiter) { + this.provider = provider; + this.limiter = limiter; + } + + @Override + public T peek() { + return provider.peek(); + } + + @Override + public void resetToStart() { + provider.resetToStart(); + } + + @Override + public int size() { + return provider.size(); + } + + @Override + public int currentIndex() { + return provider.currentIndex(); + } + + @Override + public T previous() { + if (this.limiter.canExecute(provider)) { + for (CyclingList list : this.limiter.getEntries()) { + list.previous(); + } + } + + return provider.peek(); + } + + @Override + public int nextIndex() { + return provider.nextIndex(); + } + + @Override + public int previousIndex() { + return provider.previousIndex(); + } + + @Override + public T next() { + if (this.limiter.canExecute(provider)) { + for (CyclingList list : this.limiter.getEntries()) { + list.next(); + } + } + + return provider.peek(); + } + + @Override + public List get() { + return provider.get(); + } + + private static class Mutable extends LimitedCyclingList implements CyclingList.Mutable { + public Mutable(CyclingList.Mutable provider, Limiter> limiter) { + super(provider, limiter); + } + + @Override + public void add(T entry) { + ((CyclingList.Mutable) provider).add(entry); + } + + @Override + public void addAll(Collection entries) { + ((CyclingList.Mutable) provider).addAll(entries); + } + + @Override + public void clear() { + ((CyclingList.Mutable) provider).clear(); + } + } + } } diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/screen/CompositeDisplayViewingScreen.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/screen/CompositeDisplayViewingScreen.java index c0e9e2b01..f92275424 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/screen/CompositeDisplayViewingScreen.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/screen/CompositeDisplayViewingScreen.java @@ -148,6 +148,7 @@ public class CompositeDisplayViewingScreen extends AbstractDisplayViewingScreen transformFiltering(setupDisplay); transformIngredientNotice(setupDisplay, ingredientStackToNotice); transformResultNotice(setupDisplay, resultStackToNotice); + unifyIngredients(setupDisplay); for (EntryWidget widget : Widgets.walk(widgets, EntryWidget.class::isInstance)) { widget.removeTagMatch = true; } diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/screen/DefaultDisplayViewingScreen.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/screen/DefaultDisplayViewingScreen.java index c4535757a..b47c9d825 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/screen/DefaultDisplayViewingScreen.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/screen/DefaultDisplayViewingScreen.java @@ -255,6 +255,7 @@ public class DefaultDisplayViewingScreen extends AbstractDisplayViewingScreen { transformFiltering(setupDisplay); transformIngredientNotice(setupDisplay, ingredientStackToNotice); transformResultNotice(setupDisplay, resultStackToNotice); + unifyIngredients(setupDisplay); for (EntryWidget widget : Widgets.walk(widgets, EntryWidget.class::isInstance)) { widget.removeTagMatch = true; } @@ -446,6 +447,7 @@ public class DefaultDisplayViewingScreen extends AbstractDisplayViewingScreen { transformFiltering(setupDisplay); transformIngredientNotice(setupDisplay, ingredientStackToNotice); transformResultNotice(setupDisplay, resultStackToNotice); + unifyIngredients(setupDisplay); for (EntryWidget widget : Widgets.walk(widgets(), EntryWidget.class::isInstance)) { widget.removeTagMatch = true; } diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/EntryWidget.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/EntryWidget.java index f5369814c..be2de31eb 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/EntryWidget.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/EntryWidget.java @@ -44,6 +44,7 @@ import me.shedaniel.rei.api.client.gui.screen.DisplayScreen; import me.shedaniel.rei.api.client.gui.widgets.Slot; import me.shedaniel.rei.api.client.gui.widgets.Tooltip; import me.shedaniel.rei.api.client.gui.widgets.TooltipContext; +import me.shedaniel.rei.api.client.gui.widgets.Widgets; import me.shedaniel.rei.api.client.overlay.ScreenOverlay; import me.shedaniel.rei.api.client.registry.category.CategoryRegistry; import me.shedaniel.rei.api.client.registry.display.DisplayRegistry; @@ -63,6 +64,8 @@ import me.shedaniel.rei.impl.client.gui.dragging.CurrentDraggingStack; import me.shedaniel.rei.impl.client.gui.widget.favorites.FavoritesListWidget; import me.shedaniel.rei.impl.client.registry.display.DisplayRegistryImpl; import me.shedaniel.rei.impl.client.registry.display.DisplaysHolder; +import me.shedaniel.rei.impl.client.util.CyclingList; +import me.shedaniel.rei.impl.client.util.OriginalRetainingCyclingList; import me.shedaniel.rei.impl.client.view.ViewsImpl; import net.minecraft.ChatFormatting; import net.minecraft.CrashReport; @@ -77,7 +80,6 @@ import net.minecraft.client.resources.sounds.SimpleSoundInstance; import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceLocation; import net.minecraft.sounds.SoundEvents; -import net.minecraft.util.Mth; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Nullable; @@ -87,10 +89,8 @@ import java.util.function.UnaryOperator; import java.util.stream.Collectors; import java.util.stream.Stream; +@SuppressWarnings("UnstableApiUsage") public class EntryWidget extends Slot implements DraggableStackProviderWidget { - @ApiStatus.Internal - public static long stackDisplayOffset = 0; - @ApiStatus.Internal private byte noticeMark = 0; protected boolean highlight = true; @@ -99,8 +99,9 @@ public class EntryWidget extends Slot implements DraggableStackProviderWidget { protected boolean interactable = true; protected boolean interactableFavorites = true; protected boolean wasClicked = false; - private Rectangle bounds; - private List> entryStacks; + private final Rectangle bounds; + private final OriginalRetainingCyclingList> stacks = new OriginalRetainingCyclingList<>(EntryStack::empty); + private long lastCycleTime = -1; @Nullable private Set> tooltipProcessors; public ResourceLocation tagMatch; @@ -117,7 +118,6 @@ public class EntryWidget extends Slot implements DraggableStackProviderWidget { public EntryWidget(Rectangle bounds) { this.bounds = bounds; - this.entryStacks = Collections.emptyList(); } @Override @@ -244,60 +244,64 @@ public class EntryWidget extends Slot implements DraggableStackProviderWidget { return background; } - public EntryWidget clearStacks() { - entryStacks = Collections.emptyList(); + @Override + public Slot clearEntries() { + this.getCyclingEntries().clear(); + if (removeTagMatch) tagMatch = null; return this; } - @Override - public Slot clearEntries() { - return clearStacks(); + public EntryWidget clearStacks() { + return (EntryWidget) this.clearEntries(); } @Override public EntryWidget entry(EntryStack stack) { Objects.requireNonNull(stack); - if (entryStacks.isEmpty()) { - entryStacks = Collections.singletonList(stack); - } else { - if (!(entryStacks instanceof ArrayList)) { - entryStacks = new ArrayList<>(entryStacks); - } - entryStacks.add(stack); - } + this.getCyclingEntries().add(stack); if (removeTagMatch) tagMatch = null; return this; } @Override public EntryWidget entries(Collection> stacks) { - if (!stacks.isEmpty()) { - if (!(entryStacks instanceof ArrayList)) { - entryStacks = new ArrayList<>(entryStacks); - } - entryStacks.addAll(stacks); - if (removeTagMatch) tagMatch = null; - } + Objects.requireNonNull(stacks); + this.getCyclingEntries().addAll(stacks); + if (removeTagMatch) tagMatch = null; return this; } - @Override - public EntryStack getCurrentEntry() { - int size = entryStacks.size(); - if (size == 0) - return EntryStack.empty(); - if (size == 1) - return entryStacks.get(0); - return entryStacks.get(Mth.floor(((System.currentTimeMillis() + stackDisplayOffset) / getCyclingInterval() % (double) size))); + public Slot entries(CyclingList> stacks) { + this.getCyclingEntries().setBacking(stacks); + if (removeTagMatch) tagMatch = null; + return this; } - protected long getCyclingInterval() { - return 1000; + public CyclingList> getBackingCyclingEntries() { + return this.stacks.getBacking(); + } + + public OriginalRetainingCyclingList> getCyclingEntries() { + return this.stacks; + } + + @Override + public EntryStack getCurrentEntry() { + if (this.lastCycleTime == -1) this.lastCycleTime = System.currentTimeMillis(); + if (System.currentTimeMillis() > this.lastCycleTime + getCyclingInterval()) { + this.lastCycleTime = System.currentTimeMillis(); + this.getCyclingEntries().next(); + } + return this.getCyclingEntries().peek(); } @Override public List> getEntries() { - return entryStacks; + return getCyclingEntries().get(); + } + + protected long getCyclingInterval() { + return 1000; } @Override @@ -562,12 +566,18 @@ public class EntryWidget extends Slot implements DraggableStackProviderWidget { @Override public boolean mouseScrolled(double mouseX, double mouseY, double amount) { - if (REIRuntimeImpl.isWithinRecipeViewingScreen && entryStacks.size() > 1 && containsMouse(mouseX, mouseY)) { + if (REIRuntimeImpl.isWithinRecipeViewingScreen && this.getCyclingEntries().get().size() > 1 && containsMouse(mouseX, mouseY)) { if (amount < 0) { - EntryWidget.stackDisplayOffset = ((System.currentTimeMillis() + stackDisplayOffset) / 1000 - 1) * 1000; + for (EntryWidget slot : Widgets.walk(minecraft.screen.children(), EntryWidget.class::isInstance)) { + slot.getCyclingEntries().previous(); + slot.lastCycleTime = System.currentTimeMillis(); + } return true; } else if (amount > 0) { - EntryWidget.stackDisplayOffset = ((System.currentTimeMillis() + stackDisplayOffset) / 1000 + 1) * 1000; + for (EntryWidget slot : Widgets.walk(minecraft.screen.children(), EntryWidget.class::isInstance)) { + slot.getCyclingEntries().next(); + slot.lastCycleTime = System.currentTimeMillis(); + } return true; } } @@ -708,7 +718,7 @@ public class EntryWidget extends Slot implements DraggableStackProviderWidget { public DraggableStack getHoveredStack(DraggingContext context, double mouseX, double mouseY) { if (!getCurrentEntry().isEmpty() && containsMouse(mouseX, mouseY)) { return new DraggableStack() { - EntryStack stack = getCurrentEntry().copy() + final EntryStack stack = getCurrentEntry().copy() .removeSetting(EntryStack.Settings.RENDERER) .removeSetting(EntryStack.Settings.FLUID_RENDER_RATIO); @@ -750,7 +760,7 @@ public class EntryWidget extends Slot implements DraggableStackProviderWidget { category.setDetail("Highlight enabled", () -> String.valueOf(isHighlightEnabled())); category.setDetail("Tooltip enabled", () -> String.valueOf(isTooltipsEnabled())); category.setDetail("Background enabled", () -> String.valueOf(isBackgroundEnabled())); - category.setDetail("Entries count", () -> String.valueOf(entryStacks.size())); + category.setDetail("Entries count", () -> String.valueOf(getEntries().size())); EntryStack currentEntry = getCurrentEntry(); CrashReportCategory entryCategory = report.addCategory("Current Rendering Entry"); diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/util/AbstractIndexedCyclingList.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/util/AbstractIndexedCyclingList.java new file mode 100644 index 000000000..199afbb29 --- /dev/null +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/util/AbstractIndexedCyclingList.java @@ -0,0 +1,94 @@ +/* + * This file is licensed under the MIT License, part of Roughly Enough Items. + * Copyright (c) 2018, 2019, 2020, 2021, 2022 shedaniel + * + * 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: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.shedaniel.rei.impl.client.util; + +import com.google.common.annotations.GwtCompatible; + +@GwtCompatible +abstract class AbstractIndexedCyclingList implements CyclingList { + private int position = 0; + + protected abstract T get(int index); + + protected abstract T empty(); + + @Override + public T peek() { + int size = size(); + + if (size == 0) { + return empty(); + } else { + return get(Math.floorMod(position, size)); + } + } + + @Override + public T next() { + int size = size(); + + if (size == 0) { + return empty(); + } else { + int tmp = position; + position = Math.floorMod(++tmp, size); + return get(Math.floorMod(tmp, size)); + } + } + + @Override + public int currentIndex() { + return position; + } + + @Override + public int nextIndex() { + return Math.floorMod(position + 1, size()); + } + + @Override + public T previous() { + int size = size(); + + if (size == 0) { + return empty(); + } else { + position = Math.floorMod(--position, size); + return get(position); + } + } + + @Override + public int previousIndex() { + return Math.floorMod(position - 1, size()); + } + + @Override + public void resetToStart() { + position = 0; + } + + public static abstract class Mutable extends AbstractIndexedCyclingList implements CyclingList.Mutable { + } +} diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/util/ClientTickCounter.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/util/ClientTickCounter.java new file mode 100644 index 000000000..03e2d9de8 --- /dev/null +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/util/ClientTickCounter.java @@ -0,0 +1,40 @@ +/* + * This file is licensed under the MIT License, part of Roughly Enough Items. + * Copyright (c) 2018, 2019, 2020, 2021, 2022 shedaniel + * + * 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: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.shedaniel.rei.impl.client.util; + +import dev.architectury.event.events.client.ClientTickEvent; + +public class ClientTickCounter { + private static int ticks = 0; + + static { + ClientTickEvent.CLIENT_POST.register(tick -> { + ticks++; + }); + } + + public static int getTicks() { + return ticks; + } +} diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/util/ConcatenatedListIterator.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/util/ConcatenatedListIterator.java new file mode 100644 index 000000000..eedd2b0cf --- /dev/null +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/util/ConcatenatedListIterator.java @@ -0,0 +1,189 @@ +/* + * This file is licensed under the MIT License, part of Roughly Enough Items. + * Copyright (c) 2018, 2019, 2020, 2021, 2022 shedaniel + * + * 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: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.shedaniel.rei.impl.client.util; + +import me.shedaniel.rei.api.common.util.CollectionUtils; +import org.spongepowered.include.com.google.common.collect.Iterators; + +import java.util.List; + +public abstract class ConcatenatedListIterator implements CyclingList { + private static final int HEAD_FIRST = -1, HEAD_LAST = -2, TAIL_FIRST = -3, TAIL_LAST = -4; + private final List listView; + private final CyclingList head, tail; + private int position = HEAD_FIRST; + + public ConcatenatedListIterator(CyclingList head, CyclingList tail) { + this.listView = CollectionUtils.concatUnmodifiable(() -> Iterators.forArray(head.get(), tail.get())); + this.head = head; + this.tail = tail; + this.head.resetToStart(); + this.tail.resetToStart(); + } + + protected abstract T empty(); + + @Override + public T peek() { + int p = currentIndex(); + int neededHeadPos = Math.min(p, head.size() - 1); + int neededTailPos = Math.max(p - head.size(), 0); + while (head.currentIndex() != neededHeadPos) { + head.next(); + } + while (tail.currentIndex() != neededTailPos) { + tail.next(); + } + return p < head.size() ? head.peek() : tail.peek(); + } + + @Override + public T next() { + position = nextIndex(); + int neededHeadPos = Math.min(position, head.size() - 1); + int neededTailPos = Math.max(position - head.size(), 0); + while (head.currentIndex() != neededHeadPos) { + head.next(); + } + while (tail.currentIndex() != neededTailPos) { + tail.next(); + } + T t = position < head.size() ? head.peek() : tail.peek(); + position = normalizeIndex(position); + return t; + } + + @Override + public T previous() { + position = previousIndex(); + int neededHeadPos = Math.min(position, head.size() - 1); + int neededTailPos = Math.max(position - head.size(), 0); + while (head.currentIndex() != neededHeadPos) { + head.previous(); + } + while (tail.currentIndex() != neededTailPos) { + tail.previous(); + } + T t = position < head.size() ? head.peek() : tail.peek(); + position = normalizeIndex(position); + return t; + } + + private int normalizeIndex(int index) { + if (index == 0) return HEAD_FIRST; + int hSize = head.size(); + if (index == hSize - 1) return HEAD_LAST; + if (index == hSize) return TAIL_FIRST; + if (index == hSize + tail.size() - 1) return TAIL_LAST; + return index; + } + + @Override + public int currentIndex() { + int size = size(); + int tmp = switch (position) { + case HEAD_FIRST -> 0; + case HEAD_LAST -> head.size() - 1; + case TAIL_FIRST -> tail.size() > 0 ? head.size() : 0; + case TAIL_LAST -> size - 1; + default -> position; + }; + return Math.floorMod(tmp, size); + } + + @Override + public int nextIndex() { + int size = size(); + int tmp = switch (position) { + case HEAD_FIRST -> 1; + case HEAD_LAST -> { + if (tail.size() > 0) { + yield head.size(); + } else if (head.size() > 0) { + yield 0; + } else { + yield 1; + } + } + case TAIL_FIRST -> { + if (tail.size() > 0) { + yield head.size() + 1; + } else if (head.size() > 0) { + yield 0; + } else { + yield 1; + } + } + case TAIL_LAST -> size > 0 ? 0 : 1; + default -> position + 1; + }; + return Math.floorMod(tmp, size); + } + + @Override + public int previousIndex() { + int size = size(); + int tmp = switch (position) { + case HEAD_FIRST -> size - 1; + case HEAD_LAST -> { + if (head.size() > 0) { + yield head.size() - 2; + } else if (tail.size() > 0) { + yield tail.size() - 1; + } else { + yield -1; + } + } + case TAIL_FIRST -> head.size() - 1; + case TAIL_LAST -> { + if (size > 0) { + yield size - 2; + } else { + yield -1; + } + } + default -> position - 1; + }; + return Math.floorMod(tmp, size); + } + + @Override + public List get() { + if (tail.size() == 0) return head.get(); + if (head.size() == 0) return tail.get(); + return listView; + } + + @Override + public int size() { + return head.size() + tail.size(); + } + + @Override + public void resetToStart() { + head.resetToStart(); + tail.resetToStart(); + position = 0; + } +} \ No newline at end of file diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/util/CyclingList.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/util/CyclingList.java new file mode 100644 index 000000000..2fb060d7a --- /dev/null +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/util/CyclingList.java @@ -0,0 +1,353 @@ +/* + * This file is licensed under the MIT License, part of Roughly Enough Items. + * Copyright (c) 2018, 2019, 2020, 2021, 2022 shedaniel + * + * 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: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.shedaniel.rei.impl.client.util; + +import org.jetbrains.annotations.ApiStatus; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.function.Supplier; + +@ApiStatus.Experimental +public interface CyclingList extends Supplier> { + default T peek() { + next(); + return previous(); + } + + default boolean hasNext() { + int nextIndex = nextIndex(); + return currentIndex() < nextIndex && nextIndex < size(); + } + + default boolean hasPrevious() { + int previousIndex = previousIndex(); + return previousIndex >= 0 && currentIndex() > previousIndex; + } + + T next(); + + T previous(); + + int nextIndex(); + + int previousIndex(); + + void resetToStart(); + + int size(); + + int currentIndex(); + + @ApiStatus.Experimental + interface Mutable extends CyclingList { + void add(T entry); + + void addAll(Collection entries); + + void clear(); + } + + static CyclingList of(List list, Supplier empty) { + return new AbstractIndexedCyclingList<>() { + @Override + public List get() { + return list; + } + + @Override + protected T get(int index) { + return list.get(index); + } + + @Override + protected T empty() { + return empty.get(); + } + + @Override + public int size() { + return list.size(); + } + }; + } + + static CyclingList.Mutable ofMutable(List list, Supplier empty) { + return new AbstractIndexedCyclingList.Mutable<>() { + @Override + public List get() { + return list; + } + + @Override + protected T get(int index) { + return list.get(index); + } + + @Override + protected T empty() { + return empty.get(); + } + + @Override + public int size() { + return list.size(); + } + + @Override + public void add(T entry) { + list.add(entry); + } + + @Override + public void addAll(Collection entries) { + list.addAll(entries); + } + + @Override + public void clear() { + list.clear(); + } + }; + } + + static CyclingList of(Supplier empty) { + return new CyclingList<>() { + @Override + public List get() { + return List.of(); + } + + @Override + public T peek() { + return empty.get(); + } + + @Override + public void resetToStart() { + } + + @Override + public int size() { + return 0; + } + + @Override + public int currentIndex() { + return 0; + } + + @Override + public T next() { + return empty.get(); + } + + @Override + public T previous() { + return empty.get(); + } + + @Override + public int nextIndex() { + return 1; + } + + @Override + public int previousIndex() { + return -1; + } + }; + } + + static CyclingList.Mutable ofMutable(Supplier empty) { + return new Mutable<>() { + private List list; + private CyclingList provider; + + + @Override + public List get() { + return this.list == null ? List.of() : this.list; + } + + @Override + public T peek() { + if (this.provider == null) return empty.get(); + return this.provider.peek(); + } + + @Override + public T previous() { + if (this.provider == null) return empty.get(); + return this.provider.previous(); + } + + @Override + public int nextIndex() { + return this.provider == null ? 1 : this.provider.nextIndex(); + } + + @Override + public int previousIndex() { + return this.provider == null ? -1 : this.provider.previousIndex(); + } + + @Override + public T next() { + if (this.provider == null) return empty.get(); + return this.provider.next(); + } + + @Override + public void add(T entry) { + if (this.list == null || this.list.isEmpty()) { + this.list = Collections.singletonList(entry); + this.provider = CyclingList.ofMutable(this.list, empty); + } else { + if (!(this.list instanceof ArrayList)) { + this.list = new ArrayList<>(this.list); + this.provider = CyclingList.ofMutable(this.list, empty); + } + this.list.add(entry); + } + } + + @Override + public void resetToStart() { + if (this.provider != null) { + this.provider.resetToStart(); + } + } + + @Override + public int size() { + return this.list == null ? 0 : this.list.size(); + } + + @Override + public int currentIndex() { + return this.provider == null ? 0 : this.provider.currentIndex(); + } + + @Override + public void addAll(Collection entries) { + if (this.list == null) { + this.list = new ArrayList<>(entries); + this.provider = CyclingList.ofMutable(this.list, empty); + } else { + if (!(this.list instanceof ArrayList)) { + this.list = new ArrayList<>(this.list); + this.provider = CyclingList.ofMutable(this.list, empty); + } + this.list.addAll(entries); + } + } + + @Override + public void clear() { + this.list = null; + this.provider = null; + } + }; + } + + static CyclingList.Mutable ofMutable(CyclingList provider, Supplier empty) { + return new Mutable<>() { + private final CyclingList.Mutable mutable = CyclingList.ofMutable(empty); + private CyclingList concat = CyclingList.concat(List.of(provider, mutable), empty); + + @Override + public List get() { + return this.concat.get(); + } + + @Override + public T peek() { + return this.concat.peek(); + } + + @Override + public T previous() { + return this.concat.previous(); + } + + @Override + public int nextIndex() { + return this.concat.nextIndex(); + } + + @Override + public int previousIndex() { + return this.concat.previousIndex(); + } + + @Override + public T next() { + return this.concat.next(); + } + + @Override + public void add(T entry) { + this.mutable.add(entry); + } + + @Override + public void resetToStart() { + this.concat.resetToStart(); + } + + @Override + public int size() { + return this.concat.size(); + } + + @Override + public int currentIndex() { + return this.concat.currentIndex(); + } + + @Override + public void addAll(Collection entries) { + this.mutable.addAll(entries); + } + + @Override + public void clear() { + this.concat = this.mutable; + this.mutable.clear(); + } + }; + } + + static CyclingList concat(Collection> providers, Supplier empty) { + return providers.stream().reduce((a, b) -> new ConcatenatedListIterator(a, b) { + @Override + protected T empty() { + return empty.get(); + } + }).orElse(CyclingList.of(empty)); + } +} diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/util/OriginalRetainingCyclingList.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/util/OriginalRetainingCyclingList.java new file mode 100644 index 000000000..f66e65723 --- /dev/null +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/util/OriginalRetainingCyclingList.java @@ -0,0 +1,176 @@ +/* + * This file is licensed under the MIT License, part of Roughly Enough Items. + * Copyright (c) 2018, 2019, 2020, 2021, 2022 shedaniel + * + * 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: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.shedaniel.rei.impl.client.util; + +import com.google.common.collect.Iterables; +import org.jetbrains.annotations.Nullable; + +import java.util.AbstractList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.function.Supplier; + +public class OriginalRetainingCyclingList implements CyclingList.Mutable { + private final Supplier empty; + @Nullable + private CyclingList backing = null; + + public OriginalRetainingCyclingList(Supplier empty) { + this.empty = empty; + } + + @Override + public List get() { + if (this.backing == null) return List.of(); + return this.backing.get(); + } + + @Override + public T peek() { + if (this.backing == null) return empty.get(); + return this.backing.peek(); + } + + @Override + public T previous() { + if (this.backing == null) return empty.get(); + return this.backing.previous(); + } + + @Override + public int nextIndex() { + return this.backing == null ? 0 : this.backing.nextIndex(); + } + + @Override + public int previousIndex() { + return this.backing == null ? -1 : this.backing.previousIndex(); + } + + @Override + public T next() { + if (this.backing == null) return empty.get(); + return this.backing.next(); + } + + @Override + public void add(T entry) { + if (this.backing instanceof CyclingList.Mutable mutable) { + mutable.add(entry); + } else if (this.backing == null) { + this.backing = CyclingList.of(List.of(entry), this.empty); + } else { + CyclingList.Mutable mutable = CyclingList.ofMutable(this.backing, this.empty); + mutable.add(entry); + this.backing = mutable; + } + } + + @Override + public void resetToStart() { + if (this.backing != null) this.backing.resetToStart(); + } + + @Override + public int size() { + return this.backing == null ? 0 : this.backing.size(); + } + + @Override + public int currentIndex() { + return this.backing == null ? 0 : this.backing.currentIndex(); + } + + @Override + public void addAll(Collection entries) { + if (!entries.isEmpty()) { + if (this.backing instanceof CyclingList.Mutable mutable) { + mutable.addAll(entries); + } else if (this.backing == null) { + List list; + if (entries instanceof List stacksAsList) list = (List) entries; + else list = getListFromCollection(entries); + this.backing = CyclingList.of(list, this.empty); + } else { + CyclingList.Mutable mutable = CyclingList.ofMutable(this.backing, this.empty); + mutable.addAll(entries); + this.backing = mutable; + } + } + } + + @Override + public void clear() { + if (this.backing instanceof CyclingList.Mutable mutable) { + mutable.clear(); + } else { + this.backing = null; + } + } + + public void setBacking(@Nullable CyclingList backing) { + this.backing = backing; + } + + private static AbstractList getListFromCollection(Collection entries) { + return new AbstractList<>() { + @Override + public T get(int index) { + return Iterables.get(entries, index); + } + + @Override + public int size() { + return entries.size(); + } + + @Override + public Iterator iterator() { + return (Iterator) entries.iterator(); + } + + @Override + public boolean add(T element) { + return ((Collection) entries).add(element); + } + + @Override + public void add(int index, T element) { + add(element); + } + + @Override + public T remove(int index) { + T stack = get(index); + return stack == null && entries.remove(stack) ? stack : null; + } + }; + } + + public CyclingList getBacking() { + if (this.backing == null) return CyclingList.of(this.empty); + return this.backing; + } +} diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/common/entry/EntryIngredientImpl.java b/runtime/src/main/java/me/shedaniel/rei/impl/common/entry/EntryIngredientImpl.java index cdf3d911b..56576d276 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/common/entry/EntryIngredientImpl.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/common/entry/EntryIngredientImpl.java @@ -24,10 +24,13 @@ package me.shedaniel.rei.impl.common.entry; import com.google.common.collect.Iterators; +import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap; import me.shedaniel.rei.api.common.entry.EntryIngredient; import me.shedaniel.rei.api.common.entry.EntryStack; +import me.shedaniel.rei.api.common.entry.settings.EntryIngredientSetting; import me.shedaniel.rei.impl.Internals; import net.minecraft.nbt.ListTag; +import org.jetbrains.annotations.Nullable; import java.util.*; import java.util.function.Consumer; @@ -240,9 +243,37 @@ public enum EntryIngredientImpl implements Internals.EntryIngredientProvider { public EntryIngredient map(UnaryOperator> transformer) { return this; } + + @Nullable + @Override + public T getSetting(EntryIngredientSetting setting) { + return null; + } + + @Override + public EntryIngredient setting(EntryIngredientSetting setting, T value) { + return this; + } } - private static class SingletonEntryIngredient extends AbstractList> implements EntryIngredient, RandomAccess { + private static abstract class AbstractEntryIngredient extends AbstractList> implements EntryIngredient { + private Map, Object> settings = null; + + @Override + @Nullable + public T getSetting(EntryIngredientSetting setting) { + return SettingsHandler.get(this.settings, setting); + } + + @Override + public EntryIngredient setting(EntryIngredientSetting setting, T value) { + if (value == null) this.settings = SettingsHandler.remove(this.settings, setting); + else this.settings = SettingsHandler.set(this.settings, setting, value); + return this; + } + } + + private static class SingletonEntryIngredient extends AbstractEntryIngredient implements EntryIngredient, RandomAccess { private EntryStack stack; public SingletonEntryIngredient(EntryStack stack) { @@ -366,7 +397,7 @@ public enum EntryIngredientImpl implements Internals.EntryIngredientProvider { } } - private static class ArrayIngredient extends AbstractList> implements EntryIngredient, RandomAccess { + private static class ArrayIngredient extends AbstractEntryIngredient implements EntryIngredient, RandomAccess { private static final long serialVersionUID = -2764017481108945198L; private final EntryStack[] array; @@ -473,4 +504,36 @@ public enum EntryIngredientImpl implements Internals.EntryIngredientProvider { return new ArrayIngredient(out); } } + + private static class SettingsHandler { + private static Map, Object> set(Map, Object> map, EntryIngredientSetting setting, Object value) { + if (map == null) { + map = new Reference2ObjectOpenHashMap<>(); + } + + map.put(setting, value); + return map; + } + + private static T get(Map, Object> map, EntryIngredientSetting setting) { + if (map == null) { + return null; + } + + Object o = map.get(setting); + return o == null ? null : (T) o; + } + + private static Map, Object> remove(Map, Object> map, EntryIngredientSetting setting) { + if (map == null) { + return null; + } + + map.remove(setting); + if (map.isEmpty()) { + return null; + } + return map; + } + } } -- cgit