diff --git a/src/main/java/net/minecraft/inventory/InventoryCrafting.java b/src/main/java/net/minecraft/inventory/InventoryCrafting.java index f076459..238463e 100644 --- a/src/main/java/net/minecraft/inventory/InventoryCrafting.java +++ b/src/main/java/net/minecraft/inventory/InventoryCrafting.java @@ -75,7 +75,8 @@ { itemstack = this.stackList[p_70298_1_]; this.stackList[p_70298_1_] = null; - this.eventHandler.onCraftMatrixChanged(this); + if(callMatrixChanged) + this.eventHandler.onCraftMatrixChanged(this); return itemstack; } else @@ -87,7 +88,8 @@ this.stackList[p_70298_1_] = null; } - this.eventHandler.onCraftMatrixChanged(this); + if(callMatrixChanged) + this.eventHandler.onCraftMatrixChanged(this); return itemstack; } } @@ -100,7 +102,8 @@ public void setInventorySlotContents(int p_70299_1_, ItemStack p_70299_2_) { this.stackList[p_70299_1_] = p_70299_2_; - this.eventHandler.onCraftMatrixChanged(this); + if(callMatrixChanged) + this.eventHandler.onCraftMatrixChanged(this); } public int getInventoryStackLimit() @@ -123,4 +126,11 @@ { return true; } + + public int getWidth() + { + return inventoryWidth; + } + + public static boolean callMatrixChanged = true; } \ No newline at end of file diff --git a/src/main/java/net/minecraft/inventory/SlotCrafting.java b/src/main/java/net/minecraft/inventory/SlotCrafting.java index 5a327d1..dba84d7 100644 --- a/src/main/java/net/minecraft/inventory/SlotCrafting.java +++ b/src/main/java/net/minecraft/inventory/SlotCrafting.java @@ -109,6 +109,7 @@ FMLCommonHandler.instance().firePlayerCraftingEvent(p_82870_1_, p_82870_2_, craftMatrix); this.onCrafting(p_82870_2_); + InventoryCrafting.callMatrixChanged = false; for (int i = 0; i < this.craftMatrix.getSizeInventory(); ++i) { ItemStack itemstack1 = this.craftMatrix.getStackInSlot(i); @@ -141,5 +142,7 @@ } } } + InventoryCrafting.callMatrixChanged = true; + craftMatrix.setInventorySlotContents(0, craftMatrix.getStackInSlot(0)); } } \ No newline at end of file diff --git a/src/main/java/net/minecraft/item/crafting/CraftingManager.java b/src/main/java/net/minecraft/item/crafting/CraftingManager.java index a0d112a..961989d 100644 --- a/src/main/java/net/minecraft/item/crafting/CraftingManager.java +++ b/src/main/java/net/minecraft/item/crafting/CraftingManager.java @@ -16,7 +16,7 @@ public class CraftingManager { private static final CraftingManager instance = new CraftingManager(); - private List recipes = new ArrayList(); + public List recipes = new ArrayList(); private static final String __OBFID = "CL_00000090"; public static final CraftingManager getInstance() diff --git a/src/main/java/org/ultramine/commands/basic/TechCommands.java b/src/main/java/org/ultramine/commands/basic/TechCommands.java index 1c0cfc4..e3b6839 100644 --- a/src/main/java/org/ultramine/commands/basic/TechCommands.java +++ b/src/main/java/org/ultramine/commands/basic/TechCommands.java @@ -30,6 +30,7 @@ import org.ultramine.server.MultiWorld; import org.ultramine.server.Restarter; import org.ultramine.server.Teleporter; +import org.ultramine.server.UltramineServerModContainer; import org.ultramine.server.BackupManager.BackupDescriptor; import org.ultramine.server.WorldsConfig.WorldConfig.Border; import org.ultramine.server.chunk.ChunkProfiler; @@ -711,4 +712,15 @@ ctx.getServer().initiateShutdown(); } } + + @Command( + name = "recipecache", + group = "technical", + permissions = {"command.recipecache"}, + syntax = {"[clear]"} + ) + public static void recipecache(CommandContext ctx) + { + UltramineServerModContainer.getInstance().getRecipeCache().clearCache(); + } } diff --git a/src/main/java/org/ultramine/server/RecipeCache.java b/src/main/java/org/ultramine/server/RecipeCache.java new file mode 100644 index 0000000..65cff56 --- /dev/null +++ b/src/main/java/org/ultramine/server/RecipeCache.java @@ -0,0 +1,313 @@ +package org.ultramine.server; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import net.minecraft.inventory.InventoryCrafting; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.item.crafting.CraftingManager; +import net.minecraft.item.crafting.IRecipe; +import net.minecraft.world.World; + +public class RecipeCache +{ + private final List originList; + private final CachedRecipe cachedRecipe = new CachedRecipe(); + private final Map cache = new HashMap(); + private final Set noRecipeSet = new HashSet(); + + @SuppressWarnings("unchecked") + public RecipeCache() + { + originList = CraftingManager.getInstance().getRecipeList(); + CraftingManager.getInstance().recipes = newRecipeList; + } + + public IRecipe findRecipe(InventoryCrafting inv, World world) + { + RecipeKey key = new RecipeKeyBuilder(inv).build(); + if(key.width == 0) + return null; + + IRecipe rcp = cache.get(key); + if (rcp != null && rcp.matches(inv, world)) + { + return rcp; + } + else if(noRecipeSet.contains(key)) + { + return null; + } + else + { + for(IRecipe recipe : originList) + { + if (recipe.matches(inv, world)) + { + addToCache(key, recipe); + return recipe; + } + } + } + + if(noRecipeSet.size() >= 1048576) + noRecipeSet.clear(); + noRecipeSet.add(key); + return null; + } + + private void addToCache(RecipeKey key, IRecipe recipe) + { + if(cache.size() >= 1048576) + cache.clear(); + cache.put(key, recipe); + } + + public void clearCache() + { + cache.clear(); + noRecipeSet.clear(); + } + + private List newRecipeList = new ArrayList() + { + private static final long serialVersionUID = 1L; + + { + super.add(cachedRecipe); + } + + @Override + public boolean addAll(Collection add) + { + for(IRecipe recipe : add) + originList.add(recipe); + noRecipeSet.clear(); + return true; + } + + @Override + public boolean removeAll(Collection rem) + { + throw new UnsupportedOperationException(); + } + + @Override + public IRecipe remove(int ind) + { + return cachedRecipe.removeLastFound(); + } + + @Override + public boolean remove(Object recipe) + { + if(recipe == this) + { + return cachedRecipe.removeLastFound() != null; + } + else + { + boolean rem = originList.remove(recipe); + if(rem) + cache.clear(); + return rem; + } + } + + @Override + public boolean add(IRecipe recipe) + { + originList.add(recipe); + noRecipeSet.clear(); + return true; + } + }; + + private class CachedRecipe implements IRecipe + { + private IRecipe lastFound; + + @Override + public boolean matches(InventoryCrafting inv, World world) + { + lastFound = findRecipe(inv, world); + return lastFound != null; + } + + @Override + public ItemStack getCraftingResult(InventoryCrafting inv) + { + return lastFound != null ? lastFound.getCraftingResult(inv) : null; + } + + @Override + public int getRecipeSize() + { + return 9; + } + + @Override + public ItemStack getRecipeOutput() + { + return null; + } + + public IRecipe removeLastFound() + { + if(lastFound != null) + { + originList.remove(lastFound); + cache.clear(); + } + + return lastFound; + } + }; + + private static class RecipeKey implements Comparable + { + private final int[] contents; + private final int width; + + public RecipeKey(int[] contents, int width) + { + this.contents = contents; + this.width = width; + } + + public boolean equals(Object o) + { + if(!(o instanceof RecipeKey)) + return false; + RecipeKey rk = (RecipeKey)o; + return width == rk.width && Arrays.equals(contents, rk.contents); + } + + public int hashCode() + { + int hash = 0; + for(int i = 0; i < contents.length; i++) + hash ^= contents[i]; + return hash; + } + + public int compareTo(RecipeKey rk) + { + int c1 = width - rk.width; + if(c1 != 0) + return c1; + int c2 = contents.length - rk.contents.length; + if(c2 != 0) + return c2; + for(int i = 0; i < contents.length; i++) + { + int c3 = Integer.compare(contents[i], rk.contents[i]); + if(c3 != 0) + return c3; + } + + return 0; + } + } + + private static class RecipeKeyBuilder + { + private int[] contents; + private int x; + private int y; + private int width; + private int height; + private int newWidth; + private int newHeight; + + public RecipeKeyBuilder(InventoryCrafting inv) + { + contents = new int[inv.getSizeInventory()]; + for(int i = 0; i < contents.length; i++) + { + ItemStack is = inv.getStackInSlot(i); + if(is != null) + contents[i] = (Item.getIdFromItem(is.getItem()) << 16) | (is.getItemDamage() & 0xFFFF); + } + newWidth = width = inv.getWidth(); + newHeight = height = contents.length/width; + + while(trimHorisontal(false)); + if(y == height) + { + contents = new int[0]; + newWidth = 0; + } + else + { + while(trimHorisontal(true)); + while(trimVertical(false)); + while(trimVertical(true)); + if(width != newWidth || height != newHeight) + { + int[] newContents = new int[newWidth*newHeight]; + for(int i = 0; i < newWidth; i++) + for(int j = 0; j < newHeight; j++) + newContents[i + j*newWidth] = contents[(x+i) + (y+j)*width]; + contents = newContents; + } + } + } + + private boolean trimHorisontal(boolean bottom) + { + boolean empty = true; + for(int i = 0; i < width; i++) + { + if(contents[bottom ? (y+newHeight-1)*width + i : y*width + i] != 0) + { + empty = false; + break; + } + } + if(empty) + { + newHeight--; + if(!bottom) + y++; + return newHeight != 0; + } + + return false; + } + + private boolean trimVertical(boolean right) + { + boolean empty = true; + for(int i = 0; i < newHeight; i++) + { + if(contents[y*width + i*(width) + x + (right ? newWidth-1 : 0)] != 0) + { + empty = false; + break; + } + } + if(empty) + { + newWidth--; + if(!right) + x++; + return true; + } + + return false; + } + + public RecipeKey build() + { + return new RecipeKey(contents, newWidth); + } + } +} diff --git a/src/main/java/org/ultramine/server/UltramineServerModContainer.java b/src/main/java/org/ultramine/server/UltramineServerModContainer.java index 3da731f..f0f462c 100644 --- a/src/main/java/org/ultramine/server/UltramineServerModContainer.java +++ b/src/main/java/org/ultramine/server/UltramineServerModContainer.java @@ -48,17 +48,26 @@ public class UltramineServerModContainer extends DummyModContainer { + private static UltramineServerModContainer instance; + @SideOnly(Side.SERVER) private ButtonCommand buttonCommand; + private RecipeCache recipeCache; public UltramineServerModContainer() { super(new ModMetadata()); + instance = this; ModMetadata meta = getMetadata(); meta.modId = "UltramineServer"; meta.name = "Ultramine Server"; meta.version = "1.0"; } + + public static UltramineServerModContainer getInstance() + { + return instance; + } @Override public boolean registerBus(EventBus bus, LoadController controller) @@ -134,6 +143,7 @@ loader.addDefaultWarps(); for(String name : loader.getFastWarps()) reg.registerCommand(new FastWarpCommand(name)); + recipeCache = new RecipeCache(); } @Subscribe @@ -178,4 +188,9 @@ { return this; } + + public RecipeCache getRecipeCache() + { + return recipeCache; + } } diff --git a/src/main/resources/assets/ultramine/lang/en_US.lang b/src/main/resources/assets/ultramine/lang/en_US.lang index f001ccb..848d3dd 100644 --- a/src/main/resources/assets/ultramine/lang/en_US.lang +++ b/src/main/resources/assets/ultramine/lang/en_US.lang @@ -254,3 +254,6 @@ command.backup.apply.fail.zip.unpack=Failed to unpack zip file. It is very VERY bad. May lead to the complete breaking of the world. Please, apply other backup. command.backup.apply.success.temp=Backup successfuly applied! Created temp worlds: command.backup.apply.success=Backup successfuly applied! + +command.custmsg.usage=/recipecache clear +command.custmsg.description=Clears crafting recipe cache diff --git a/src/main/resources/assets/ultramine/lang/ru_RU.lang b/src/main/resources/assets/ultramine/lang/ru_RU.lang index d0365f0..9fe6360 100644 --- a/src/main/resources/assets/ultramine/lang/ru_RU.lang +++ b/src/main/resources/assets/ultramine/lang/ru_RU.lang @@ -254,3 +254,6 @@ command.backup.apply.fail.zip.unpack=Не удалось распаковать zip файл. это очень ОЧЕНЬ полохо. Может привести к полному разрушению миров. Пожалуйста, применить другой бэкап. command.backup.apply.success.temp=Бэкап успешно применен! Созданы временные миры: command.backup.apply.success=Бэкап успешно применен! + +command.custmsg.usage=/recipecache clear +command.custmsg.description=Очищает кэш рецептов крафта