From 12c0abbac656c6ed7275ef89a71f43236044cee0 Mon Sep 17 00:00:00 2001 From: AlgorithmX2 Date: Sun, 22 Jun 2014 23:50:56 -0500 Subject: [PATCH] Crafting calculations are now synchronized with the world thread to prevent possible CME's --- crafting/CraftingJob.java | 205 ++++++++++++++++++++++++------ crafting/CraftingTreeNode.java | 14 +- crafting/CraftingTreeProcess.java | 13 +- hooks/TickHandler.java | 37 +++++- 4 files changed, 221 insertions(+), 48 deletions(-) diff --git a/crafting/CraftingJob.java b/crafting/CraftingJob.java index 2f503094..86d2a462 100644 --- a/crafting/CraftingJob.java +++ b/crafting/CraftingJob.java @@ -14,6 +14,7 @@ import appeng.api.networking.storage.IStorageGrid; import appeng.api.storage.data.IAEItemStack; import appeng.api.storage.data.IItemList; import appeng.core.AELog; +import appeng.hooks.TickHandler; import appeng.me.cache.CraftingCache; import appeng.util.Platform; @@ -36,6 +37,7 @@ public class CraftingJob implements Runnable public CraftingTreeNode tree; private BaseActionSource actionSrc; + long bytes = 0; World world; public IAEItemStack getOutput() @@ -75,14 +77,6 @@ public class CraftingJob implements Runnable private World wrapWorld(World w) { return w; - /* - * -- works on interfaces.. :( try { - * - * InvocationHandler handler = new CraftingWorldSync( w ); Class proxyClass = Proxy.getProxyClass( - * World.class.getClassLoader(), new Class[] { World.class } ); return (World) proxyClass.getConstructor( new - * Class[] { InvocationHandler.class } ).newInstance( new Object[] { handler } ); } catch (Throwable t) { throw - * new RuntimeException( t ); } - */ } private CraftingTreeNode getCraftingTree(CraftingCache cc, IAEItemStack what) @@ -90,6 +84,11 @@ public class CraftingJob implements Runnable return new CraftingTreeNode( cc, this, what, null, -1, 0 ); } + public long getByteTotal() + { + return bytes; + } + public void writeToNBT(NBTTagCompound out) { @@ -139,38 +138,18 @@ public class CraftingJob implements Runnable @Override public void run() { - try { - Stopwatch timer = Stopwatch.createStarted(); - - MECraftingInventory meci = new MECraftingInventory( original, true, false, true ); - meci.ignore( output ); - - tree.request( meci, output.getStackSize(), actionSrc ); - tree.dive( this ); - - for (String s : opsAndMultiplier.keySet()) - { - twoIntegers ti = opsAndMultiplier.get( s ); - AELog.info( s + " * " + ti.times + " = " + (ti.perOp * ti.times) ); - } - - AELog.info( "------------- real" + timer.elapsed( TimeUnit.MILLISECONDS ) + "ms" ); - // if ( mode == Actionable.MODULATE ) - // meci.moveItemsToStorage( storage ); - } - catch (CraftBranchFailure e) - { - simulate = true; - try { + TickHandler.instance.registerCraftingSimulation( world, this ); + handlepausing(); + Stopwatch timer = Stopwatch.createStarted(); + MECraftingInventory meci = new MECraftingInventory( original, true, false, true ); meci.ignore( output ); - tree.setSimulate(); tree.request( meci, output.getStackSize(), actionSrc ); tree.dive( this ); @@ -180,11 +159,46 @@ public class CraftingJob implements Runnable AELog.info( s + " * " + ti.times + " = " + (ti.perOp * ti.times) ); } - AELog.info( "------------- simulate" + timer.elapsed( TimeUnit.MILLISECONDS ) + "ms" ); + AELog.info( "------------- " + getByteTotal() + "b real" + timer.elapsed( TimeUnit.MILLISECONDS ) + "ms" ); + // if ( mode == Actionable.MODULATE ) + // meci.moveItemsToStorage( storage ); } - catch (CraftBranchFailure e1) + catch (CraftBranchFailure e) { - AELog.error( e1 ); + simulate = true; + + try + { + Stopwatch timer = Stopwatch.createStarted(); + MECraftingInventory meci = new MECraftingInventory( original, true, false, true ); + meci.ignore( output ); + + tree.setSimulate(); + tree.request( meci, output.getStackSize(), actionSrc ); + tree.dive( this ); + + for (String s : opsAndMultiplier.keySet()) + { + twoIntegers ti = opsAndMultiplier.get( s ); + AELog.info( s + " * " + ti.times + " = " + (ti.perOp * ti.times) ); + } + + AELog.info( "------------- " + getByteTotal() + "b simulate" + timer.elapsed( TimeUnit.MILLISECONDS ) + "ms" ); + } + catch (CraftBranchFailure e1) + { + AELog.error( e1 ); + } + catch (CraftingCalculationFailure f) + { + AELog.error( f ); + } + catch (InterruptedException e1) + { + AELog.info( "Crafting calculation canceled." ); + finish(); + return; + } } catch (CraftingCalculationFailure f) { @@ -193,19 +207,30 @@ public class CraftingJob implements Runnable catch (InterruptedException e1) { AELog.info( "Crafting calculation canceled." ); + finish(); return; } + + log( "crafting job now done" ); } - catch (CraftingCalculationFailure f) + catch (Throwable t) { - AELog.error( f ); - } - catch (InterruptedException e1) - { - AELog.info( "Crafting calculation canceled." ); - return; + finish(); + throw new RuntimeException( t ); } + finish(); + + } + + public void finish() + { + synchronized (monitor) + { + running = false; + done = true; + monitor.notify(); + } } public boolean isSimulation() @@ -213,9 +238,105 @@ public class CraftingJob implements Runnable return simulate; } + public boolean isDone() + { + return done; + } + public World getWorld() { return world; } + private boolean running = false; + private boolean done = false; + private Object monitor = new Object(); + private Stopwatch watch = Stopwatch.createUnstarted(); + private int time = 5; + + /** + * returns true if this needs more simulation. + * + * @param milli + * @return + */ + public boolean simulateFor(int milli) + { + time = milli; + + synchronized (monitor) + { + if ( isDone() ) + return false; + + watch.reset(); + watch.start(); + running = true; + + log( "main thread is now going to sleep" ); + + monitor.notify(); + + while (running) + { + try + { + monitor.wait(); + } + catch (InterruptedException e) + { + } + } + + log( "main thread is now active" ); + } + + return true; + } + + private int incTime = Integer.MAX_VALUE; + + public void handlepausing() throws InterruptedException + { + if ( incTime++ > 100 ) + { + incTime = 0; + + synchronized (monitor) + { + if ( watch.elapsed( TimeUnit.MICROSECONDS ) > time ) + { + running = false; + watch.stop(); + monitor.notify(); + } + + if ( !running ) + { + log( "crafting job will now sleep" ); + + while (!running) + { + monitor.wait(); + } + + log( "crafting job now active" ); + } + } + + if ( Thread.interrupted() ) + throw new InterruptedException(); + } + } + + private void log(String string) + { + // AELog.info( string ); + } + + public void addBytes(long crafts) + { + bytes += crafts; + } + } diff --git a/crafting/CraftingTreeNode.java b/crafting/CraftingTreeNode.java index 3b4861a5..668e48bd 100644 --- a/crafting/CraftingTreeNode.java +++ b/crafting/CraftingTreeNode.java @@ -22,6 +22,7 @@ public class CraftingTreeNode // what slot! int slot; + int bytes = 0; // what item is this? private IAEItemStack what; @@ -75,8 +76,7 @@ public class CraftingTreeNode public IAEItemStack request(MECraftingInventory inv, long l, BaseActionSource src) throws CraftBranchFailure, InterruptedException { - if ( Thread.interrupted() ) - throw new InterruptedException(); + job.handlepausing(); what.setStackSize( l ); if ( slot >= 0 && parent != null && parent.details.isCraftable() ) @@ -93,6 +93,8 @@ public class CraftingTreeNode { if ( !exhausted ) used.add( job.checkUse( available ) ); + + bytes += available.getStackSize(); l -= available.getStackSize(); if ( l == 0 ) @@ -109,6 +111,8 @@ public class CraftingTreeNode { if ( !exhausted ) used.add( job.checkUse( available ) ); + + bytes += available.getStackSize(); l -= available.getStackSize(); if ( l == 0 ) @@ -131,6 +135,7 @@ public class CraftingTreeNode if ( available != null ) { + bytes += available.getStackSize(); l -= available.getStackSize(); if ( l <= 0 ) @@ -157,6 +162,7 @@ public class CraftingTreeNode if ( available != null ) { + bytes += available.getStackSize(); l -= available.getStackSize(); if ( l <= 0 ) @@ -176,6 +182,7 @@ public class CraftingTreeNode if ( sim ) { missing += l; + bytes += l; return what; } @@ -188,6 +195,8 @@ public class CraftingTreeNode job.addMissing( getStack( missing ) ); missing = 0; + job.addBytes( 8 + bytes ); + for (CraftingTreeProcess pro : nodes) pro.dive( job ); } @@ -196,6 +205,7 @@ public class CraftingTreeNode { sim = true; missing = 0; + bytes = 0; used.resetStatus(); exhausted = false; diff --git a/crafting/CraftingTreeProcess.java b/crafting/CraftingTreeProcess.java index 9912c701..26b9f3d4 100644 --- a/crafting/CraftingTreeProcess.java +++ b/crafting/CraftingTreeProcess.java @@ -31,6 +31,7 @@ public class CraftingTreeProcess boolean damageable; boolean fullsimulation; + private long bytes = 0; final private int depth; Map nodes = new HashMap(); @@ -113,8 +114,7 @@ public class CraftingTreeProcess public void request(MECraftingInventory inv, long i, BaseActionSource src) throws CraftBranchFailure, InterruptedException { - if ( Thread.interrupted() ) - throw new InterruptedException(); + job.handlepausing(); if ( fullsimulation ) { @@ -137,7 +137,10 @@ public class CraftingTreeProcess IAEItemStack o = AEApi.instance().storage().createItemStack( is ); if ( o != null ) + { + bytes++; inv.injectItems( o, Actionable.MODULATE, src ); + } } } else @@ -153,7 +156,10 @@ public class CraftingTreeProcess ItemStack is = Platform.getContainerItem( stack.getItemStack() ); IAEItemStack o = AEApi.instance().storage().createItemStack( is ); if ( o != null ) + { + bytes++; inv.injectItems( o, Actionable.MODULATE, src ); + } } } } @@ -176,11 +182,14 @@ public class CraftingTreeProcess job.addTask( getAmountCrafted( parent.getStack( 1 ) ), crafts, details, depth ); for (CraftingTreeNode pro : nodes.keySet()) pro.dive( job ); + + job.addBytes( 8 + crafts + bytes ); } public void setSimulate() { crafts = 0; + bytes = 0; for (CraftingTreeNode pro : nodes.keySet()) pro.setSimulate(); diff --git a/hooks/TickHandler.java b/hooks/TickHandler.java index 8d3bdfc4..e038b9d4 100644 --- a/hooks/TickHandler.java +++ b/hooks/TickHandler.java @@ -1,22 +1,30 @@ package appeng.hooks; import java.util.Collection; +import java.util.Iterator; import java.util.LinkedList; import java.util.Queue; import java.util.concurrent.Callable; +import net.minecraft.world.World; import net.minecraftforge.event.world.ChunkEvent; import net.minecraftforge.event.world.WorldEvent; import appeng.api.networking.IGridNode; import appeng.core.AELog; +import appeng.crafting.CraftingJob; import appeng.me.Grid; import appeng.me.NetworkList; import appeng.tile.AEBaseTile; import appeng.util.Platform; + +import com.google.common.collect.LinkedListMultimap; +import com.google.common.collect.Multimap; + import cpw.mods.fml.common.eventhandler.SubscribeEvent; import cpw.mods.fml.common.gameevent.TickEvent; import cpw.mods.fml.common.gameevent.TickEvent.Phase; import cpw.mods.fml.common.gameevent.TickEvent.Type; +import cpw.mods.fml.common.gameevent.TickEvent.WorldTickEvent; public class TickHandler { @@ -119,8 +127,24 @@ public class TickHandler @SubscribeEvent public void onTick(TickEvent ev) { - if ( ev.type == Type.SERVER && ev.phase == Phase.END ) // for no there is no reason to care about this on the - // client... + // rwar! + if ( ev.type == Type.WORLD && ev.phase == Phase.END ) + { + WorldTickEvent wte = (WorldTickEvent) ev; + synchronized (craftingJobs) + { + Iterator i = craftingJobs.get( wte.world ).iterator(); + while (i.hasNext()) + { + CraftingJob cj = i.next(); + if ( !cj.simulateFor( 5 ) ) + i.remove(); + } + } + } + + // for no there is no reason to care about this on the client... + else if ( ev.type == Type.SERVER && ev.phase == Phase.END ) { HandlerRep repo = getRepo(); while (!repo.tiles.isEmpty()) @@ -149,4 +173,13 @@ public class TickHandler } } + Multimap craftingJobs = LinkedListMultimap.create(); + + public void registerCraftingSimulation(World world, CraftingJob craftingJob) + { + synchronized (craftingJobs) + { + craftingJobs.put( world, craftingJob ); + } + } }