From 4c783e228ef09079737a88546c7281d1ae818071 Mon Sep 17 00:00:00 2001 From: Ruben Taelman Date: Thu, 18 Dec 2014 20:12:53 +0100 Subject: [PATCH] Significantly improve client and server side performance. Introduces caches at busy places. --- .../evilcraft/core/algorithm/SingleCache.java | 41 ++++++++++++ .../OreDictItemStackRecipeComponent.java | 11 ++-- .../event/LivingUpdateEventHook.java | 33 +++++----- .../java/evilcraft/network/PacketCodec.java | 64 ++++++++++++++++--- .../tileentity/TileBloodInfuser.java | 49 ++++++++++---- 5 files changed, 156 insertions(+), 42 deletions(-) create mode 100644 src/main/java/evilcraft/core/algorithm/SingleCache.java diff --git a/src/main/java/evilcraft/core/algorithm/SingleCache.java b/src/main/java/evilcraft/core/algorithm/SingleCache.java new file mode 100644 index 0000000000..de465faf53 --- /dev/null +++ b/src/main/java/evilcraft/core/algorithm/SingleCache.java @@ -0,0 +1,41 @@ +package evilcraft.core.algorithm; + +/** + * A generic single object cache. + * @param The key type. + * @param The value type. + * @author rubensworks + */ +public class SingleCache { + + private boolean initialized = false; + private K key = null; + private V value = null; + private ICacheUpdater updater; + + public SingleCache(ICacheUpdater updater) { + this.updater = updater; + } + + public V get(K key) { + if(!this.initialized || !this.updater.isKeyEqual(this.key, key)) { + this.value = this.updater.getNewValue(key); + this.key = key; + this.initialized = true; + } + return this.value; + } + + /** + * This is responsible for fetching new updates when the cache desires this. + * @param The key type. + * @param The value type. + */ + public static interface ICacheUpdater { + + public V getNewValue(K key); + public boolean isKeyEqual(K cacheKey, K newKey); + + } + +} diff --git a/src/main/java/evilcraft/core/recipe/custom/OreDictItemStackRecipeComponent.java b/src/main/java/evilcraft/core/recipe/custom/OreDictItemStackRecipeComponent.java index 59e9d73c75..9077839230 100644 --- a/src/main/java/evilcraft/core/recipe/custom/OreDictItemStackRecipeComponent.java +++ b/src/main/java/evilcraft/core/recipe/custom/OreDictItemStackRecipeComponent.java @@ -31,10 +31,13 @@ public boolean equals(Object object) { if (!(object instanceof ItemStackRecipeComponent)) return false; ItemStackRecipeComponent that = (ItemStackRecipeComponent)object; - - for(ItemStack itemStack : getItemStacks()) { - if(equals(itemStack, that.getItemStack())) { - return true; + // To increase performance, first check if the comparing stack is not null before + // potentially matching it with the whole oredict. + if(that.getItemStack() != null) { + for (ItemStack itemStack : getItemStacks()) { + if (equals(itemStack, that.getItemStack())) { + return true; + } } } diff --git a/src/main/java/evilcraft/event/LivingUpdateEventHook.java b/src/main/java/evilcraft/event/LivingUpdateEventHook.java index f5d740deb2..ee64fb974f 100644 --- a/src/main/java/evilcraft/event/LivingUpdateEventHook.java +++ b/src/main/java/evilcraft/event/LivingUpdateEventHook.java @@ -1,12 +1,5 @@ package evilcraft.event; -import net.minecraft.entity.passive.EntityAnimal; -import net.minecraft.entity.passive.EntityVillager; -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.init.Blocks; -import net.minecraft.util.MathHelper; -import net.minecraft.world.World; -import net.minecraftforge.event.entity.living.LivingEvent.LivingUpdateEvent; import cpw.mods.fml.common.eventhandler.EventPriority; import cpw.mods.fml.common.eventhandler.SubscribeEvent; import evilcraft.Configs; @@ -17,6 +10,13 @@ import evilcraft.core.helper.WorldHelpers; import evilcraft.entity.monster.Werewolf; import evilcraft.entity.villager.WerewolfVillagerConfig; +import net.minecraft.entity.passive.EntityAnimal; +import net.minecraft.entity.passive.EntityVillager; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.init.Blocks; +import net.minecraft.util.MathHelper; +import net.minecraft.world.World; +import net.minecraftforge.event.entity.living.LivingEvent.LivingUpdateEvent; /** * Event hook for {@link LivingUpdateEvent}. @@ -43,18 +43,17 @@ public void onLivingUpdate(LivingUpdateEvent event) { private void dropExcrement(LivingUpdateEvent event) { if(event.entity instanceof EntityAnimal && Configs.isEnabled(ExcrementPileConfig.class) - && !event.entity.worldObj.isRemote) { + && !event.entity.worldObj.isRemote + && event.entity.worldObj.rand.nextInt(CHANCE_DROP_EXCREMENT) == 0) { EntityAnimal entity = (EntityAnimal) event.entity; World world = entity.worldObj; - if(world.rand.nextInt(CHANCE_DROP_EXCREMENT) == 0) { - int x = MathHelper.floor_double(entity.posX); - int y = MathHelper.floor_double(entity.posY); - int z = MathHelper.floor_double(entity.posZ); - if(world.getBlock(x, y, z) == Blocks.air && world.getBlock(x, y - 1, z).isNormalCube()) { - world.setBlock(x, y, z, ExcrementPile.getInstance()); - } else if (world.getBlock(x, y, z) == ExcrementPile.getInstance()) { - ExcrementPile.heightenPileAt(world, x, y, z); - } + int x = MathHelper.floor_double(entity.posX); + int y = MathHelper.floor_double(entity.posY); + int z = MathHelper.floor_double(entity.posZ); + if(world.getBlock(x, y, z) == Blocks.air && world.getBlock(x, y - 1, z).isNormalCube()) { + world.setBlock(x, y, z, ExcrementPile.getInstance()); + } else if(world.getBlock(x, y, z) == ExcrementPile.getInstance()) { + ExcrementPile.heightenPileAt(world, x, y, z); } } } diff --git a/src/main/java/evilcraft/network/PacketCodec.java b/src/main/java/evilcraft/network/PacketCodec.java index 5483453179..f36d9883e6 100644 --- a/src/main/java/evilcraft/network/PacketCodec.java +++ b/src/main/java/evilcraft/network/PacketCodec.java @@ -1,16 +1,14 @@ package evilcraft.network; -import java.lang.reflect.Field; -import java.util.Arrays; -import java.util.Comparator; -import java.util.Map; -import java.util.Set; - -import org.apache.commons.lang3.ClassUtils; - +import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.io.ByteArrayDataInput; import com.google.common.io.ByteArrayDataOutput; +import evilcraft.core.algorithm.SingleCache; +import org.apache.commons.lang3.ClassUtils; + +import java.lang.reflect.Field; +import java.util.*; /** * Packet with automatic coding and decoding of basic fields annotated with {@link CodecField}. @@ -142,6 +140,42 @@ public Object decode(ByteArrayDataInput input) { } }); } + + private SingleCache> fieldCache = new SingleCache>( + new SingleCache.ICacheUpdater>() { + + @Override + public List getNewValue(Void key) { + Field[] fields = PacketCodec.this.getClass().getDeclaredFields(); + + // Sort this because the Java API tells us that getDeclaredFields() + // does not deterministically define the order of the fields in the array. + // Otherwise we might get nasty class cast exceptions when running in SMP. + Arrays.sort(fields, new Comparator() { + + @Override + public int compare(Field o1, Field o2) { + return o1.getName().compareTo(o2.getName()); + } + + }); + + List fieldList = Lists.newLinkedList(); + for(final Field field : fields) { + if(field.isAnnotationPresent(CodecField.class)) { + fieldList.add(field); + } + } + + return fieldList; + } + + @Override + public boolean isKeyEqual(Void cacheKey, Void newKey) { + return true; + } + + }); protected static ICodecAction getAction(Class clazz) { if(ClassUtils.isPrimitiveWrapper(clazz)) { @@ -156,7 +190,7 @@ protected static ICodecAction getAction(Class clazz) { } private void loopCodecFields(ICodecRunnable runnable) { - Field[] fields = this.getClass().getDeclaredFields(); + /*Field[] fields = this.getClass().getDeclaredFields(); // Sort this because the Java API tells us that getDeclaredFields() // does not deterministically define the order of the fields in the array. @@ -181,7 +215,17 @@ public int compare(Field o1, Field o2) { runnable.run(field, action); field.setAccessible(accessible); } - } + }*/ + for(Field field : fieldCache.get(null)) { + Class clazz = field.getType(); + ICodecAction action = getAction(clazz); + + // Make private fields temporarily accessible. + boolean accessible = field.isAccessible(); + field.setAccessible(true); + runnable.run(field, action); + field.setAccessible(accessible); + } } @Override diff --git a/src/main/java/evilcraft/tileentity/TileBloodInfuser.java b/src/main/java/evilcraft/tileentity/TileBloodInfuser.java index e48ad7e6a1..56157e52c6 100644 --- a/src/main/java/evilcraft/tileentity/TileBloodInfuser.java +++ b/src/main/java/evilcraft/tileentity/TileBloodInfuser.java @@ -2,6 +2,7 @@ import evilcraft.api.recipes.custom.IRecipe; import evilcraft.block.BloodInfuser; +import evilcraft.core.algorithm.SingleCache; import evilcraft.core.fluid.BloodFluidConverter; import evilcraft.core.fluid.ImplicitFluidConversionTank; import evilcraft.core.fluid.SingleUseTank; @@ -30,6 +31,8 @@ import net.minecraftforge.fluids.FluidStack; import net.minecraftforge.fluids.IFluidContainerItem; import org.apache.commons.lang3.mutable.MutableInt; +import org.apache.commons.lang3.tuple.ImmutableTriple; +import org.apache.commons.lang3.tuple.Triple; import java.util.LinkedHashMap; import java.util.LinkedList; @@ -78,6 +81,8 @@ public class TileBloodInfuser extends TileWorking public static final Fluid ACCEPTED_FLUID = Blood.getInstance(); private int infuseTicker; + private SingleCache, + IRecipe> recipeCache; private static final Map, ITickAction> INFUSE_TICK_ACTIONS = new LinkedHashMap, ITickAction>(); static { @@ -159,6 +164,35 @@ public void applyUpgrade(TileBloodInfuser upgradable, Upgrades.Upgrade upgrade, } } }); + + // Efficient cache to retrieve the current craftable recipe. + recipeCache = new SingleCache, + IRecipe>( + new SingleCache.ICacheUpdater, + IRecipe>() { + @Override + public IRecipe getNewValue(Triple key) { + ItemFluidStackAndTierRecipeComponent recipeInput = new ItemFluidStackAndTierRecipeComponent(key.getLeft(), + key.getMiddle(), -1); + IRecipe maxRecipe = null; + int maxRecipeTier = -1; + for(IRecipe recipe : + BloodInfuser.getInstance().getRecipeRegistry().findRecipesByInput(recipeInput)) { + if(recipe.getInput().getTier() > maxRecipeTier && key.getRight() >= recipe.getInput().getTier()) { + maxRecipe = recipe; + } + } + return maxRecipe; + } + + @Override + public boolean isKeyEqual(Triple cacheKey, Triple newKey) { + return cacheKey == null || newKey == null || + (ItemStack.areItemStacksEqual(cacheKey.getLeft(), newKey.getLeft()) && + FluidStack.areFluidStackTagsEqual(cacheKey.getMiddle(), newKey.getMiddle()) && + cacheKey.getRight().equals(newKey.getRight())); + } + }); } @Override @@ -173,17 +207,10 @@ protected SingleUseTank newTank(String tankName, int tankSize) { */ public IRecipe getRecipe(ItemStack itemStack) { - ItemFluidStackAndTierRecipeComponent recipeInput = new ItemFluidStackAndTierRecipeComponent(itemStack, - getTank().getFluid(), -1); - IRecipe maxRecipe = null; - int maxRecipeTier = -1; - for(IRecipe recipe : - BloodInfuser.getInstance().getRecipeRegistry().findRecipesByInput(recipeInput)) { - if(recipe.getInput().getTier() > maxRecipeTier && getTier() >= recipe.getInput().getTier()) { - maxRecipe = recipe; - } - } - return maxRecipe; + return recipeCache.get(new ImmutableTriple( + itemStack == null ? null : itemStack.copy(), + getTank().getFluid() == null ? null : getTank().getFluid().copy(), + getTier())); } @Override