Przeglądaj źródła

Get rid of mixins, turn EntryStack methods turn an ImmutableList instead of ArrayList

Signed-off-by: shedaniel <daniel@shedaniel.me>
shedaniel 4 lat temu
rodzic
commit
d643b58106
27 zmienionych plików z 486 dodań i 331 usunięć
  1. 5 1
      RoughlyEnoughItems-api/src/main/java/me/shedaniel/rei/api/BuiltinPlugin.java
  2. 2 0
      RoughlyEnoughItems-api/src/main/java/me/shedaniel/rei/api/ConfigObject.java
  3. 57 8
      RoughlyEnoughItems-api/src/main/java/me/shedaniel/rei/api/EntryStack.java
  4. 60 43
      RoughlyEnoughItems-default-plugin/src/main/java/me/shedaniel/rei/plugin/DefaultPlugin.java
  5. 11 5
      RoughlyEnoughItems-default-plugin/src/main/java/me/shedaniel/rei/plugin/brewing/DefaultBrewingDisplay.java
  6. 2 2
      RoughlyEnoughItems-default-plugin/src/main/java/me/shedaniel/rei/plugin/brewing/RegisteredBrewingRecipe.java
  7. 8 6
      RoughlyEnoughItems-default-plugin/src/main/java/me/shedaniel/rei/plugin/composting/DefaultCompostingCategory.java
  8. 21 13
      RoughlyEnoughItems-default-plugin/src/main/java/me/shedaniel/rei/plugin/composting/DefaultCompostingDisplay.java
  9. 10 9
      RoughlyEnoughItems-default-plugin/src/main/java/me/shedaniel/rei/plugin/crafting/DefaultCustomDisplay.java
  10. 0 88
      RoughlyEnoughItems-default-plugin/src/main/java/me/shedaniel/rei/plugin/mixin/MixinPotionBrewing.java
  11. 0 3
      RoughlyEnoughItems-default-plugin/src/main/resources/fabric.mod.json
  12. 0 13
      RoughlyEnoughItems-default-plugin/src/main/resources/mixin.roughlyenoughitems-default-plugin.json
  13. 7 0
      RoughlyEnoughItems-default-plugin/src/main/resources/roughlyenoughitems-default-plugin.accessWidener
  14. 5 7
      RoughlyEnoughItems-runtime/src/main/java/me/shedaniel/rei/gui/widget/EntryListWidget.java
  15. 6 6
      RoughlyEnoughItems-runtime/src/main/java/me/shedaniel/rei/gui/widget/FavoritesListWidget.java
  16. 31 12
      RoughlyEnoughItems-runtime/src/main/java/me/shedaniel/rei/impl/AbstractEntryStack.java
  17. 1 2
      RoughlyEnoughItems-runtime/src/main/java/me/shedaniel/rei/impl/ConfigObjectImpl.java
  18. 1 1
      RoughlyEnoughItems-runtime/src/main/java/me/shedaniel/rei/impl/EntryRegistryImpl.java
  19. 3 3
      RoughlyEnoughItems-runtime/src/main/java/me/shedaniel/rei/impl/FluidEntryStack.java
  20. 13 6
      RoughlyEnoughItems-runtime/src/main/java/me/shedaniel/rei/impl/ItemEntryStack.java
  21. 0 31
      RoughlyEnoughItems-runtime/src/main/java/me/shedaniel/rei/impl/ItemStackHook.java
  22. 5 5
      RoughlyEnoughItems-runtime/src/main/java/me/shedaniel/rei/impl/RecipeHelperImpl.java
  23. 0 48
      RoughlyEnoughItems-runtime/src/main/java/me/shedaniel/rei/mixin/MixinItemStack.java
  24. 0 3
      RoughlyEnoughItems-runtime/src/main/resources/fabric.mod.json
  25. 0 13
      RoughlyEnoughItems-runtime/src/main/resources/mixin.roughlyenoughitems-runtime.json
  26. 237 2
      build.gradle
  27. 1 1
      gradle.properties

+ 5 - 1
RoughlyEnoughItems-api/src/main/java/me/shedaniel/rei/api/BuiltinPlugin.java

@@ -56,7 +56,11 @@ public interface BuiltinPlugin {
         return Internals.getBuiltinPlugin();
     }
     
-    void registerBrewingRecipe(ItemStack input, Ingredient ingredient, ItemStack output);
+    default void registerBrewingRecipe(ItemStack input, Ingredient ingredient, ItemStack output) {
+        registerBrewingRecipe(Ingredient.of(input), ingredient, output);
+    }
+    
+    void registerBrewingRecipe(Ingredient input, Ingredient ingredient, ItemStack output);
     
     void registerInformation(List<EntryStack> entryStacks, Component name, UnaryOperator<List<Component>> textBuilder);
     

+ 2 - 0
RoughlyEnoughItems-api/src/main/java/me/shedaniel/rei/api/ConfigObject.java

@@ -59,6 +59,8 @@ public interface ConfigObject {
     
     boolean isToastDisplayedOnCopyIdentifier();
     
+    @Deprecated
+    @ApiStatus.ScheduledForRemoval
     boolean doesRenderEntryEnchantmentGlint();
     
     boolean isEntryListWidgetScrolled();

+ 57 - 8
RoughlyEnoughItems-api/src/main/java/me/shedaniel/rei/api/EntryStack.java

@@ -23,9 +23,12 @@
 
 package me.shedaniel.rei.api;
 
+import com.google.common.collect.ImmutableList;
 import com.google.gson.JsonElement;
 import com.google.gson.JsonObject;
 import com.mojang.blaze3d.vertex.PoseStack;
+import it.unimi.dsi.fastutil.shorts.Short2ObjectMap;
+import it.unimi.dsi.fastutil.shorts.Short2ObjectOpenHashMap;
 import me.shedaniel.math.Point;
 import me.shedaniel.math.Rectangle;
 import me.shedaniel.rei.api.fluid.FluidSupportProvider;
@@ -85,36 +88,57 @@ public interface EntryStack extends TextRepresentable {
     }
     
     static List<EntryStack> ofItems(Collection<ItemLike> stacks) {
-        List<EntryStack> result = new ArrayList<>(stacks.size());
+        if (stacks.size() == 0) return Collections.emptyList();
+        if (stacks.size() == 1) return Collections.singletonList(create(stacks.iterator().next()));
+        EntryStack[] result = new EntryStack[stacks.size()];
+        int i = 0;
         for (ItemLike stack : stacks) {
-            result.add(create(stack));
+            result[i] = create(stack);
+            i++;
         }
-        return result;
+        return Arrays.asList(result);
     }
     
     static List<EntryStack> ofItemStacks(Collection<ItemStack> stacks) {
+        if (stacks.size() == 0) return Collections.emptyList();
+        if (stacks.size() == 1) {
+            ItemStack stack = stacks.iterator().next();
+            if (stack.isEmpty()) return Collections.emptyList();
+            return Collections.singletonList(create(stack));
+        }
         List<EntryStack> result = new ArrayList<>(stacks.size());
         for (ItemStack stack : stacks) {
             result.add(create(stack));
         }
-        return result;
+        return ImmutableList.copyOf(result);
     }
     
     static List<EntryStack> ofIngredient(Ingredient ingredient) {
+        if (ingredient.isEmpty()) return Collections.emptyList();
         ItemStack[] matchingStacks = ingredient.getItems();
+        if (matchingStacks.length == 0) return Collections.emptyList();
+        if (matchingStacks.length == 1) return Collections.singletonList(create(matchingStacks[0]));
         List<EntryStack> result = new ArrayList<>(matchingStacks.length);
         for (ItemStack matchingStack : matchingStacks) {
-            result.add(create(matchingStack));
+            if (!matchingStack.isEmpty())
+                result.add(create(matchingStack));
         }
-        return result;
+        return ImmutableList.copyOf(result);
     }
     
     static List<List<EntryStack>> ofIngredients(List<Ingredient> ingredients) {
+        if (ingredients.size() == 0) return Collections.emptyList();
+        if (ingredients.size() == 1) {
+            Ingredient ingredient = ingredients.get(0);
+            if (ingredient.isEmpty()) return Collections.emptyList();
+            return Collections.singletonList(ofIngredient(ingredient));
+        }
         List<List<EntryStack>> result = new ArrayList<>(ingredients.size());
         for (Ingredient ingredient : ingredients) {
-            result.add(ofIngredient(ingredient));
+            if (!ingredient.isEmpty())
+                result.add(ofIngredient(ingredient));
         }
-        return result;
+        return ImmutableList.copyOf(result);
     }
     
     @Deprecated
@@ -246,6 +270,11 @@ public interface EntryStack extends TextRepresentable {
     
     EntryStack copy();
     
+    @ApiStatus.Internal
+    default EntryStack rewrap() {
+        return copy();
+    }
+    
     Object getObject();
     
     boolean equals(EntryStack stack, boolean ignoreTags, boolean ignoreAmount);
@@ -335,6 +364,9 @@ public interface EntryStack extends TextRepresentable {
     }
     
     class Settings<T> {
+        @ApiStatus.Internal
+        private static final Short2ObjectMap<Settings<?>> ID_TO_SETTINGS = new Short2ObjectOpenHashMap<>();
+        
         public static final Supplier<Boolean> TRUE = () -> true;
         public static final Supplier<Boolean> FALSE = () -> false;
         public static final Settings<Supplier<Boolean>> RENDER = new Settings<>(TRUE);
@@ -346,17 +378,34 @@ public interface EntryStack extends TextRepresentable {
         public static final Settings<Function<EntryStack, List<Component>>> TOOLTIP_APPEND_EXTRA = new Settings<>(stack -> Collections.emptyList());
         public static final Settings<Function<EntryStack, String>> COUNTS = new Settings<>(stack -> null);
         
+        private static short nextId;
         private T defaultValue;
+        private short id;
         
+        @ApiStatus.Internal
         public Settings(T defaultValue) {
             this.defaultValue = defaultValue;
+            this.id = nextId++;
+            ID_TO_SETTINGS.put(this.id, this);
+        }
+        
+        @ApiStatus.Internal
+        public static <T> Settings<T> getById(short id) {
+            return (Settings<T>) ID_TO_SETTINGS.get(id);
         }
         
         public T getDefaultValue() {
             return defaultValue;
         }
         
+        @ApiStatus.Internal
+        public short getId() {
+            return id;
+        }
+        
         public static class Item {
+            @Deprecated
+            @ApiStatus.ScheduledForRemoval
             public static final Settings<Supplier<Boolean>> RENDER_ENCHANTMENT_GLINT = new Settings<>(TRUE);
             
             private Item() {

+ 60 - 43
RoughlyEnoughItems-default-plugin/src/main/java/me/shedaniel/rei/plugin/DefaultPlugin.java

@@ -23,9 +23,12 @@
 
 package me.shedaniel.rei.plugin;
 
+import com.google.common.collect.Iterators;
 import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
 import it.unimi.dsi.fastutil.objects.Object2FloatMap;
+import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet;
+import it.unimi.dsi.fastutil.objects.ReferenceSet;
 import me.shedaniel.math.Rectangle;
 import me.shedaniel.rei.api.*;
 import me.shedaniel.rei.api.fluid.FluidSupportProvider;
@@ -79,10 +82,10 @@ import net.minecraft.resources.ResourceLocation;
 import net.minecraft.tags.BlockTags;
 import net.minecraft.tags.ItemTags;
 import net.minecraft.tags.Tag;
-import net.minecraft.util.LazyLoadedValue;
-import net.minecraft.util.Mth;
 import net.minecraft.world.InteractionResultHolder;
 import net.minecraft.world.item.*;
+import net.minecraft.world.item.alchemy.Potion;
+import net.minecraft.world.item.alchemy.PotionBrewing;
 import net.minecraft.world.item.alchemy.PotionUtils;
 import net.minecraft.world.item.crafting.*;
 import net.minecraft.world.item.enchantment.Enchantment;
@@ -123,8 +126,6 @@ public class DefaultPlugin implements REIPluginV0, BuiltinPlugin {
     public static final ResourceLocation INFO = BuiltinPlugin.INFO;
     private static final ResourceLocation DISPLAY_TEXTURE = new ResourceLocation("roughlyenoughitems", "textures/gui/display.png");
     private static final ResourceLocation DISPLAY_TEXTURE_DARK = new ResourceLocation("roughlyenoughitems", "textures/gui/display_dark.png");
-    private static final List<LazyLoadedValue<DefaultBrewingDisplay>> BREWING_DISPLAYS = Lists.newArrayList();
-    private static final List<DefaultInformationDisplay> INFO_DISPLAYS = Lists.newArrayList();
     
     public static ResourceLocation getDisplayTexture() {
         return REIHelper.getInstance().getDefaultDisplayTexture();
@@ -137,19 +138,19 @@ public class DefaultPlugin implements REIPluginV0, BuiltinPlugin {
     @Deprecated
     @ApiStatus.ScheduledForRemoval
     public static void registerBrewingDisplay(DefaultBrewingDisplay recipe) {
-        BREWING_DISPLAYS.add(new LazyLoadedValue<>(() -> recipe));
+        RecipeHelper.getInstance().registerDisplay(recipe);
     }
     
     public static void registerBrewingRecipe(RegisteredBrewingRecipe recipe) {
-        BREWING_DISPLAYS.add(new LazyLoadedValue<>(() -> new DefaultBrewingDisplay(recipe.input, recipe.ingredient, recipe.output)));
+        RecipeHelper.getInstance().registerDisplay(new DefaultBrewingDisplay(recipe.input, recipe.ingredient, recipe.output));
     }
     
     public static void registerInfoDisplay(DefaultInformationDisplay display) {
-        INFO_DISPLAYS.add(display);
+        RecipeHelper.getInstance().registerDisplay(display);
     }
     
     @Override
-    public void registerBrewingRecipe(ItemStack input, Ingredient ingredient, ItemStack output) {
+    public void registerBrewingRecipe(Ingredient input, Ingredient ingredient, ItemStack output) {
         registerBrewingRecipe(new RegisteredBrewingRecipe(input, ingredient, output));
     }
     
@@ -163,11 +164,6 @@ public class DefaultPlugin implements REIPluginV0, BuiltinPlugin {
         return PLUGIN;
     }
     
-    @Override
-    public void preRegister() {
-        INFO_DISPLAYS.clear();
-    }
-    
     @Override
     public void registerEntries(EntryRegistry entryRegistry) {
         for (Item item : Registry.ITEM) {
@@ -232,41 +228,36 @@ public class DefaultPlugin implements REIPluginV0, BuiltinPlugin {
         recipeHelper.registerRecipes(CAMPFIRE, CampfireCookingRecipe.class, DefaultCampfireDisplay::new);
         recipeHelper.registerRecipes(STONE_CUTTING, StonecutterRecipe.class, DefaultStoneCuttingDisplay::new);
         recipeHelper.registerRecipes(SMITHING, UpgradeRecipe.class, DefaultSmithingDisplay::new);
-        for (LazyLoadedValue<DefaultBrewingDisplay> display : BREWING_DISPLAYS) {
-            recipeHelper.registerDisplay(display.get());
-        }
         for (Map.Entry<Item, Integer> entry : AbstractFurnaceBlockEntity.getFuel().entrySet()) {
             recipeHelper.registerDisplay(new DefaultFuelDisplay(EntryStack.create(entry.getKey()), entry.getValue()));
         }
         List<EntryStack> arrowStack = Collections.singletonList(EntryStack.create(Items.ARROW));
+        ReferenceSet<Potion> registeredPotions = new ReferenceOpenHashSet<>();
         EntryRegistry.getInstance().getEntryStacks().filter(entry -> entry.getItem() == Items.LINGERING_POTION).forEach(entry -> {
-            List<List<EntryStack>> input = new ArrayList<>();
-            for (int i = 0; i < 4; i++)
-                input.add(arrowStack);
-            input.add(Collections.singletonList(EntryStack.create(entry.getItemStack())));
-            for (int i = 0; i < 4; i++)
-                input.add(arrowStack);
-            ItemStack outputStack = new ItemStack(Items.TIPPED_ARROW, 8);
-            PotionUtils.setPotion(outputStack, PotionUtils.getPotion(entry.getItemStack()));
-            PotionUtils.setCustomEffects(outputStack, PotionUtils.getCustomEffects(entry.getItemStack()));
-            List<EntryStack> output = Collections.singletonList(EntryStack.create(outputStack).addSetting(EntryStack.Settings.CHECK_TAGS, EntryStack.Settings.TRUE));
-            recipeHelper.registerDisplay(new DefaultCustomDisplay(null, input, output));
+            Potion potion = PotionUtils.getPotion(entry.getItemStack());
+            if (registeredPotions.add(potion)) {
+                List<List<EntryStack>> input = new ArrayList<>();
+                for (int i = 0; i < 4; i++)
+                    input.add(arrowStack);
+                input.add(Collections.singletonList(EntryStack.create(entry.getItemStack())));
+                for (int i = 0; i < 4; i++)
+                    input.add(arrowStack);
+                ItemStack outputStack = new ItemStack(Items.TIPPED_ARROW, 8);
+                PotionUtils.setPotion(outputStack, potion);
+                PotionUtils.setCustomEffects(outputStack, PotionUtils.getCustomEffects(entry.getItemStack()));
+                List<EntryStack> output = Collections.singletonList(EntryStack.create(outputStack).addSetting(EntryStack.Settings.CHECK_TAGS, EntryStack.Settings.TRUE));
+                recipeHelper.registerDisplay(new DefaultCustomDisplay(null, input, output));
+            }
         });
-        Map<ItemLike, Float> map = Maps.newLinkedHashMap();
         if (ComposterBlock.COMPOSTABLES.isEmpty())
             ComposterBlock.bootStrap();
-        for (Object2FloatMap.Entry<ItemLike> entry : ComposterBlock.COMPOSTABLES.object2FloatEntrySet()) {
-            if (entry.getFloatValue() > 0)
-                map.put(entry.getKey(), entry.getFloatValue());
-        }
-        List<ItemLike> stacks = Lists.newArrayList(map.keySet());
-        stacks.sort(Comparator.comparing(map::get));
-        for (int i = 0; i < stacks.size(); i += Mth.clamp(48, 1, stacks.size() - i)) {
-            List<ItemLike> thisStacks = Lists.newArrayList();
-            for (int j = i; j < i + 48; j++)
-                if (j < stacks.size())
-                    thisStacks.add(stacks.get(j));
-            recipeHelper.registerDisplay(new DefaultCompostingDisplay(Mth.floor(i / 48f), thisStacks, map, new ItemStack(Items.BONE_MEAL)));
+        Object2FloatMap<ItemLike> compostables = ComposterBlock.COMPOSTABLES;
+        int i = 0;
+        Iterator<List<Object2FloatMap.Entry<ItemLike>>> iterator = Iterators.partition(compostables.object2FloatEntrySet().stream().sorted(Map.Entry.comparingByValue()).iterator(), 48);
+        while (iterator.hasNext()) {
+            List<Object2FloatMap.Entry<ItemLike>> entries = iterator.next();
+            recipeHelper.registerDisplay(new DefaultCompostingDisplay(i, entries, compostables, new ItemStack(Items.BONE_MEAL)));
+            i++;
         }
         DummyAxeItem.getStrippedBlocksMap().entrySet().stream().sorted(Comparator.comparing(b -> Registry.BLOCK.getKey(b.getKey()))).forEach(set -> {
             recipeHelper.registerDisplay(new DefaultStrippingDisplay(EntryStack.create(set.getKey()), EntryStack.create(set.getValue())));
@@ -279,12 +270,38 @@ public class DefaultPlugin implements REIPluginV0, BuiltinPlugin {
         });
         recipeHelper.registerDisplay(new DefaultBeaconBaseDisplay(CollectionUtils.map(Lists.newArrayList(BlockTags.BEACON_BASE_BLOCKS.getValues()), ItemStack::new)));
         recipeHelper.registerDisplay(new DefaultBeaconPaymentDisplay(CollectionUtils.map(Lists.newArrayList(ItemTags.BEACON_PAYMENT_ITEMS.getValues()), ItemStack::new)));
+        Set<Potion> potions = Sets.newLinkedHashSet();
+        for (Ingredient container : PotionBrewing.ALLOWED_CONTAINERS) {
+            for (PotionBrewing.Mix<Potion> mix : PotionBrewing.POTION_MIXES) {
+                Potion from = mix.from;
+                Ingredient ingredient = mix.ingredient;
+                Potion to = mix.to;
+                Ingredient base = Ingredient.of(Arrays.stream(container.getItems())
+                        .map(ItemStack::copy)
+                        .map(stack -> PotionUtils.setPotion(stack, from)));
+                ItemStack output = Arrays.stream(container.getItems())
+                        .map(ItemStack::copy)
+                        .map(stack -> PotionUtils.setPotion(stack, to))
+                        .findFirst().orElse(ItemStack.EMPTY);
+                registerBrewingRecipe(base, ingredient, output);
+                potions.add(from);
+                potions.add(to);
+            }
+        }
+        for (Potion potion : potions) {
+            for (PotionBrewing.Mix<Item> mix : PotionBrewing.CONTAINER_MIXES) {
+                Item from = mix.from;
+                Ingredient ingredient = mix.ingredient;
+                Item to = mix.to;
+                Ingredient base = Ingredient.of(PotionUtils.setPotion(new ItemStack(from), potion));
+                ItemStack output = PotionUtils.setPotion(new ItemStack(to), potion);
+                registerBrewingRecipe(base, ingredient, output);
+            }
+        }
     }
     
     @Override
     public void postRegister() {
-        for (DefaultInformationDisplay display : INFO_DISPLAYS)
-            RecipeHelper.getInstance().registerDisplay(display);
         // TODO Turn this into an API
         // Sit tight! This will be a fast journey!
         long time = System.currentTimeMillis();

+ 11 - 5
RoughlyEnoughItems-default-plugin/src/main/java/me/shedaniel/rei/plugin/brewing/DefaultBrewingDisplay.java

@@ -44,12 +44,18 @@ import java.util.List;
 @Environment(EnvType.CLIENT)
 public class DefaultBrewingDisplay implements RecipeDisplay {
     
-    private EntryStack input, output;
-    private List<EntryStack> reactant;
+    private EntryStack output;
+    private List<EntryStack> reactant, input;
     
     @ApiStatus.Internal
-    public DefaultBrewingDisplay(ItemStack input, Ingredient reactant, ItemStack output) {
-        this.input = EntryStack.create(input).setting(EntryStack.Settings.TOOLTIP_APPEND_EXTRA, stack -> Collections.singletonList(new TranslatableComponent("category.rei.brewing.input").withStyle(ChatFormatting.YELLOW)));
+    public DefaultBrewingDisplay(Ingredient input, Ingredient reactant, ItemStack output) {
+        ItemStack[] inputItems = input.getItems();
+        this.input = new ArrayList<>(inputItems.length);
+        for (ItemStack inputItem : inputItems) {
+            EntryStack entryStack = EntryStack.create(inputItem);
+            entryStack.setting(EntryStack.Settings.TOOLTIP_APPEND_EXTRA, s -> Collections.singletonList(new TranslatableComponent("category.rei.brewing.input").withStyle(ChatFormatting.YELLOW)));
+            this.input.add(entryStack);
+        }
         ItemStack[] reactantStacks = reactant.getItems();
         this.reactant = new ArrayList<>(reactantStacks.length);
         for (ItemStack stack : reactantStacks) {
@@ -62,7 +68,7 @@ public class DefaultBrewingDisplay implements RecipeDisplay {
     
     @Override
     public @NotNull List<List<EntryStack>> getInputEntries() {
-        return Lists.newArrayList(Collections.singletonList(input), reactant);
+        return Lists.newArrayList(input, reactant);
     }
     
     @Override

+ 2 - 2
RoughlyEnoughItems-default-plugin/src/main/java/me/shedaniel/rei/plugin/brewing/RegisteredBrewingRecipe.java

@@ -28,11 +28,11 @@ import net.minecraft.world.item.crafting.Ingredient;
 
 public class RegisteredBrewingRecipe {
     
-    public final ItemStack input;
+    public final Ingredient input;
     public final Ingredient ingredient;
     public final ItemStack output;
     
-    public RegisteredBrewingRecipe(ItemStack input, Ingredient ingredient, ItemStack output) {
+    public RegisteredBrewingRecipe(Ingredient input, Ingredient ingredient, ItemStack output) {
         this.input = input;
         this.ingredient = ingredient;
         this.output = output;

+ 8 - 6
RoughlyEnoughItems-default-plugin/src/main/java/me/shedaniel/rei/plugin/composting/DefaultCompostingCategory.java

@@ -86,17 +86,19 @@ public class DefaultCompostingCategory implements RecipeCategory<DefaultComposti
     public @NotNull List<Widget> setupDisplay(DefaultCompostingDisplay display, Rectangle bounds) {
         List<Widget> widgets = Lists.newArrayList();
         Point startingPoint = new Point(bounds.x + bounds.width - 55, bounds.y + 110);
-        List<EntryStack> stacks = new ArrayList<>(display.getRequiredEntries().get(0));
+        List<List<EntryStack>> stacks = new ArrayList<>(display.getInputEntries());
         int i = 0;
         for (int y = 0; y < 6; y++)
             for (int x = 0; x < 8; x++) {
-                EntryStack[] entryStack = {stacks.size() > i ? stacks.get(i) : EntryStack.empty()};
-                if (!entryStack[0].isEmpty()) {
-                    display.getInputMap().entrySet().parallelStream().filter(entry -> entry.getKey() != null && Objects.equals(entry.getKey().asItem(), entryStack[0].getItem())).findAny().map(Map.Entry::getValue).ifPresent(chance -> {
-                        entryStack[0] = entryStack[0].setting(EntryStack.Settings.TOOLTIP_APPEND_EXTRA, s -> Collections.singletonList(new TranslatableComponent("text.rei.composting.chance", Mth.fastFloor(chance * 100)).withStyle(ChatFormatting.YELLOW)));
+                List<EntryStack> entryStack = stacks.size() > i ? stacks.get(i) : Collections.emptyList();
+                if (!entryStack.isEmpty()) {
+                    display.getInputMap().object2FloatEntrySet().stream().filter(entry -> entry.getKey() != null && Objects.equals(entry.getKey().asItem(), entryStack.get(0).getItem())).findAny().map(Map.Entry::getValue).ifPresent(chance -> {
+                        for (EntryStack stack : entryStack) {
+                            stack.setting(EntryStack.Settings.TOOLTIP_APPEND_EXTRA, s -> Collections.singletonList(new TranslatableComponent("text.rei.composting.chance", Mth.fastFloor(chance * 100)).withStyle(ChatFormatting.YELLOW)));
+                        }
                     });
                 }
-                widgets.add(Widgets.createSlot(new Point(bounds.getCenterX() - 72 + x * 18, bounds.y + 3 + y * 18)).entry(entryStack[0]).markInput());
+                widgets.add(Widgets.createSlot(new Point(bounds.getCenterX() - 72 + x * 18, bounds.y + 3 + y * 18)).entries(entryStack).markInput());
                 i++;
             }
         widgets.add(Widgets.createArrow(new Point(startingPoint.x - 1, startingPoint.y + 7)));

+ 21 - 13
RoughlyEnoughItems-default-plugin/src/main/java/me/shedaniel/rei/plugin/composting/DefaultCompostingDisplay.java

@@ -23,10 +23,10 @@
 
 package me.shedaniel.rei.plugin.composting;
 
+import it.unimi.dsi.fastutil.objects.Object2FloatMap;
 import me.shedaniel.rei.api.EntryStack;
 import me.shedaniel.rei.api.RecipeDisplay;
 import me.shedaniel.rei.plugin.DefaultPlugin;
-import me.shedaniel.rei.utils.CollectionUtils;
 import net.fabricmc.api.EnvType;
 import net.fabricmc.api.Environment;
 import net.minecraft.resources.ResourceLocation;
@@ -34,22 +34,30 @@ import net.minecraft.world.item.ItemStack;
 import net.minecraft.world.level.ItemLike;
 import org.jetbrains.annotations.NotNull;
 
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
-import java.util.Map;
 
 @Environment(EnvType.CLIENT)
 public class DefaultCompostingDisplay implements RecipeDisplay {
-    private List<EntryStack> order;
-    private Map<ItemLike, Float> inputMap;
-    private List<EntryStack> output;
+    private List<List<EntryStack>> inputs;
+    private Object2FloatMap<ItemLike> inputMap;
+    private List<List<EntryStack>> output;
     private int page;
     
-    public DefaultCompostingDisplay(int page, List<ItemLike> order, Map<ItemLike, Float> inputMap, ItemStack output) {
+    public DefaultCompostingDisplay(int page, List<Object2FloatMap.Entry<ItemLike>> inputs, Object2FloatMap<ItemLike> map, ItemStack output) {
         this.page = page;
-        this.order = EntryStack.ofItems(order);
-        this.inputMap = inputMap;
-        this.output = EntryStack.ofItemStacks(Collections.singletonList(output));
+        {
+            List<EntryStack>[] result = new List[inputs.size()];
+            int i = 0;
+            for (Object2FloatMap.Entry<ItemLike> entry : inputs) {
+                result[i] = Collections.singletonList(EntryStack.create(entry.getKey()));
+                i++;
+            }
+            this.inputs = Arrays.asList(result);
+        }
+        this.inputMap = map;
+        this.output = Collections.singletonList(Collections.singletonList(EntryStack.create(output)));
     }
     
     public int getPage() {
@@ -58,16 +66,16 @@ public class DefaultCompostingDisplay implements RecipeDisplay {
     
     @Override
     public @NotNull List<List<EntryStack>> getInputEntries() {
-        return CollectionUtils.map(order, Collections::singletonList);
+        return inputs;
     }
     
-    public Map<ItemLike, Float> getInputMap() {
+    public Object2FloatMap<ItemLike> getInputMap() {
         return inputMap;
     }
     
     @Override
     public @NotNull List<List<EntryStack>> getResultingEntries() {
-        return Collections.singletonList(output);
+        return output;
     }
     
     @Override
@@ -77,6 +85,6 @@ public class DefaultCompostingDisplay implements RecipeDisplay {
     
     @Override
     public @NotNull List<List<EntryStack>> getRequiredEntries() {
-        return Collections.singletonList(order);
+        return inputs;
     }
 }

+ 10 - 9
RoughlyEnoughItems-default-plugin/src/main/java/me/shedaniel/rei/plugin/crafting/DefaultCustomDisplay.java

@@ -23,7 +23,7 @@
 
 package me.shedaniel.rei.plugin.crafting;
 
-import com.google.common.collect.Lists;
+import com.google.common.collect.ImmutableList;
 import me.shedaniel.rei.api.EntryStack;
 import me.shedaniel.rei.utils.CollectionUtils;
 import net.fabricmc.api.EnvType;
@@ -33,6 +33,7 @@ import net.minecraft.world.item.ItemStack;
 import net.minecraft.world.item.crafting.Recipe;
 import org.jetbrains.annotations.NotNull;
 
+import java.util.BitSet;
 import java.util.Collections;
 import java.util.List;
 import java.util.Optional;
@@ -50,21 +51,21 @@ public class DefaultCustomDisplay implements DefaultCraftingDisplay {
     }
     
     public DefaultCustomDisplay(Recipe<?> possibleRecipe, List<List<EntryStack>> input, List<EntryStack> output) {
-        this.input = input;
-        this.output = output;
+        this.input = ImmutableList.copyOf(input);
+        this.output = ImmutableList.copyOf(output);
         this.possibleRecipe = possibleRecipe;
-        List<Boolean> row = Lists.newArrayList(false, false, false);
-        List<Boolean> column = Lists.newArrayList(false, false, false);
+        BitSet row = new BitSet(3);
+        BitSet column = new BitSet(3);
         for (int i = 0; i < 9; i++)
             if (i < this.input.size()) {
                 List<EntryStack> stacks = this.input.get(i);
                 if (stacks.stream().anyMatch(stack -> !stack.isEmpty())) {
-                    row.set((i - (i % 3)) / 3, true);
-                    column.set(i % 3, true);
+                    row.set((i - (i % 3)) / 3);
+                    column.set(i % 3);
                 }
             }
-        this.width = (int) column.stream().filter(Boolean::booleanValue).count();
-        this.height = (int) row.stream().filter(Boolean::booleanValue).count();
+        this.width = row.cardinality();
+        this.height = column.cardinality();
     }
     
     public DefaultCustomDisplay(List<List<ItemStack>> input, List<ItemStack> output) {

+ 0 - 88
RoughlyEnoughItems-default-plugin/src/main/java/me/shedaniel/rei/plugin/mixin/MixinPotionBrewing.java

@@ -1,88 +0,0 @@
-/*
- * This file is licensed under the MIT License, part of Roughly Enough Items.
- * Copyright (c) 2018, 2019, 2020 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.plugin.mixin;
-
-import com.google.common.collect.Lists;
-import me.shedaniel.rei.plugin.DefaultPlugin;
-import me.shedaniel.rei.plugin.brewing.BrewingRecipe;
-import me.shedaniel.rei.plugin.brewing.RegisteredBrewingRecipe;
-import net.fabricmc.api.EnvType;
-import net.fabricmc.api.Environment;
-import net.minecraft.world.item.Item;
-import net.minecraft.world.item.ItemStack;
-import net.minecraft.world.item.PotionItem;
-import net.minecraft.world.item.alchemy.Potion;
-import net.minecraft.world.item.alchemy.PotionBrewing;
-import net.minecraft.world.item.alchemy.PotionUtils;
-import net.minecraft.world.item.crafting.Ingredient;
-import org.spongepowered.asm.mixin.Mixin;
-import org.spongepowered.asm.mixin.Unique;
-import org.spongepowered.asm.mixin.injection.At;
-import org.spongepowered.asm.mixin.injection.Inject;
-import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
-
-import java.util.List;
-
-@Mixin(PotionBrewing.class)
-@Environment(EnvType.CLIENT)
-public class MixinPotionBrewing {
-    
-    @Unique private static final List<BrewingRecipe> SELF_ITEM_RECIPES = Lists.newArrayList();
-    @Unique private static final List<Potion> REGISTERED_POTION_TYPES = Lists.newArrayList();
-    @Unique private static final List<Ingredient> SELF_POTION_TYPES = Lists.newArrayList();
-    
-    @Inject(method = "addContainer", at = @At("RETURN"))
-    private static void addContainer(Item item_1, CallbackInfo ci) {
-        if (item_1 instanceof PotionItem)
-            SELF_POTION_TYPES.add(Ingredient.of(item_1));
-    }
-    
-    @Inject(method = "addContainerRecipe", at = @At("RETURN"))
-    private static void addContainerRecipe(Item item_1, Item item_2, Item item_3, CallbackInfo ci) {
-        if (item_1 instanceof PotionItem && item_3 instanceof PotionItem)
-            SELF_ITEM_RECIPES.add(new BrewingRecipe(item_1, Ingredient.of(item_2), item_3));
-    }
-    
-    @Inject(method = "addMix", at = @At("RETURN"))
-    private static void addMix(Potion potion_1, Item item_1, Potion potion_2, CallbackInfo ci) {
-        if (!REGISTERED_POTION_TYPES.contains(potion_1))
-            rei_registerPotionType(potion_1);
-        if (!REGISTERED_POTION_TYPES.contains(potion_2))
-            rei_registerPotionType(potion_2);
-        for (Ingredient type : SELF_POTION_TYPES) {
-            for (ItemStack stack : type.getItems()) {
-                DefaultPlugin.registerBrewingRecipe(new RegisteredBrewingRecipe(PotionUtils.setPotion(stack.copy(), potion_1), Ingredient.of(item_1), PotionUtils.setPotion(stack.copy(), potion_2)));
-            }
-        }
-    }
-    
-    @Unique
-    private static void rei_registerPotionType(Potion potion) {
-        REGISTERED_POTION_TYPES.add(potion);
-        for (BrewingRecipe recipe : SELF_ITEM_RECIPES) {
-            DefaultPlugin.registerBrewingRecipe(new RegisteredBrewingRecipe(PotionUtils.setPotion(recipe.input.getDefaultInstance(), potion), recipe.ingredient, PotionUtils.setPotion(recipe.output.getDefaultInstance(), potion)));
-        }
-    }
-    
-}

+ 0 - 3
RoughlyEnoughItems-default-plugin/src/main/resources/fabric.mod.json

@@ -23,9 +23,6 @@
     ]
   },
   "accessWidener": "roughlyenoughitems-default-plugin.accessWidener",
-  "mixins": [
-    "mixin.roughlyenoughitems-default-plugin.json"
-  ],
   "custom": {
     "modmenu:parent": "roughlyenoughitems"
   }

+ 0 - 13
RoughlyEnoughItems-default-plugin/src/main/resources/mixin.roughlyenoughitems-default-plugin.json

@@ -1,13 +0,0 @@
-{
-  "required": true,
-  "package": "me.shedaniel.rei.plugin.mixin",
-  "minVersion": "0.7.11",
-  "compatibilityLevel": "JAVA_8",
-  "mixins": [],
-  "client": [
-    "MixinPotionBrewing"
-  ],
-  "injectors": {
-    "defaultRequire": 1
-  }
-}

+ 7 - 0
RoughlyEnoughItems-default-plugin/src/main/resources/roughlyenoughitems-default-plugin.accessWidener

@@ -12,3 +12,10 @@ accessible field net/minecraft/client/gui/screens/inventory/AbstractContainerScr
 accessible method net/minecraft/client/gui/GuiComponent innerBlit (Lcom/mojang/math/Matrix4f;IIIIIFFFF)V
 accessible field net/minecraft/world/item/crafting/UpgradeRecipe base Lnet/minecraft/world/item/crafting/Ingredient;
 accessible field net/minecraft/world/item/crafting/UpgradeRecipe addition Lnet/minecraft/world/item/crafting/Ingredient;
+accessible field net/minecraft/world/item/alchemy/PotionBrewing ALLOWED_CONTAINERS Ljava/util/List;
+accessible field net/minecraft/world/item/alchemy/PotionBrewing POTION_MIXES Ljava/util/List;
+accessible field net/minecraft/world/item/alchemy/PotionBrewing CONTAINER_MIXES Ljava/util/List;
+accessible class net/minecraft/world/item/alchemy/PotionBrewing$Mix
+accessible field net/minecraft/world/item/alchemy/PotionBrewing$Mix from Ljava/lang/Object;
+accessible field net/minecraft/world/item/alchemy/PotionBrewing$Mix to Ljava/lang/Object;
+accessible field net/minecraft/world/item/alchemy/PotionBrewing$Mix ingredient Lnet/minecraft/world/item/crafting/Ingredient;

+ 5 - 7
RoughlyEnoughItems-runtime/src/main/java/me/shedaniel/rei/gui/widget/EntryListWidget.java

@@ -25,10 +25,11 @@ package me.shedaniel.rei.gui.widget;
 
 import com.google.common.base.Stopwatch;
 import com.google.common.collect.Lists;
-import com.google.common.collect.Sets;
 import com.mojang.blaze3d.vertex.PoseStack;
 import com.mojang.blaze3d.vertex.Tesselator;
 import com.mojang.math.Matrix4f;
+import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
+import it.unimi.dsi.fastutil.ints.IntSet;
 import me.shedaniel.clothconfig2.ClothConfigInitializer;
 import me.shedaniel.clothconfig2.api.ScissorsHandler;
 import me.shedaniel.clothconfig2.api.ScrollingContainer;
@@ -62,16 +63,13 @@ import org.jetbrains.annotations.Nullable;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
-import java.util.Set;
 import java.util.concurrent.*;
-import java.util.function.Supplier;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
 @ApiStatus.Internal
 public class EntryListWidget extends WidgetWithBounds {
     
-    static final Supplier<Boolean> RENDER_ENCHANTMENT_GLINT = ConfigObject.getInstance()::doesRenderEntryEnchantmentGlint;
     static final Comparator<? super EntryStack> ENTRY_NAME_COMPARER = Comparator.comparing(stack -> stack.asFormatStrippedText().getString());
     static final Comparator<? super EntryStack> ENTRY_GROUP_COMPARER = Comparator.comparingInt(stack -> {
         if (stack.getType() == EntryStack.Type.ITEM) {
@@ -436,7 +434,7 @@ public class EntryListWidget extends WidgetWithBounds {
             this.lastSearchArguments = SearchArgument.processSearchTerm(searchTerm);
             List<EntryStack> list = Lists.newArrayList();
             boolean checkCraftable = ConfigManager.getInstance().isCraftableOnlyEnabled() && !ScreenHelper.inventoryStacks.isEmpty();
-            Set<Integer> workingItems = checkCraftable ? Sets.newHashSet() : null;
+            IntSet workingItems = checkCraftable ? new IntOpenHashSet() : null;
             if (checkCraftable)
                 workingItems.addAll(CollectionUtils.map(RecipeHelper.getInstance().findCraftableEntriesByItems(ScreenHelper.inventoryStacks), EntryStack::hashIgnoreAmount));
             List<EntryStack> stacks = EntryRegistry.getInstance().getPreFilteredList();
@@ -450,7 +448,7 @@ public class EntryListWidget extends WidgetWithBounds {
                                 if (canLastSearchTermsBeAppliedTo(stack)) {
                                     if (workingItems != null && !workingItems.contains(stack.hashIgnoreAmount()))
                                         continue;
-                                    filtered.add(stack.copy().setting(EntryStack.Settings.RENDER_COUNTS, EntryStack.Settings.FALSE).setting(EntryStack.Settings.Item.RENDER_ENCHANTMENT_GLINT, RENDER_ENCHANTMENT_GLINT));
+                                    filtered.add(stack.rewrap().setting(EntryStack.Settings.RENDER_COUNTS, EntryStack.Settings.FALSE));
                                 }
                             }
                             return filtered;
@@ -471,7 +469,7 @@ public class EntryListWidget extends WidgetWithBounds {
                         if (canLastSearchTermsBeAppliedTo(stack)) {
                             if (workingItems != null && !workingItems.contains(stack.hashIgnoreAmount()))
                                 continue;
-                            list.add(stack.copy().setting(EntryStack.Settings.RENDER_COUNTS, EntryStack.Settings.FALSE).setting(EntryStack.Settings.Item.RENDER_ENCHANTMENT_GLINT, RENDER_ENCHANTMENT_GLINT));
+                            list.add(stack.rewrap().setting(EntryStack.Settings.RENDER_COUNTS, EntryStack.Settings.FALSE));
                         }
                     }
                 }

+ 6 - 6
RoughlyEnoughItems-runtime/src/main/java/me/shedaniel/rei/gui/widget/FavoritesListWidget.java

@@ -24,8 +24,9 @@
 package me.shedaniel.rei.gui.widget;
 
 import com.google.common.collect.Lists;
-import com.google.common.collect.Sets;
 import com.mojang.blaze3d.vertex.PoseStack;
+import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
+import it.unimi.dsi.fastutil.ints.IntSet;
 import me.shedaniel.clothconfig2.ClothConfigInitializer;
 import me.shedaniel.clothconfig2.api.ScissorsHandler;
 import me.shedaniel.clothconfig2.api.ScrollingContainer;
@@ -51,7 +52,6 @@ import org.jetbrains.annotations.Nullable;
 
 import java.util.Collections;
 import java.util.List;
-import java.util.Set;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
@@ -199,14 +199,14 @@ public class FavoritesListWidget extends WidgetWithBounds {
             if (ConfigObject.getInstance().doSearchFavorites()) {
                 List<EntryStack> list = Lists.newArrayList();
                 boolean checkCraftable = ConfigManager.getInstance().isCraftableOnlyEnabled() && !ScreenHelper.inventoryStacks.isEmpty();
-                Set<Integer> workingItems = checkCraftable ? Sets.newHashSet() : null;
+                IntSet workingItems = checkCraftable ? new IntOpenHashSet() : null;
                 if (checkCraftable)
                     workingItems.addAll(CollectionUtils.map(RecipeHelper.getInstance().findCraftableEntriesByItems(ScreenHelper.inventoryStacks), EntryStack::hashIgnoreAmount));
                 for (EntryStack stack : ConfigObject.getInstance().getFavorites()) {
                     if (listWidget.canLastSearchTermsBeAppliedTo(stack)) {
                         if (checkCraftable && !workingItems.contains(stack.hashIgnoreAmount()))
                             continue;
-                        list.add(stack.copy().setting(EntryStack.Settings.RENDER_COUNTS, EntryStack.Settings.FALSE).setting(EntryStack.Settings.Item.RENDER_ENCHANTMENT_GLINT, RENDER_ENCHANTMENT_GLINT));
+                        list.add(stack.copy().setting(EntryStack.Settings.RENDER_COUNTS, EntryStack.Settings.FALSE));
                     }
                 }
                 EntryPanelOrdering ordering = ConfigObject.getInstance().getItemListOrdering();
@@ -220,13 +220,13 @@ public class FavoritesListWidget extends WidgetWithBounds {
             } else {
                 List<EntryStack> list = Lists.newArrayList();
                 boolean checkCraftable = ConfigManager.getInstance().isCraftableOnlyEnabled() && !ScreenHelper.inventoryStacks.isEmpty();
-                Set<Integer> workingItems = checkCraftable ? Sets.newHashSet() : null;
+                IntSet workingItems = checkCraftable ? new IntOpenHashSet() : null;
                 if (checkCraftable)
                     workingItems.addAll(CollectionUtils.map(RecipeHelper.getInstance().findCraftableEntriesByItems(ScreenHelper.inventoryStacks), EntryStack::hashIgnoreAmount));
                 for (EntryStack stack : ConfigObject.getInstance().getFavorites()) {
                     if (checkCraftable && !workingItems.contains(stack.hashIgnoreAmount()))
                         continue;
-                    list.add(stack.copy().setting(EntryStack.Settings.RENDER_COUNTS, EntryStack.Settings.FALSE).setting(EntryStack.Settings.Item.RENDER_ENCHANTMENT_GLINT, RENDER_ENCHANTMENT_GLINT));
+                    list.add(stack.copy().setting(EntryStack.Settings.RENDER_COUNTS, EntryStack.Settings.FALSE));
                 }
                 EntryPanelOrdering ordering = ConfigObject.getInstance().getItemListOrdering();
                 if (ordering == EntryPanelOrdering.NAME)

+ 31 - 12
RoughlyEnoughItems-runtime/src/main/java/me/shedaniel/rei/impl/AbstractEntryStack.java

@@ -23,31 +23,50 @@
 
 package me.shedaniel.rei.impl;
 
-import it.unimi.dsi.fastutil.objects.Reference2ObjectMaps;
-import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap;
+import it.unimi.dsi.fastutil.shorts.Short2ObjectMap;
+import it.unimi.dsi.fastutil.shorts.Short2ObjectMaps;
+import it.unimi.dsi.fastutil.shorts.Short2ObjectOpenHashMap;
 import me.shedaniel.rei.api.EntryStack;
 import net.minecraft.client.gui.GuiComponent;
 import org.jetbrains.annotations.ApiStatus;
 
-import java.util.Map;
-
 @ApiStatus.Internal
 public abstract class AbstractEntryStack extends GuiComponent implements EntryStack {
-    private static final Map<Settings<?>, Object> EMPTY_SETTINGS = Reference2ObjectMaps.emptyMap();
-    private Map<Settings<?>, Object> settings = null;
+    private static final Short2ObjectMap<Object> EMPTY_SETTINGS = Short2ObjectMaps.emptyMap();
+    private Short2ObjectMap<Object> settings = null;
     
     @Override
     public <T> EntryStack setting(Settings<T> settings, T value) {
+        short settingsId = settings.getId();
         if (this.settings == null)
-            this.settings = new Reference2ObjectOpenHashMap<>(4);
-        this.settings.put(settings, value);
+            this.settings = Short2ObjectMaps.singleton(settingsId, value);
+        else {
+            if (this.settings.size() == 1) {
+                if (this.settings.containsKey(settingsId)) {
+                    this.settings = Short2ObjectMaps.singleton(settingsId, value);
+                    return this;
+                } else {
+                    Short2ObjectMap<Object> singletonSettings = this.settings;
+                    this.settings = new Short2ObjectOpenHashMap<>(4, 1);
+                    this.settings.putAll(singletonSettings);
+                }
+            }
+            this.settings.put(settingsId, value);
+        }
         return this;
     }
     
     @Override
     public <T> EntryStack removeSetting(Settings<T> settings) {
-        if (this.settings != null && this.settings.remove(settings) != null && this.settings.isEmpty()) {
-            this.settings = null;
+        if (this.settings != null) {
+            short settingsId = settings.getId();
+            if (this.settings.size() == 1) {
+                if (this.settings.containsKey(settingsId)) {
+                    this.settings = null;
+                }
+            } else if (this.settings.remove(settingsId) != null && this.settings.isEmpty()) {
+                this.settings = null;
+            }
         }
         return this;
     }
@@ -58,13 +77,13 @@ public abstract class AbstractEntryStack extends GuiComponent implements EntrySt
         return this;
     }
     
-    protected Map<Settings<?>, Object> getSettings() {
+    protected Short2ObjectMap<Object> getSettings() {
         return this.settings == null ? EMPTY_SETTINGS : this.settings;
     }
     
     @Override
     public <T> T get(Settings<T> settings) {
-        Object o = this.settings == null ? null : this.settings.get(settings);
+        Object o = this.settings == null ? null : this.settings.get(settings.getId());
         if (o == null)
             return settings.getDefaultValue();
         return (T) o;

+ 1 - 2
RoughlyEnoughItems-runtime/src/main/java/me/shedaniel/rei/impl/ConfigObjectImpl.java

@@ -106,7 +106,7 @@ public class ConfigObjectImpl implements ConfigObject, ConfigData {
     
     @Override
     public boolean doesRenderEntryEnchantmentGlint() {
-        return advanced.miscellaneous.renderEntryEnchantmentGlint;
+        return true;
     }
     
     @Override
@@ -464,7 +464,6 @@ public class ConfigObjectImpl implements ConfigObject, ConfigData {
         public static class Miscellaneous {
             @Comment("Declares whether arrows in containers should be clickable.") private boolean clickableRecipeArrows = true;
             private boolean registerRecipesInAnotherThread = true;
-            @Comment("Whether REI should render entry's enchantment glint") private boolean renderEntryEnchantmentGlint = true;
             private boolean newFastEntryRendering = true;
         }
         

+ 1 - 1
RoughlyEnoughItems-runtime/src/main/java/me/shedaniel/rei/impl/EntryRegistryImpl.java

@@ -78,7 +78,7 @@ public class EntryRegistryImpl implements EntryRegistry {
     @Override
     @NotNull
     public Stream<EntryStack> getEntryStacks() {
-        return entries.stream();
+        return reloading ? reloadingRegistry.stream().map(AmountIgnoredEntryStackWrapper::unwrap) : entries.stream();
     }
     
     @Override

+ 3 - 3
RoughlyEnoughItems-runtime/src/main/java/me/shedaniel/rei/impl/FluidEntryStack.java

@@ -29,6 +29,7 @@ import com.mojang.blaze3d.vertex.DefaultVertexFormat;
 import com.mojang.blaze3d.vertex.PoseStack;
 import com.mojang.blaze3d.vertex.Tesselator;
 import com.mojang.math.Matrix4f;
+import it.unimi.dsi.fastutil.shorts.Short2ObjectMap;
 import me.shedaniel.math.Point;
 import me.shedaniel.math.Rectangle;
 import me.shedaniel.rei.api.ClientHelper;
@@ -55,7 +56,6 @@ import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
 import java.util.List;
-import java.util.Map;
 import java.util.Optional;
 import java.util.concurrent.ThreadLocalRandom;
 import java.util.stream.Collectors;
@@ -124,8 +124,8 @@ public class FluidEntryStack extends AbstractEntryStack {
     @Override
     public EntryStack copy() {
         EntryStack stack = EntryStack.create(fluid, amount);
-        for (Map.Entry<Settings<?>, Object> entry : getSettings().entrySet()) {
-            stack.setting((Settings<? super Object>) entry.getKey(), entry.getValue());
+        for (Short2ObjectMap.Entry<Object> entry : getSettings().short2ObjectEntrySet()) {
+            stack.setting(EntryStack.Settings.getById(entry.getShortKey()), entry.getValue());
         }
         return stack;
     }

+ 13 - 6
RoughlyEnoughItems-runtime/src/main/java/me/shedaniel/rei/impl/ItemEntryStack.java

@@ -30,6 +30,7 @@ import com.mojang.blaze3d.systems.RenderSystem;
 import com.mojang.blaze3d.vertex.PoseStack;
 import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet;
 import it.unimi.dsi.fastutil.objects.ReferenceSet;
+import it.unimi.dsi.fastutil.shorts.Short2ObjectMap;
 import me.shedaniel.math.Point;
 import me.shedaniel.math.Rectangle;
 import me.shedaniel.rei.api.ClientHelper;
@@ -61,7 +62,6 @@ import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
 import java.util.List;
-import java.util.Map;
 import java.util.Optional;
 import java.util.stream.Stream;
 
@@ -110,9 +110,18 @@ public class ItemEntryStack extends AbstractEntryStack implements OptimalEntrySt
     
     @Override
     public EntryStack copy() {
-        EntryStack stack = EntryStack.create(getItemStack().copy());
-        for (Map.Entry<Settings<?>, Object> entry : getSettings().entrySet()) {
-            stack.setting((Settings<? super Object>) entry.getKey(), entry.getValue());
+        EntryStack stack = EntryStack.create(itemStack.copy());
+        for (Short2ObjectMap.Entry<Object> entry : getSettings().short2ObjectEntrySet()) {
+            stack.setting(EntryStack.Settings.getById(entry.getShortKey()), entry.getValue());
+        }
+        return stack;
+    }
+    
+    @Override
+    public EntryStack rewrap() {
+        EntryStack stack = EntryStack.create(itemStack);
+        for (Short2ObjectMap.Entry<Object> entry : getSettings().short2ObjectEntrySet()) {
+            stack.setting(EntryStack.Settings.getById(entry.getShortKey()), entry.getValue());
         }
         return stack;
     }
@@ -358,13 +367,11 @@ public class ItemEntryStack extends AbstractEntryStack implements OptimalEntrySt
     public void optimisedRenderBase(PoseStack matrices, MultiBufferSource.BufferSource immediate, Rectangle bounds, int mouseX, int mouseY, float delta) {
         if (!isEmpty() && get(Settings.RENDER).get()) {
             ItemStack stack = getItemStack();
-            ((ItemStackHook) (Object) stack).rei_setRenderEnchantmentGlint(get(Settings.Item.RENDER_ENCHANTMENT_GLINT).get());
             matrices.pushPose();
             matrices.translate(bounds.getCenterX(), bounds.getCenterY(), 100.0F + getZ());
             matrices.scale(bounds.getWidth(), (bounds.getWidth() + bounds.getHeight()) / -2f, bounds.getHeight());
             Minecraft.getInstance().getItemRenderer().render(stack, ItemTransforms.TransformType.GUI, false, matrices, immediate, 15728880, OverlayTexture.NO_OVERLAY, getModelFromStack(stack));
             matrices.popPose();
-            ((ItemStackHook) (Object) stack).rei_setRenderEnchantmentGlint(false);
         }
     }
     

+ 0 - 31
RoughlyEnoughItems-runtime/src/main/java/me/shedaniel/rei/impl/ItemStackHook.java

@@ -1,31 +0,0 @@
-/*
- * This file is licensed under the MIT License, part of Roughly Enough Items.
- * Copyright (c) 2018, 2019, 2020 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;
-
-import org.jetbrains.annotations.ApiStatus;
-
-@ApiStatus.Internal
-public interface ItemStackHook {
-    void rei_setRenderEnchantmentGlint(boolean b);
-}

+ 5 - 5
RoughlyEnoughItems-runtime/src/main/java/me/shedaniel/rei/impl/RecipeHelperImpl.java

@@ -309,7 +309,7 @@ public class RecipeHelperImpl implements RecipeHelper {
     private void startSection(MutablePair<Stopwatch, String> sectionData, String section) {
         sectionData.setRight(section);
         RoughlyEnoughItemsCore.LOGGER.debug("Reloading Section: \"%s\"", section);
-        sectionData.getLeft().start();
+        sectionData.getLeft().reset().start();
     }
     
     private void endSection(MutablePair<Stopwatch, String> sectionData) {
@@ -321,13 +321,13 @@ public class RecipeHelperImpl implements RecipeHelper {
     
     private void pluginSection(MutablePair<Stopwatch, String> sectionData, String sectionName, List<REIPluginV0> list, Consumer<REIPluginV0> consumer) {
         for (REIPluginV0 plugin : list) {
+            startSection(sectionData, sectionName + " for " + plugin.getPluginIdentifier().toString());
             try {
-                startSection(sectionData, sectionName + " for " + plugin.getPluginIdentifier().toString());
                 consumer.accept(plugin);
-                endSection(sectionData);
             } catch (Throwable e) {
                 RoughlyEnoughItemsCore.LOGGER.error(plugin.getPluginIdentifier().toString() + " plugin failed to " + sectionName + "!", e);
             }
+            endSection(sectionData);
         }
     }
     
@@ -378,16 +378,16 @@ public class RecipeHelperImpl implements RecipeHelper {
         List<REIPluginV0> reiPluginV0s = new ArrayList<>();
         endSection(sectionData);
         for (REIPluginEntry plugin : plugins) {
+            startSection(sectionData, "pre-register for " + plugin.getPluginIdentifier().toString());
             try {
                 if (plugin instanceof REIPluginV0) {
-                    startSection(sectionData, "pre-register for " + plugin.getPluginIdentifier().toString());
                     ((REIPluginV0) plugin).preRegister();
                     reiPluginV0s.add((REIPluginV0) plugin);
-                    endSection(sectionData);
                 }
             } catch (Throwable e) {
                 RoughlyEnoughItemsCore.LOGGER.error(plugin.getPluginIdentifier().toString() + " plugin failed to pre register!", e);
             }
+            endSection(sectionData);
         }
         pluginSection(sectionData, "register-bounds", reiPluginV0s, plugin -> plugin.registerBounds(displayHelper));
         pluginSection(sectionData, "register-entries", reiPluginV0s, plugin -> plugin.registerEntries(entryRegistry));

+ 0 - 48
RoughlyEnoughItems-runtime/src/main/java/me/shedaniel/rei/mixin/MixinItemStack.java

@@ -1,48 +0,0 @@
-/*
- * This file is licensed under the MIT License, part of Roughly Enough Items.
- * Copyright (c) 2018, 2019, 2020 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.mixin;
-
-import me.shedaniel.rei.impl.ItemStackHook;
-import net.minecraft.world.item.ItemStack;
-import org.spongepowered.asm.mixin.Mixin;
-import org.spongepowered.asm.mixin.injection.At;
-import org.spongepowered.asm.mixin.injection.Inject;
-import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
-
-@Mixin(ItemStack.class)
-public class MixinItemStack implements ItemStackHook {
-    private boolean rei_dontRenderOverlay = false;
-    
-    @Override
-    public void rei_setRenderEnchantmentGlint(boolean b) {
-        rei_dontRenderOverlay = !b;
-    }
-    
-    @Inject(method = "hasFoil", at = @At("HEAD"), cancellable = true)
-    public void hasEnchantmentGlint(CallbackInfoReturnable<Boolean> callbackInfo) {
-        if (rei_dontRenderOverlay)
-            callbackInfo.setReturnValue(false);
-    }
-    
-}

+ 0 - 3
RoughlyEnoughItems-runtime/src/main/resources/fabric.mod.json

@@ -30,9 +30,6 @@
       "me.shedaniel.rei.gui.plugin.DefaultRuntimePlugin"
     ]
   },
-  "mixins": [
-    "mixin.roughlyenoughitems-runtime.json"
-  ],
   "accessWidener": "roughlyenoughitems-runtime.accessWidener",
   "custom": {
     "modmenu:parent": "roughlyenoughitems",

+ 0 - 13
RoughlyEnoughItems-runtime/src/main/resources/mixin.roughlyenoughitems-runtime.json

@@ -1,13 +0,0 @@
-{
-  "required": true,
-  "package": "me.shedaniel.rei.mixin",
-  "minVersion": "0.7.11",
-  "compatibilityLevel": "JAVA_8",
-  "mixins": [],
-  "client": [
-    "MixinItemStack"
-  ],
-  "injectors": {
-    "defaultRequire": 1
-  }
-}

+ 237 - 2
build.gradle

@@ -8,9 +8,25 @@ plugins {
     id("net.corda.plugins.jar-filter") version("5.0.8") apply false
 }
 
+import net.fabricmc.loom.LoomGradleExtension
 import net.fabricmc.loom.task.RemapJarTask
-
+import net.fabricmc.loom.util.DownloadUtil
+import net.fabricmc.loom.util.MinecraftVersionInfo
+import net.fabricmc.lorenztiny.TinyMappingsReader
+import net.fabricmc.mapping.tree.TinyMappingFactory
+import org.cadixdev.lorenz.MappingSet
+import org.cadixdev.lorenz.io.TextMappingsWriter
+import org.cadixdev.lorenz.io.proguard.ProGuardReader
+import org.cadixdev.lorenz.model.*
+import org.zeroturnaround.zip.ByteSource
+import org.zeroturnaround.zip.ZipEntrySource
+import org.zeroturnaround.zip.ZipUtil
+
+import java.nio.charset.StandardCharsets
+import java.nio.file.Files
+import java.nio.file.Path
 import java.text.SimpleDateFormat
+import java.util.function.Consumer
 
 archivesBaseName = "RoughlyEnoughItems"
 version = project.mod_version
@@ -61,7 +77,7 @@ allprojects {
 
     dependencies {
         minecraft("com.mojang:minecraft:${project.minecraft_version}")
-        mappings(minecraft.officialMojangMappings())
+        mappings(new MojangMappingsDependency(project, loom))
         modApi("net.fabricmc:fabric-loader:${project.fabricloader_version}")
         modApi("net.fabricmc.fabric-api:fabric-api:${project.fabric_api}") {
             exclude(module: "fabric-biomes-v1")
@@ -327,3 +343,222 @@ publishing {
         }
     }
 }
+
+/*
+The following code is licensed under MIT License.
+
+Copyright (c) 2016 FabricMC
+
+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.
+*/
+
+class MojangMappingsDependency implements SelfResolvingDependency {
+    private final Project project
+    private final LoomGradleExtension extension
+
+    MojangMappingsDependency(Project project, LoomGradleExtension extension) {
+        this.project = project
+        this.extension = extension
+    }
+
+    @Override
+    Set<File> resolve() {
+        Path mappingsDir = extension.getMappingsProvider().getMappingsDir()
+        Path mappingsFile = mappingsDir.resolve(String.format("net.mojang.minecraft-mappings-%s.tiny", getVersion()))
+        Path clientMappings = mappingsDir.resolve(String.format("net.mojang.minecraft.mappings-%s-client.map", getVersion()))
+        Path serverMappings = mappingsDir.resolve(String.format("net.mojang.minecraft.mappings-%s-server.map", getVersion()))
+
+        if (!Files.exists(mappingsFile) || project.getGradle().getStartParameter().isRefreshDependencies()) {
+            MappingSet mappingSet
+
+            try {
+                mappingSet = getMappingsSet(clientMappings, serverMappings)
+
+                Writer writer = new StringWriter()
+                new TinyWriter(writer, "intermediary", "named").write(mappingSet)
+                Files.deleteIfExists(mappingsFile)
+
+                ZipUtil.pack([
+                        new ByteSource("mappings/mappings.tiny", writer.toString().getBytes(StandardCharsets.UTF_8))
+                ] as ZipEntrySource[], mappingsFile.toFile())
+                writer.close()
+            } catch (IOException e) {
+                throw new RuntimeException("Failed to resolve Mojang mappings", e)
+            }
+        }
+
+        return Collections.singleton(mappingsFile.toFile())
+    }
+
+    private MappingSet getMappingsSet(Path clientMappings, Path serverMappings) throws IOException {
+        MinecraftVersionInfo versionInfo = extension.getMinecraftProvider().getVersionInfo()
+
+        if (versionInfo.downloads.get("client_mappings") == null) {
+            throw new RuntimeException("Failed to find official mojang mappings for " + getVersion())
+        }
+
+        String clientMappingsUrl = versionInfo.downloads.get("client_mappings").url
+        String serverMappingsUrl = versionInfo.downloads.get("server_mappings").url
+
+        DownloadUtil.downloadIfChanged(new URL(clientMappingsUrl), clientMappings.toFile(), project.getLogger())
+        DownloadUtil.downloadIfChanged(new URL(serverMappingsUrl), serverMappings.toFile(), project.getLogger())
+
+        MappingSet mappings = MappingSet.create()
+
+        BufferedReader clientBufferedReader = Files.newBufferedReader(clientMappings, StandardCharsets.UTF_8)
+        BufferedReader serverBufferedReader = Files.newBufferedReader(serverMappings, StandardCharsets.UTF_8)
+        ProGuardReader proGuardReaderClient = new ProGuardReader(clientBufferedReader)
+        ProGuardReader proGuardReaderServer = new ProGuardReader(serverBufferedReader)
+        proGuardReaderClient.read(mappings)
+        proGuardReaderServer.read(mappings)
+
+        clientBufferedReader.close()
+        serverBufferedReader.close()
+        proGuardReaderClient.close()
+        proGuardReaderServer.close()
+
+        MappingSet officialToNamed = mappings.reverse()
+        MappingSet intermediaryToOfficial
+
+        BufferedReader reader = Files.newBufferedReader(extension.getMappingsProvider().getIntermediaryTiny(), StandardCharsets.UTF_8)
+        intermediaryToOfficial = new TinyMappingsReader(TinyMappingFactory.loadWithDetection(reader), "intermediary", "official").read()
+        reader.close()
+
+        MappingSet intermediaryToMojang = MappingSet.create()
+
+        // Merging. Don't use MappingSet#merge
+        iterateClasses(intermediaryToOfficial, { inputMappings ->
+            officialToNamed.getClassMapping(inputMappings.getFullDeobfuscatedName())
+                    .ifPresent({ namedClass ->
+                        ClassMapping mojangClassMapping = intermediaryToMojang.getOrCreateClassMapping(inputMappings.getFullObfuscatedName())
+                                .setDeobfuscatedName(namedClass.getFullDeobfuscatedName())
+
+                        for (FieldMapping fieldMapping : inputMappings.getFieldMappings()) {
+                            namedClass.getFieldMapping(fieldMapping.getDeobfuscatedName())
+                                    .ifPresent({ namedField ->
+                                        mojangClassMapping.getOrCreateFieldMapping(fieldMapping.getSignature())
+                                                .setDeobfuscatedName(namedField.getDeobfuscatedName())
+                                    })
+                        }
+
+                        for (MethodMapping methodMapping : inputMappings.getMethodMappings()) {
+                            namedClass.getMethodMapping(methodMapping.getDeobfuscatedSignature())
+                                    .ifPresent({ namedMethod ->
+                                        mojangClassMapping.getOrCreateMethodMapping(methodMapping.getSignature())
+                                                .setDeobfuscatedName(namedMethod.getDeobfuscatedName())
+                                    })
+                        }
+                    })
+        })
+
+        return intermediaryToMojang
+    }
+
+    @Override
+    Set<File> resolve(boolean transitive) {
+        return resolve()
+    }
+
+    @Override
+    TaskDependency getBuildDependencies() {
+        return { Collections.emptySet() }
+    }
+
+    @Override
+    String getGroup() {
+        return "net.mojang.minecraft"
+    }
+
+    @Override
+    String getName() {
+        return "mappings"
+    }
+
+    @Override
+    String getVersion() {
+        return extension.getMinecraftProvider().getMinecraftVersion()
+    }
+
+    @Override
+    boolean contentEquals(Dependency dependency) {
+        if (dependency instanceof MojangMappingsDependency) {
+            return ((MojangMappingsDependency) dependency).extension.getMinecraftProvider().getMinecraftVersion() == getVersion()
+        }
+
+        return false
+    }
+
+    @Override
+    Dependency copy() {
+        return new MojangMappingsDependency(project, extension)
+    }
+
+    @Override
+    String getReason() {
+        return null
+    }
+
+    @Override
+    void because(String s) {
+    }
+
+    private static void iterateClasses(MappingSet mappings, Closure<ClassMapping> consumer) {
+        for (TopLevelClassMapping classMapping : mappings.getTopLevelClassMappings()) {
+            iterateClass(classMapping, consumer)
+        }
+    }
+
+    private static void iterateClass(ClassMapping classMapping, Consumer<ClassMapping> consumer) {
+        consumer.accept(classMapping)
+
+        for (InnerClassMapping innerClassMapping : classMapping.getInnerClassMappings()) {
+            iterateClass(innerClassMapping, consumer)
+        }
+    }
+
+    private static class TinyWriter extends TextMappingsWriter {
+        private final String namespaceFrom
+        private final String namespaceTo
+
+        protected TinyWriter(Writer writer, String namespaceFrom, String namespaceTo) {
+            super(writer)
+            this.namespaceFrom = namespaceFrom
+            this.namespaceTo = namespaceTo
+        }
+
+        @Override
+        void write(MappingSet mappings) {
+            writer.println("tiny\t2\t0\t" + namespaceFrom + "\t" + namespaceTo)
+
+            iterateClasses(mappings, { classMapping ->
+                writer.println("c\t" + classMapping.getFullObfuscatedName() + "\t" + classMapping.getFullDeobfuscatedName())
+
+                for (FieldMapping fieldMapping : classMapping.getFieldMappings()) {
+                    fieldMapping.getType().ifPresent({ fieldType ->
+                        writer.println("\tf\t" + fieldType + "\t" + fieldMapping.getObfuscatedName() + "\t" + fieldMapping.getDeobfuscatedName())
+                    })
+                }
+
+                for (MethodMapping methodMapping : classMapping.getMethodMappings()) {
+                    writer.println("\tm\t" + methodMapping.getSignature().getDescriptor() + "\t" + methodMapping.getObfuscatedName() + "\t" + methodMapping.getDeobfuscatedName())
+                }
+            })
+        }
+    }
+}

+ 1 - 1
gradle.properties

@@ -1,5 +1,5 @@
 org.gradle.jvmargs=-Xmx3G
-mod_version=5.3.0
+mod_version=5.4.0
 supported_version=1.16.2
 minecraft_version=1.16.2-rc1
 yarn_version=1.16.2-rc1+build.4+legacy.20w09a+build.8