From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Mykyta Komarn Date: Thu, 1 Oct 2020 06:57:43 -0700 Subject: [PATCH] Heavily optimize recipe lookups in CraftingManager Recipe lookups are now cached in CraftingManager, which prevent unnecessary ArrayLists being created for every lookup. Additionally, an EMPTY_MAP variable was added to prevent bottlenecks during map creation, since that map is only ever iterated. These changes knock off an extra ~10ms of tick duration with a sample of ~7,700 running furnaces on a server. Co-authored-by: Ivan Pekov Co-authored-by: ishland diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java index 26fff0540e3d8863e83b0a60df2205422d50f1e1..dbfa9584c11296bf78afef52c2d0c5e682961761 100644 --- a/src/main/java/net/minecraft/world/item/ItemStack.java +++ b/src/main/java/net/minecraft/world/item/ItemStack.java @@ -640,6 +640,7 @@ public final class ItemStack { return !this.e() ? this.doMaterialsMatch(itemstack) : !itemstack.isEmpty() && this.getItem() == itemstack.getItem(); } + public final String getTranslationKey() { return j(); } // Yatopia - OBFHELPER public String j() { return this.getItem().f(this); } diff --git a/src/main/java/net/minecraft/world/item/crafting/CraftingManager.java b/src/main/java/net/minecraft/world/item/crafting/CraftingManager.java index 5ba58bf1a47c696235e6e7a4a6815104bc23de80..f1180c57a76883c007393bc187de680618f60de3 100644 --- a/src/main/java/net/minecraft/world/item/crafting/CraftingManager.java +++ b/src/main/java/net/minecraft/world/item/crafting/CraftingManager.java @@ -35,6 +35,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap; // CraftBukkit +import it.unimi.dsi.fastutil.objects.ObjectArrayList; // Yatopia public class CraftingManager extends ResourceDataJson { @@ -42,6 +43,10 @@ public class CraftingManager extends ResourceDataJson { private static final Logger LOGGER = LogManager.getLogger(); public Map, Object2ObjectLinkedOpenHashMap>> recipes = ImmutableMap.of(); // CraftBukkit private boolean d; + // Yatopia start + private final List> ALL_RECIPES_CACHE = new ObjectArrayList<>(); + private final Map, List>> TYPES_CACHE = new Object2ObjectLinkedOpenHashMap<>(); + // Yatopia end public CraftingManager() { super(CraftingManager.a, "recipes"); @@ -49,6 +54,7 @@ public class CraftingManager extends ResourceDataJson { protected void a(Map map, IResourceManager iresourcemanager, GameProfilerFiller gameprofilerfiller) { this.d = false; + clearRecipes(); // Yatopia // CraftBukkit start - SPIGOT-5667 make sure all types are populated and mutable Map, Object2ObjectLinkedOpenHashMap>> map1 = Maps.newHashMap(); for (Recipes recipeType : IRegistry.RECIPE_TYPE) { @@ -74,9 +80,17 @@ public class CraftingManager extends ResourceDataJson { } } + // Yatopia start - nuke stream & cache all recipes for constant access in b() + /* this.recipes = (Map) map1.entrySet().stream().collect(ImmutableMap.toImmutableMap(Entry::getKey, (entry1) -> { return entry1.getValue(); // CraftBukkit // Paper - decompile fix - *shrugs internally* })); + */ + this.recipes = ImmutableMap.copyOf(map1); + for (Object2ObjectLinkedOpenHashMap> recipesMap : map1.values()) { + ALL_RECIPES_CACHE.addAll(recipesMap.values()); + } + // Yatopia end CraftingManager.LOGGER.info("Loaded {} recipes", map1.size()); } @@ -90,33 +104,65 @@ public class CraftingManager extends ResourceDataJson { } else { map.putAndMoveToFirst(irecipe.getKey(), irecipe); // CraftBukkit - SPIGOT-4638: last recipe gets priority } + ALL_RECIPES_CACHE.add(irecipe); // Yatopia } // CraftBukkit end public > Optional craft(Recipes recipes, C c0, World world) { // CraftBukkit start + // Yatopia start - replace stream + /* Optional recipe = this.b(recipes).values().stream().flatMap((irecipe) -> { return SystemUtils.a(recipes.a(irecipe, world, c0)); }).findFirst(); + */ + // Yatopia start - replace stream + Collection> allTypes = this.b(recipes).values(); + Optional recipe = Optional.empty(); + + for (IRecipe possible : allTypes) { + Optional possibleRecipe = recipes.a(possible, world, c0); + if (possibleRecipe.isPresent()) { + recipe = possibleRecipe; + break; + } + } + // Yatopia end c0.setCurrentRecipe(recipe.orElse(null)); // CraftBukkit - Clear recipe when no recipe is found // CraftBukkit end return recipe; } public > List a(Recipes recipes) { + // Yatopia start - replaced logic + /* return (List) this.b(recipes).values().stream().map((irecipe) -> { return irecipe; }).collect(Collectors.toList()); + */ + return (List) TYPES_CACHE.computeIfAbsent(recipes, recipes1 -> new me.jellysquid.mods.lithium.common.util.collections.HashedList<>(new ObjectArrayList<>(getRecipesMap(recipes).values()))); + // Yatopia end } public > List b(Recipes recipes, C c0, World world) { + // Yatopia start - replace stream + /* return (List) this.b(recipes).values().stream().flatMap((irecipe) -> { return SystemUtils.a(recipes.a(irecipe, world, c0)); }).sorted(Comparator.comparing((irecipe) -> { return irecipe.getResult().j(); })).collect(Collectors.toList()); + */ + List ret = new ObjectArrayList<>(); + for (IRecipe recipe : this.b(recipes).values()) { + recipes.a(recipe, world, c0).ifPresent(ret::add); + } + ret.sort(Comparator.comparing(recipe -> recipe.getResult().getTranslationKey())); + return ret; + // Yatopia end } + private > Map> getRecipesMap(Recipes recipes) { return b(recipes); } // Yatopia - OBFHELPER private > Map> b(Recipes recipes) { return (Map) this.recipes.getOrDefault(recipes, new Object2ObjectLinkedOpenHashMap<>()); // CraftBukkit } @@ -138,15 +184,26 @@ public class CraftingManager extends ResourceDataJson { } public Optional> getRecipe(MinecraftKey minecraftkey) { + // Yatopia start - replace stream + /* return this.recipes.values().stream().map((map) -> { return map.get(minecraftkey); // CraftBukkit - decompile error }).filter(Objects::nonNull).findFirst(); + */ + for (Map> map : recipes.values()) { + IRecipe recipe = map.get(minecraftkey); + if (recipe != null) { + return Optional.of(recipe); + } + } + return Optional.empty(); + // Yatopia end } public Collection> b() { - return (Collection) this.recipes.values().stream().flatMap((map) -> { - return map.values().stream(); - }).collect(Collectors.toSet()); + // Yatopia start - O(1) constant complexity + return ALL_RECIPES_CACHE; + // Yatopia end } public Stream d() { @@ -166,6 +223,10 @@ public class CraftingManager extends ResourceDataJson { // CraftBukkit start public void clearRecipes() { this.recipes = Maps.newHashMap(); + // Yatopia start - also clear cache + ALL_RECIPES_CACHE.clear(); + TYPES_CACHE.clear(); + // Yatopia end for (Recipes recipeType : IRegistry.RECIPE_TYPE) { this.recipes.put(recipeType, new Object2ObjectLinkedOpenHashMap<>());