diff --git a/src/main/java/com/pengu/thaumcraft/additions/tileentity/TileTankBasic.java b/src/main/java/com/pengu/thaumcraft/additions/tileentity/TileTankBasic.java index 6855701..c1a5fd5 100644 --- a/src/main/java/com/pengu/thaumcraft/additions/tileentity/TileTankBasic.java +++ b/src/main/java/com/pengu/thaumcraft/additions/tileentity/TileTankBasic.java @@ -46,14 +46,17 @@ public class TileTankBasic extends TileThaumcraft implements ITubable { } } - boolean var4 = this.worldObj.getTileEntity(this.xCoord, this.yCoord + 1, this.zCoord) instanceof TileTankBasic && this.vis + this.taint < this.capacity; + // each neighbour tile is looked up once; nothing between here and the uses changes the blocks around + net.minecraft.tileentity.TileEntity aboveTile = this.worldObj.getTileEntity(this.xCoord, this.yCoord + 1, this.zCoord); + boolean var4 = aboveTile instanceof TileTankBasic && this.vis + this.taint < this.capacity; if(var4 && this.taint + this.vis > this.capacity - 5) { MagicType var5 = Math.max(this.taint, this.vis) == this.vis?MagicType.VIS:MagicType.TAINT; - this.ejectVis(var5, ((ITubable)this.worldObj.getTileEntity(this.xCoord, this.yCoord + 1, this.zCoord)).acceptVis(var5, var5 == MagicType.VIS?this.vis:this.taint, ForgeDirection.DOWN), ForgeDirection.UP); + this.ejectVis(var5, ((ITubable)aboveTile).acceptVis(var5, var5 == MagicType.VIS?this.vis:this.taint, ForgeDirection.DOWN), ForgeDirection.UP); } - if(this.worldObj.getTileEntity(this.xCoord, this.yCoord - 1, this.zCoord) instanceof TileTankBasic) { - TileTankBasic var6 = (TileTankBasic)this.worldObj.getTileEntity(this.xCoord, this.yCoord - 1, this.zCoord); + net.minecraft.tileentity.TileEntity belowTile = this.worldObj.getTileEntity(this.xCoord, this.yCoord - 1, this.zCoord); + if(belowTile instanceof TileTankBasic) { + TileTankBasic var6 = (TileTankBasic)belowTile; if(var6.vis + var6.taint < var6.capacity - 5) { for(int i = 5; i > 0; --i) { if(this.vis >= i && var6.vis + var6.taint + i <= this.capacity - 5) { diff --git a/src/test/java/com/pengu/thaumcraft/additions/tileentity/TileTankBasicLookupTest.java b/src/test/java/com/pengu/thaumcraft/additions/tileentity/TileTankBasicLookupTest.java new file mode 100644 index 0000000..9c4d40d --- /dev/null +++ b/src/test/java/com/pengu/thaumcraft/additions/tileentity/TileTankBasicLookupTest.java @@ -0,0 +1,153 @@ +package com.pengu.thaumcraft.additions.tileentity; + +import static org.junit.Assert.assertEquals; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.Map; +import java.util.Random; +import net.minecraft.block.Block; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.world.World; +import net.minecraft.world.WorldProviderSurface; +import net.minecraft.world.WorldServer; +import org.junit.Test; + +/** TDLM/thaumicadditions#20: a tank looks each neighbour tile up once per tick and moves vis/taint exactly as before. */ +public class TileTankBasicLookupTest { + + @SuppressWarnings("unchecked") + private static T allocate(Class type) throws Exception { + Constructor ctor = sun.reflect.ReflectionFactory.getReflectionFactory() + .newConstructorForSerialization(type, Object.class.getDeclaredConstructor()); + return (T) ctor.newInstance(); + } + + public static class CountingWorld extends WorldServer { + + Map tiles; + int tileLookups; + int blockLookups; + + CountingWorld() { + super(null, null, "tanks", 0, null, null); + } + + static long key(int x, int y, int z) { + return ((long) x << 40) ^ ((long) y << 20) ^ z; + } + + @Override + public TileEntity getTileEntity(int x, int y, int z) { + this.tileLookups++; + return this.tiles.get(key(x, y, z)); + } + + @Override + public Block getBlock(int x, int y, int z) { + this.blockLookups++; + return null; // equals Blocks.air, which is unset without a Minecraft bootstrap + } + + @Override + public int getBlockMetadata(int x, int y, int z) { + this.blockLookups++; + return 0; + } + } + + private static CountingWorld world() throws Exception { + CountingWorld world = allocate(CountingWorld.class); + world.rand = new Random(1); + Field provider = World.class.getDeclaredField("provider"); + provider.setAccessible(true); + provider.set(world, new WorldProviderSurface()); + world.isRemote = true; // no taint explosion branch + world.tiles = new HashMap(); + return world; + } + + private static TileTankBasic tank(CountingWorld world, int y, int vis, int taint) throws Exception { + TileTankBasic tank = allocate(TileTankBasic.class); + tank.setWorldObj(world); + tank.xCoord = 3; + tank.yCoord = y; + tank.zCoord = 5; + tank.capacity = 1005; + tank.vis = vis; + tank.taint = taint; + world.tiles.put(CountingWorld.key(3, y, 5), tank); + return tank; + } + + /** The lookups of the old method, in order, on the same fields. */ + private static void referenceUpdate(TileTankBasic t) { + World w = t.getWorldObj(); + w.getBlock(t.xCoord, t.yCoord + 1, t.zCoord); + w.getBlock(t.xCoord, t.yCoord - 1, t.zCoord); + w.getBlockMetadata(t.xCoord, t.yCoord, t.zCoord); + boolean var4 = w.getTileEntity(t.xCoord, t.yCoord + 1, t.zCoord) instanceof TileTankBasic && t.vis + t.taint < t.capacity; + if (var4 && t.taint + t.vis > t.capacity - 5) { + TileTankBasic above = (TileTankBasic) w.getTileEntity(t.xCoord, t.yCoord + 1, t.zCoord); + int type = Math.max(t.taint, t.vis) == t.vis ? 0 : 1; + int q = above.acceptVis(type == 0 ? com.pengu.api.thaumicadditions.tubesyst.ITubable.MagicType.VIS : com.pengu.api.thaumicadditions.tubesyst.ITubable.MagicType.TAINT, + type == 0 ? t.vis : t.taint, net.minecraftforge.common.util.ForgeDirection.DOWN); + t.ejectVis(type == 0 ? com.pengu.api.thaumicadditions.tubesyst.ITubable.MagicType.VIS : com.pengu.api.thaumicadditions.tubesyst.ITubable.MagicType.TAINT, q, + net.minecraftforge.common.util.ForgeDirection.UP); + } + if (w.getTileEntity(t.xCoord, t.yCoord - 1, t.zCoord) instanceof TileTankBasic) { + TileTankBasic var6 = (TileTankBasic) w.getTileEntity(t.xCoord, t.yCoord - 1, t.zCoord); + if (var6.vis + var6.taint < var6.capacity - 5) { + for (int i = 5; i > 0; --i) { + if (t.vis >= i && var6.vis + var6.taint + i <= t.capacity - 5) { + var6.vis += i; + t.vis -= i; + } + if (t.taint >= i && var6.vis + var6.taint + i <= t.capacity - 5) { + var6.taint += i; + t.taint -= i; + } + } + } + } + } + + private static int[] state(TileTankBasic... tanks) { + int[] out = new int[tanks.length * 2]; + for (int i = 0; i < tanks.length; i++) { + out[2 * i] = tanks[i].vis; + out[2 * i + 1] = tanks[i].taint; + } + return out; + } + + @Test + public void aStackOfTanksEndsInTheSameStateWithFewerLookups() throws Exception { + Random rnd = new Random(9); + for (int round = 0; round < 40; round++) { + int[] seedValues = new int[6]; + for (int i = 0; i < 6; i++) { + seedValues[i] = rnd.nextInt(1000); + } + CountingWorld oldWorld = world(); + CountingWorld newWorld = world(); + TileTankBasic[] oldTanks = new TileTankBasic[3]; + TileTankBasic[] newTanks = new TileTankBasic[3]; + for (int i = 0; i < 3; i++) { + oldTanks[i] = tank(oldWorld, 64 + i, seedValues[2 * i], seedValues[2 * i + 1] % 500); + newTanks[i] = tank(newWorld, 64 + i, seedValues[2 * i], seedValues[2 * i + 1] % 500); + } + for (int tick = 0; tick < 30; tick++) { + for (int i = 0; i < 3; i++) { + referenceUpdate(oldTanks[i]); + newTanks[i].updateEntity(); + } + org.junit.Assert.assertArrayEquals("round " + round + " tick " + tick, state(oldTanks), state(newTanks)); + } + org.junit.Assert.assertTrue("the old method looks up at least as often", oldWorld.tileLookups >= newWorld.tileLookups); + assertEquals("new: two tile lookups per tank per tick", 2 * 3 * 30, newWorld.tileLookups); + assertEquals(oldWorld.blockLookups, newWorld.blockLookups); + } + } +}