diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPowerSubstation.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPowerSubstation.java index abf7a3d31d4..c189c803800 100644 --- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPowerSubstation.java +++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPowerSubstation.java @@ -45,6 +45,7 @@ import net.minecraft.util.text.TextFormatting; import net.minecraft.world.World; import net.minecraftforge.common.capabilities.Capability; +import net.minecraftforge.common.util.Constants; import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.SideOnly; @@ -140,7 +141,7 @@ protected void formStructure(PatternMatchContext context) { if (this.energyBank == null) { this.energyBank = new PowerStationEnergyBank(parts); } else { - this.energyBank = energyBank.rebuild(parts); + this.energyBank.rebuild(parts); } this.passiveDrain = this.energyBank.getPassiveDrainPerTick(); } @@ -603,46 +604,60 @@ public static class PowerStationEnergyBank { private static final String NBT_SIZE = "Size"; private static final String NBT_STORED = "Stored"; private static final String NBT_MAX = "Max"; - - private final long[] storage; - private final long[] maximums; - private final BigInteger capacity; - private int index; + // the following two fields represent ((a[0] << 63) | a[1]) + private final long[] stored = new long[2]; + private final long[] max = new long[2]; + private BigInteger capacity; + private long drain; + private long drainMod; public PowerStationEnergyBank(List batteries) { - storage = new long[batteries.size()]; - maximums = new long[batteries.size()]; - for (int i = 0; i < batteries.size(); i++) { - maximums[i] = batteries.get(i).getCapacity(); + for (IBatteryData i : batteries) { + add(max, i.getCapacity()); + updateDrain(i.getCapacity()); } - capacity = summarize(maximums); + capacity = summarize(max); } public PowerStationEnergyBank(NBTTagCompound storageTag) { - int size = storageTag.getInteger(NBT_SIZE); - storage = new long[size]; - maximums = new long[size]; - for (int i = 0; i < size; i++) { - NBTTagCompound subtag = storageTag.getCompoundTag(String.valueOf(i)); - if (subtag.hasKey(NBT_STORED)) { - storage[i] = subtag.getLong(NBT_STORED); + // legacy nbt handling + if (storageTag.hasKey(NBT_SIZE, Constants.NBT.TAG_INT)) { + int size = storageTag.getInteger(NBT_SIZE); + for (int i = 0; i < size; i++) { + NBTTagCompound tag = storageTag.getCompoundTag(String.valueOf(i)); + if (tag.hasKey(NBT_STORED)) add(stored, tag.getLong(NBT_STORED)); + long store = tag.getLong(NBT_MAX); + add(max, store); + updateDrain(store); } - maximums[i] = subtag.getLong(NBT_MAX); + } else { + stored[0] = storageTag.getLong(NBT_STORED + "0"); + stored[1] = storageTag.getLong(NBT_STORED + "1"); + drain = storageTag.getLong("drain"); + drainMod = storageTag.getLong("drainMod"); } - capacity = summarize(maximums); + capacity = summarize(max); + } + + public NBTTagCompound writeToNBT(NBTTagCompound compound) { + compound.setLong(NBT_STORED + "0", stored[0]); + compound.setLong(NBT_STORED + "1", stored[1]); + compound.setLong("drain", drain); + compound.setLong("drainMod", drainMod); + return compound; } - private NBTTagCompound writeToNBT(NBTTagCompound compound) { - compound.setInteger(NBT_SIZE, storage.length); - for (int i = 0; i < storage.length; i++) { - NBTTagCompound subtag = new NBTTagCompound(); - if (storage[i] > 0) { - subtag.setLong(NBT_STORED, storage[i]); + private void updateDrain(long val) { + if (val / PASSIVE_DRAIN_DIVISOR >= PASSIVE_DRAIN_MAX_PER_STORAGE) { + drain += PASSIVE_DRAIN_MAX_PER_STORAGE; + } else { + drain += val / PASSIVE_DRAIN_DIVISOR; + drainMod += (val % PASSIVE_DRAIN_DIVISOR); + if (drainMod >= PASSIVE_DRAIN_DIVISOR) { + drain++; + drainMod -= PASSIVE_DRAIN_DIVISOR; } - subtag.setLong(NBT_MAX, maximums[i]); - compound.setTag(String.valueOf(i), subtag); } - return compound; } /** @@ -650,76 +665,60 @@ private NBTTagCompound writeToNBT(NBTTagCompound compound) { * Will use existing stored power and try to map it onto new batteries. * If there was more power before the rebuild operation, it will be lost. */ - public PowerStationEnergyBank rebuild(@NotNull List batteries) { + public void rebuild(@NotNull List batteries) { if (batteries.isEmpty()) { throw new IllegalArgumentException("Cannot rebuild Power Substation power bank with no batteries!"); } - PowerStationEnergyBank newStorage = new PowerStationEnergyBank(batteries); - for (long stored : storage) { - newStorage.fill(stored); + Arrays.fill(max, 0); + drain = 0; + for (IBatteryData i : batteries) { + add(max, i.getCapacity()); + updateDrain(i.getCapacity()); + } + + if (stored[0] > max[0]) { + stored[0] = max[0]; + stored[1] = max[1]; + } else if (stored[0] == max[0]) { + stored[1] = Math.min(stored[1], max[1]); } - return newStorage; + capacity = summarize(max); } /** @return Amount filled into storage */ public long fill(long amount) { if (amount < 0) throw new IllegalArgumentException("Amount cannot be negative!"); - - // ensure index - if (index != storage.length - 1 && storage[index] == maximums[index]) { - index++; + // can't overflow, just add normally + if (max[0] == stored[0]) { + amount = Math.min(max[1] - stored[1], amount); + stored[1] += amount; + return amount; } - - long maxFill = Math.min(maximums[index] - storage[index], amount); - - // storage is completely full - if (maxFill == 0 && index == storage.length - 1) { - return 0; - } - - // fill this "battery" as much as possible - storage[index] += maxFill; - amount -= maxFill; - - // try to fill other "batteries" if necessary - if (amount > 0 && index != storage.length - 1) { - return maxFill + fill(amount); + if (stored[1] + amount < 0) { + stored[0]++; + stored[1] += Long.MIN_VALUE; + if (max[0] == stored[0] && max[1] < stored[1] + amount) { + amount = max[1] - stored[1]; + } } - - // other fill not necessary, either because the storage is now completely full, - // or we were able to consume all the energy in this "battery" - return maxFill; + stored[1] += amount; + return amount; } /** @return Amount drained from storage */ public long drain(long amount) { if (amount < 0) throw new IllegalArgumentException("Amount cannot be negative!"); - - // ensure index - if (index != 0 && storage[index] == 0) { - index--; + // cant borrow, just subtract normally + if (stored[0] == 0) { + long sub = Math.min(stored[1], amount); + stored[1] -= sub; + return sub; } - - long maxDrain = Math.min(storage[index], amount); - - // storage is completely empty - if (maxDrain == 0 && index == 0) { - return 0; - } - - // drain this "battery" as much as possible - storage[index] -= maxDrain; - amount -= maxDrain; - - // try to drain other "batteries" if necessary - if (amount > 0 && index != 0) { - index--; - return maxDrain + drain(amount); - } - - // other drain not necessary, either because the storage is now completely empty, - // or we were able to drain all the energy from this "battery" - return maxDrain; + if (stored[1] < amount) { + stored[0]--; + stored[1] -= amount + Long.MIN_VALUE; + } else stored[1] -= amount; + return amount; } public BigInteger getCapacity() { @@ -727,51 +726,28 @@ public BigInteger getCapacity() { } public BigInteger getStored() { - return summarize(storage); + return summarize(stored); } public boolean hasEnergy() { - for (long l : storage) { - if (l > 0) return true; - } - return false; + return stored[0] != 0 && stored[1] != 0; } - private static BigInteger summarize(long[] values) { - BigInteger retVal = BigInteger.ZERO; - long currentSum = 0; - for (long value : values) { - if (currentSum != 0 && value > Long.MAX_VALUE - currentSum) { - // will overflow if added - retVal = retVal.add(BigInteger.valueOf(currentSum)); - currentSum = 0; - } - currentSum += value; - } - if (currentSum != 0) { - retVal = retVal.add(BigInteger.valueOf(currentSum)); + private static BigInteger summarize(long[] num) { + return BigInteger.valueOf(num[0]).shiftLeft(63).add(BigInteger.valueOf(num[1])); + } + + private static void add(long[] num, long val) { + num[1] += val; + if (num[1] < 0) { + num[0]++; + num[1] -= Long.MIN_VALUE; } - return retVal; } @VisibleForTesting public long getPassiveDrainPerTick() { - long[] maximumsExcl = new long[maximums.length]; - int index = 0; - int numExcl = 0; - for (long maximum : maximums) { - if (maximum / PASSIVE_DRAIN_DIVISOR >= PASSIVE_DRAIN_MAX_PER_STORAGE) { - numExcl++; - } else { - maximumsExcl[index++] = maximum; - } - } - maximumsExcl = Arrays.copyOf(maximumsExcl, index); - BigInteger capacityExcl = summarize(maximumsExcl); - - return capacityExcl.divide(BigInteger.valueOf(PASSIVE_DRAIN_DIVISOR)) - .add(BigInteger.valueOf(PASSIVE_DRAIN_MAX_PER_STORAGE * numExcl)) - .longValue(); + return drain; } } diff --git a/src/test/java/gregtech/common/metatileentities/multiblock/PowerSubstationTest.java b/src/test/java/gregtech/common/metatileentities/multiblock/PowerSubstationTest.java index de9a1172121..01b598f3464 100644 --- a/src/test/java/gregtech/common/metatileentities/multiblock/PowerSubstationTest.java +++ b/src/test/java/gregtech/common/metatileentities/multiblock/PowerSubstationTest.java @@ -2,6 +2,7 @@ import gregtech.Bootstrap; import gregtech.api.metatileentity.multiblock.IBatteryData; +import gregtech.api.util.random.XoShiRo256PlusPlusRandom; import gregtech.common.metatileentities.multi.electric.MetaTileEntityPowerSubstation.PowerStationEnergyBank; import org.hamcrest.Matcher; @@ -14,7 +15,6 @@ import java.math.BigInteger; import java.util.ArrayList; import java.util.List; -import java.util.Random; import java.util.function.Consumer; import static gregtech.common.metatileentities.multi.electric.MetaTileEntityPowerSubstation.MAX_BATTERY_LAYERS; @@ -237,7 +237,7 @@ public void Test_Rebuild_Storage() { MatcherAssert.assertThat(storage.getStored(), isBigInt(3000)); // Rebuild with more storage than needed - storage = rebuildStorage(storage, 1000, 4000, 4000); + rebuildStorage(storage, 1000, 4000, 4000); MatcherAssert.assertThat(storage.getCapacity(), isBigInt(9000)); MatcherAssert.assertThat(storage.getStored(), isBigInt(3000)); @@ -250,14 +250,14 @@ public void Test_Rebuild_Storage() { MatcherAssert.assertThat(storage.getStored(), isBigInt(3000)); // Rebuild with less storage than needed - storage = rebuildStorage(storage, 100, 100, 400, 500); + rebuildStorage(storage, 100, 100, 400, 500); MatcherAssert.assertThat(storage.getCapacity(), isBigInt(1100)); MatcherAssert.assertThat(storage.getStored(), isBigInt(1100)); } @Test public void Test_Optimized_Big_Integer_Summarize() { - Consumer testRunner = r -> { + Consumer testRunner = r -> { BigInteger summation = BigInteger.ZERO; long[] storageValues = new long[9 * MAX_BATTERY_LAYERS]; for (int i = 0; i < storageValues.length; i++) { @@ -271,7 +271,7 @@ public void Test_Optimized_Big_Integer_Summarize() { }; for (int i = 0; i < 100; i++) { - testRunner.accept(new Random()); + testRunner.accept(new XoShiRo256PlusPlusRandom()); } } @@ -282,13 +282,13 @@ public void Test_Passive_Drain_Calculation() { MatcherAssert.assertThat(storage.getPassiveDrainPerTick(), is(2 * PASSIVE_DRAIN_MAX_PER_STORAGE)); - Consumer testRunner = r -> { + Consumer testRunner = r -> { int numTruncated = 0; BigInteger nonTruncated = BigInteger.ZERO; long[] storageValues = new long[9 * MAX_BATTERY_LAYERS]; for (int i = 0; i < storageValues.length; i++) { - long randomLong = Math.abs(r.nextLong()); + long randomLong = r.nextLong(PASSIVE_DRAIN_MAX_PER_STORAGE * PASSIVE_DRAIN_DIVISOR * 2); storageValues[i] = randomLong; if (randomLong / PASSIVE_DRAIN_DIVISOR >= PASSIVE_DRAIN_MAX_PER_STORAGE) { numTruncated++; @@ -305,59 +305,77 @@ public void Test_Passive_Drain_Calculation() { }; for (int i = 0; i < 100; i++) { - testRunner.accept(new Random()); + testRunner.accept(new XoShiRo256PlusPlusRandom()); } } @Test public void Test_Fill_Drain_Randomized() { - Consumer testRunner = r -> { - BigInteger totalStorage = BigInteger.ZERO; + Consumer testRunner = r -> { + BigInteger capacity = BigInteger.ZERO; long[] storageValues = new long[9 * MAX_BATTERY_LAYERS]; for (int i = 0; i < storageValues.length; i++) { long randomLong = Math.abs(r.nextLong()); storageValues[i] = randomLong; - totalStorage = totalStorage.add(BigInteger.valueOf(randomLong)); + capacity = capacity.add(BigInteger.valueOf(randomLong)); } PowerStationEnergyBank storage = createStorage(storageValues); // test capacity - MatcherAssert.assertThat(storage.getCapacity(), is(totalStorage)); + MatcherAssert.assertThat(storage.getCapacity(), is(capacity)); - // test fill - BigInteger amountToFill = totalStorage; - do { - long randomLong = Math.abs(r.nextLong()); - BigInteger randomBigInt = BigInteger.valueOf(randomLong); + BigInteger current = BigInteger.valueOf(Long.MAX_VALUE) + .multiply(BigInteger.valueOf(9 * MAX_BATTERY_LAYERS / 4)); + for (int i = 0; i < 9 * MAX_BATTERY_LAYERS / 4; i++) { + storage.fill(Long.MAX_VALUE); + } - if (amountToFill.compareTo(randomBigInt) <= 0) { - MatcherAssert.assertThat(storage.fill(randomLong), is(amountToFill.longValue())); - amountToFill = BigInteger.ZERO; + MatcherAssert.assertThat(storage.getStored(), is(current)); + + for (int i = 0; i < 100; i++) { + long randLong = Math.abs(r.nextLong()); + BigInteger randBig = BigInteger.valueOf(randLong); + if (r.nextBoolean()) { + MatcherAssert.assertThat(storage.fill(randLong), is(randLong)); + current = current.add(randBig); + MatcherAssert.assertThat(storage.getStored(), is(current)); } else { - MatcherAssert.assertThat(storage.fill(randomLong), is(randomLong)); - amountToFill = amountToFill.subtract(randomBigInt); + MatcherAssert.assertThat(storage.drain(randLong), is(randLong)); + current = current.subtract(randBig); + MatcherAssert.assertThat(storage.getStored(), is(current)); } - } while (!amountToFill.equals(BigInteger.ZERO)); + } - // test drain - BigInteger amountToDrain = totalStorage; - do { - long randomLong = Math.abs(r.nextLong()); - BigInteger randomBigInt = BigInteger.valueOf(randomLong); + while (!current.equals(capacity)) { + long randLong = Math.abs(r.nextLong()); + BigInteger randBig = BigInteger.valueOf(randLong); + if (current.add(randBig).compareTo(capacity) >= 0) { + MatcherAssert.assertThat(storage.fill(randLong), is(capacity.subtract(current).longValue())); + current = capacity; + } else { + MatcherAssert.assertThat(storage.fill(randLong), is(randLong)); + current = current.add(randBig); + } + MatcherAssert.assertThat(storage.getStored(), is(current)); + } - if (amountToDrain.compareTo(randomBigInt) <= 0) { - MatcherAssert.assertThat(storage.drain(randomLong), is(amountToDrain.longValue())); - amountToDrain = BigInteger.ZERO; + while (current.signum() > 0) { + long randLong = Math.abs(r.nextLong()); + BigInteger randBig = BigInteger.valueOf(randLong); + if (current.compareTo(randBig) <= 0) { + MatcherAssert.assertThat(storage.drain(randLong), is(current.longValue())); + current = BigInteger.ZERO; } else { - MatcherAssert.assertThat(storage.drain(randomLong), is(randomLong)); - amountToDrain = amountToDrain.subtract(randomBigInt); + MatcherAssert.assertThat(storage.drain(randLong), is(randLong)); + current = current.subtract(randBig); } - } while (!amountToDrain.equals(BigInteger.ZERO)); + MatcherAssert.assertThat(storage.getStored(), is(current)); + } }; for (int i = 0; i < 100; i++) { - testRunner.accept(new Random()); + testRunner.accept(new XoShiRo256PlusPlusRandom()); } } @@ -379,12 +397,12 @@ private static PowerStationEnergyBank createStorage(long... storageValues) { return new PowerStationEnergyBank(batteries); } - private static PowerStationEnergyBank rebuildStorage(PowerStationEnergyBank storage, long... storageValues) { + private static void rebuildStorage(PowerStationEnergyBank storage, long... storageValues) { List batteries = new ArrayList<>(); for (long value : storageValues) { batteries.add(new TestBattery(value)); } - return storage.rebuild(batteries); + storage.rebuild(batteries); } private static class TestBattery implements IBatteryData {