From 18ae994ee4a26efa6be010d8a5000a317e55e9bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Salom=C3=A3o?= Date: Thu, 10 Jun 2021 21:13:41 -0300 Subject: [PATCH] try to avoid CME by copying FE p2p outputs before iterating backported 1c88ea7b78c38addd5ea7b69fd9fbd32f3165030 and attempt to fix #25 with feedback --- .../java/appeng/me/cache/EnergyGridCache.java | 104 ++++++++++++++---- .../java/appeng/parts/p2p/PartP2PFEPower.java | 14 ++- 2 files changed, 94 insertions(+), 24 deletions(-) diff --git a/src/main/java/appeng/me/cache/EnergyGridCache.java b/src/main/java/appeng/me/cache/EnergyGridCache.java index c64459a8e..98fd42819 100644 --- a/src/main/java/appeng/me/cache/EnergyGridCache.java +++ b/src/main/java/appeng/me/cache/EnergyGridCache.java @@ -30,6 +30,7 @@ import java.util.PriorityQueue; import java.util.Queue; import java.util.Set; +import com.google.common.base.Preconditions; import com.google.common.collect.HashMultiset; import com.google.common.collect.Multiset; import com.google.common.collect.Sets; @@ -63,7 +64,7 @@ import appeng.me.energy.EnergyWatcher; public class EnergyGridCache implements IEnergyGrid { - private static final double MAX_BUFFER_STORAGE = 200; + private static final double MAX_BUFFER_STORAGE = 800; private static final Comparator COMPARATOR_HIGHEST_AMOUNT_STORED_FIRST = ( o1, o2 ) -> Double.compare( o2.getProviderStoredEnergy(), o1.getProviderStoredEnergy() ); @@ -76,9 +77,24 @@ public class EnergyGridCache implements IEnergyGrid }; private final NavigableSet interests = Sets.newTreeSet(); + + // Should only be modified from the add/remove methods below to guard against + // concurrent modifications private final double averageLength = 40.0; private final Set providers = new LinkedHashSet<>(); + // Used to track whether an extraction is currently in progress, to fail fast + // when something externally + // modifies the energy grid. + private boolean ongoingExtractOperation = false; + + // Should only be modified from the add/remove methods below to guard against + // concurrent modifications private final Set requesters = new LinkedHashSet<>(); + // Used to track whether an injection is currently in progress, to fail fast + // when something externally + // modifies the energy grid. + private boolean ongoingInjectOperation = false; + private final Multiset energyGridProviders = HashMultiset.create(); private final IGrid myGrid; private final HashMap watchers = new HashMap<>(); @@ -148,13 +164,19 @@ public class EnergyGridCache implements IEnergyGrid case PROVIDE_POWER: if( ev.storage.getPowerFlow() != AccessRestriction.WRITE ) { - this.providers.add( ev.storage ); + if (!ongoingExtractOperation) + { + addProvider( ev.storage ); + } } break; case REQUEST_POWER: if( ev.storage.getPowerFlow() != AccessRestriction.READ ) { - this.requesters.add( ev.storage ); + if (!ongoingInjectOperation) + { + addRequester(ev.storage); + } } break; } @@ -303,18 +325,24 @@ public class EnergyGridCache implements IEnergyGrid final Iterator it = this.providers.iterator(); - while( extractedPower < amt && it.hasNext() ) + ongoingExtractOperation = true; + try { - final IAEPowerStorage node = it.next(); - - final double req = amt - extractedPower; - final double newPower = node.extractAEPower( req, mode, PowerMultiplier.ONE ); - extractedPower += newPower; - - if( newPower < req && mode == Actionable.MODULATE ) + while ( extractedPower < amt && it.hasNext() ) { - it.remove(); + final IAEPowerStorage node = it.next(); + + final double req = amt - extractedPower; + final double newPower = node.extractAEPower( req, mode, PowerMultiplier.ONE ); + extractedPower += newPower; + + if( newPower < req && mode == Actionable.MODULATE ) + { + it.remove(); + } } + } finally { + ongoingExtractOperation = false; } final double result = Math.min( extractedPower, amt ); @@ -340,15 +368,22 @@ public class EnergyGridCache implements IEnergyGrid final Iterator it = this.requesters.iterator(); - while( amt > 0 && it.hasNext() ) + ongoingInjectOperation = true; + try { - final IAEPowerStorage node = it.next(); - amt = node.injectAEPower( amt, mode ); - - if( amt > 0 && mode == Actionable.MODULATE ) + while ( amt > 0 && it.hasNext() ) { - it.remove(); + final IAEPowerStorage node = it.next(); + amt = node.injectAEPower( amt, mode ); + + if( amt > 0 && mode == Actionable.MODULATE ) + { + it.remove(); + } } + } finally + { + ongoingInjectOperation = false; } final double overflow = Math.max( 0.0, amt ); @@ -506,8 +541,8 @@ public class EnergyGridCache implements IEnergyGrid this.globalAvailablePower -= ps.getAECurrentPower(); } - this.providers.remove( ps ); - this.requesters.remove( ps ); + removeProvider(ps); + removeRequester(ps); } } @@ -523,6 +558,31 @@ public class EnergyGridCache implements IEnergyGrid } } + private void addRequester(IAEPowerStorage requester) { + Preconditions.checkState(!ongoingInjectOperation, + "Cannot modify energy requesters while energy is being injected."); + this.requesters.add(requester); + } + + private void removeRequester(IAEPowerStorage requester) { + Preconditions.checkState(!ongoingInjectOperation, + "Cannot modify energy requesters while energy is being injected."); + this.requesters.add(requester); + } + + private void addProvider(IAEPowerStorage provider) { + Preconditions.checkState(!ongoingExtractOperation, + "Cannot modify energy providers while energy is being extracted."); + this.providers.add(provider); + } + + private void removeProvider(IAEPowerStorage provider) { + Preconditions.checkState(!ongoingExtractOperation, + "Cannot modify energy providers while energy is being extracted."); + this.providers.remove(provider); + } + + @Override public void addNode( final IGridNode node, final IGridHost machine ) { @@ -554,12 +614,12 @@ public class EnergyGridCache implements IEnergyGrid if( current > 0 && ps.getPowerFlow() != AccessRestriction.WRITE ) { this.globalAvailablePower += current; - this.providers.add( ps ); + addProvider(ps); } if( current < max && ps.getPowerFlow() != AccessRestriction.READ ) { - this.requesters.add( ps ); + addRequester(ps); } } } diff --git a/src/main/java/appeng/parts/p2p/PartP2PFEPower.java b/src/main/java/appeng/parts/p2p/PartP2PFEPower.java index 901579ea5..80654ff5b 100644 --- a/src/main/java/appeng/parts/p2p/PartP2PFEPower.java +++ b/src/main/java/appeng/parts/p2p/PartP2PFEPower.java @@ -19,11 +19,12 @@ package appeng.parts.p2p; -import java.util.List; +import java.util.*; import javax.annotation.Nonnull; import javax.annotation.Nullable; +import appeng.me.cache.helpers.TunnelCollection; import net.minecraft.item.ItemStack; import net.minecraft.tileentity.TileEntity; import net.minecraftforge.common.capabilities.Capability; @@ -42,6 +43,7 @@ public class PartP2PFEPower extends PartP2PTunnel private static final IEnergyStorage NULL_ENERGY_STORAGE = new NullEnergyStorage(); private final IEnergyStorage inputHandler = new InputEnergyStorage(); private final IEnergyStorage outputHandler = new OutputEnergyStorage(); + private final Queue outputs = new ArrayDeque<>(); public PartP2PFEPower( ItemStack is ) { @@ -108,6 +110,8 @@ public class PartP2PFEPower extends PartP2PTunnel private class InputEnergyStorage implements IEnergyStorage { + private boolean iteratingOutputs; + @Override public int extractEnergy( int maxExtract, boolean simulate ) { @@ -131,8 +135,14 @@ public class PartP2PFEPower extends PartP2PTunnel final int amountPerOutput = maxReceive / outputTunnels; int overflow = amountPerOutput == 0 ? maxReceive : maxReceive % amountPerOutput; - for( PartP2PFEPower target : PartP2PFEPower.this.getOutputs() ) + if (outputs.isEmpty()) { + for ( PartP2PFEPower o : PartP2PFEPower.this.getOutputs()) + outputs.add( o ); + } + + while ( !outputs.isEmpty() ) { + PartP2PFEPower target = outputs.poll(); final IEnergyStorage output = target.getAttachedEnergyStorage(); final int toSend = amountPerOutput + overflow; final int received = output.receiveEnergy( toSend, simulate );