diff options
| author | shedaniel <daniel@shedaniel.me> | 2022-12-25 00:10:10 +0800 |
|---|---|---|
| committer | shedaniel <daniel@shedaniel.me> | 2024-04-16 00:38:17 +0900 |
| commit | c93345b568ba0c6231986cac30485e689212c731 (patch) | |
| tree | f99a81395cf624315f4de3e56296cfa811668902 /runtime/src | |
| parent | ffe21652b40a93a00f33a27a5ecf41479b48bcd9 (diff) | |
| download | RoughlyEnoughItems-c93345b568ba0c6231986cac30485e689212c731.tar.gz RoughlyEnoughItems-c93345b568ba0c6231986cac30485e689212c731.tar.bz2 RoughlyEnoughItems-c93345b568ba0c6231986cac30485e689212c731.zip | |
Implement Unified Ingredients
Diffstat (limited to 'runtime/src')
11 files changed, 1497 insertions, 49 deletions
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<? extends GuiEventListener> setupDisplay, List<EntryStack<?>> noticeStacks) { if (noticeStacks.isEmpty()) return; + Map<EntryStack<?>, LongSet> noticeSet = new HashMap<>(); for (EntryWidget widget : Widgets.<EntryWidget>walk(setupDisplay, EntryWidget.class::isInstance)) { - if (widget.getNoticeMark() == marker && widget.getEntries().size() > 1) { + List<EntryStack<?>> 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.<EntryWidget>walk(setupDisplay, EntryWidget.class::isInstance)) { + List<EntryStack<?>> entries = widget.getEntries(); + if (widget.getNoticeMark() != marker && entries.size() > 1 && entries instanceof EntryIngredient ingredient) { + long hashFocusIngredient = hashFocusIngredient(ingredient); + for (Map.Entry<EntryStack<?>, 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<? extends GuiEventListener> setupDisplay) { for (EntryWidget widget : Widgets.<EntryWidget>walk(setupDisplay, EntryWidget.class::isInstance)) { if (widget.getEntries().size() > 1) { Collection<EntryStack<?>> 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<Widget> widgets) { outer: for (EntryWidget widget : Widgets.<EntryWidget>walk(widgets, EntryWidget.class::isInstance)) { @@ -283,6 +320,32 @@ public abstract class AbstractDisplayViewingScreen extends Screen implements Dis } } + protected void unifyIngredients(List<Widget> widgets) { + Map<EntryIngredient, List<EntryWidget>> slots = new TreeMap<>(Comparator.comparingLong(AbstractDisplayViewingScreen::hashFocusIngredient)); + for (EntryWidget slot : Widgets.<EntryWidget>walk(widgets, EntryWidget.class::isInstance)) { + CyclingList<EntryStack<?>> entries = slot.getBackingCyclingEntries(); + if (entries.get() instanceof EntryIngredient ingredient) { + slots.computeIfAbsent(ingredient, key -> new ArrayList<>()).add(slot); + } + } + for (Map.Entry<EntryIngredient, List<EntryWidget>> entry : slots.entrySet()) { + List<EntryWidget> slotList = entry.getValue(); + if (slotList.size() > 1) { + List<CyclingList<EntryStack<?>>> all = new ArrayList<>(); + Limiter<CyclingList<EntryStack<?>>> limiter = new TickCountLimiter<>(all); + for (EntryWidget slot : slotList) { + CyclingList<EntryStack<?>> limited; + CyclingList<EntryStack<?>> backing = slot.getBackingCyclingEntries(); + if (backing instanceof CyclingList.Mutable<EntryStack<?>> 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<T> { + boolean canExecute(T t); + + List<T> getEntries(); + } + + private static class TickCountLimiter<T> implements Limiter<T> { + private int ticks = -1; + private final List<T> list; + private final Set<T> set = new ReferenceOpenHashSet<>(); + + public TickCountLimiter(List<T> 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<T> getEntries() { + return list; + } + } + + private static class LimitedCyclingList<T> implements CyclingList<T> { + protected final CyclingList<T> provider; + private final Limiter<CyclingList<T>> limiter; + + public LimitedCyclingList(CyclingList<T> provider, Limiter<CyclingList<T>> 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<T> 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<T> list : this.limiter.getEntries()) { + list.next(); + } + } + + return provider.peek(); + } + + @Override + public List<T> get() { + return provider.get(); + } + + private static class Mutable<T> extends LimitedCyclingList<T> implements CyclingList.Mutable<T> { + public Mutable(CyclingList.Mutable<T> provider, Limiter<CyclingList<T>> limiter) { + super(provider, limiter); + } + + @Override + public void add(T entry) { + ((CyclingList.Mutable<T>) provider).add(entry); + } + + @Override + public void addAll(Collection<? extends T> entries) { + ((CyclingList.Mutable<T>) provider).addAll(entries); + } + + @Override + public void clear() { + ((CyclingList.Mutable<T>) 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.<EntryWidget>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.<EntryWidget>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.<EntryWidget>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,11 +89,9 @@ 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; protected boolean tooltips = 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<EntryStack<?>> entryStacks; + private final Rectangle bounds; + private final OriginalRetainingCyclingList<EntryStack<?>> stacks = new OriginalRetainingCyclingList<>(EntryStack::empty); + private long lastCycleTime = -1; @Nullable private Set<UnaryOperator<Tooltip>> 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<? extends EntryStack<?>> 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<EntryStack<?>> stacks) { + this.getCyclingEntries().setBacking(stacks); + if (removeTagMatch) tagMatch = null; + return this; } - protected long getCyclingInterval() { - return 1000; + public CyclingList<EntryStack<?>> getBackingCyclingEntries() { + return this.stacks.getBacking(); + } + + public OriginalRetainingCyclingList<EntryStack<?>> 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<EntryStack<?>> 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.<EntryWidget>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.<EntryWidget>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<Screen> 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<T> implements CyclingList<T> { + 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<T> extends AbstractIndexedCyclingList<T> implements CyclingList.Mutable<T> { + } +} 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<T> implements CyclingList<T> { + private static final int HEAD_FIRST = -1, HEAD_LAST = -2, TAIL_FIRST = -3, TAIL_LAST = -4; + private final List<T> listView; + private final CyclingList<T> head, tail; + private int position = HEAD_FIRST; + + public ConcatenatedListIterator(CyclingList<T> head, CyclingList<T> 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 = |
