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. GlueList was also used as a replacement for ArrayList as it is substantially faster. 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 diff --git a/src/main/java/net/minecraft/server/CraftingManager.java b/src/main/java/net/minecraft/server/CraftingManager.java index 58ecbe1e20581dc9e78cdd2f4ece29cfa014da8a..ddd3a071dde9daeebc3d512dd8fd02594d2071fe 100644 --- a/src/main/java/net/minecraft/server/CraftingManager.java +++ b/src/main/java/net/minecraft/server/CraftingManager.java @@ -31,6 +31,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 static final List> ALL_RECIPES_CACHE = new net.yatopia.server.list.GlueList<>(); + private static final Map, List>> TYPES_CACHE = new Object2ObjectLinkedOpenHashMap<>(); + // Yatopia end public CraftingManager() { super(CraftingManager.a, "recipes"); @@ -63,9 +67,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()); } @@ -79,23 +91,44 @@ 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 net.yatopia.server.list.GlueList<>(getRecipesMap(recipes).values())); + // Yatopia end } public > List b(Recipes recipes, C c0, World world) { @@ -106,6 +139,7 @@ public class CraftingManager extends ResourceDataJson { })).collect(Collectors.toList()); } + private > Map> getRecipesMap(Recipes recipes) { return b(recipes); } // Yatopia - OBFHELPER private > Map> b(Recipes recipes) { return (Map) this.recipes.getOrDefault(recipes, new Object2ObjectLinkedOpenHashMap<>()); // CraftBukkit } @@ -127,15 +161,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() { @@ -155,6 +200,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<>());