diff --git a/src/main/java/com/almostreliable/kubeio/enderio/CustomEnergyConduitTicker.java b/src/main/java/com/almostreliable/kubeio/enderio/CustomEnergyConduitTicker.java index 82fe59c..840455d 100644 --- a/src/main/java/com/almostreliable/kubeio/enderio/CustomEnergyConduitTicker.java +++ b/src/main/java/com/almostreliable/kubeio/enderio/CustomEnergyConduitTicker.java @@ -1,6 +1,17 @@ package com.almostreliable.kubeio.enderio; +import com.enderio.api.conduit.ConduitData; +import com.enderio.api.conduit.ticker.IOAwareConduitTicker; import com.enderio.conduits.common.conduit.type.energy.EnergyConduitTicker; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.util.Tuple; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraftforge.common.capabilities.ForgeCapabilities; +import net.minecraftforge.energy.IEnergyStorage; +import org.jetbrains.annotations.Nullable; + +import java.util.*; +import java.util.function.BiFunction; public class CustomEnergyConduitTicker extends EnergyConduitTicker { @@ -10,7 +21,252 @@ public class CustomEnergyConduitTicker extends EnergyConduitTicker { this.transferRate = transferRate; } - public int getTransferRate() { - return transferRate; + public > void customTickEnergyGraph( + ServerLevel level, + List> inserts, + List> extracts + ) { + Map capabilitiesByBlocks = getCapabilitiesByBlocks( + level, + inserts, + extracts + ); + ExtractInsertData extractInsertData = transformCapabilitiesByBlocks(capabilitiesByBlocks); + + // If this is true, no energy transfer is necessary + if ((extractInsertData.inserts.isEmpty() && extractInsertData.extracts.isEmpty()) || + (extractInsertData.inserts.isEmpty() && extractInsertData.insertAndExtracts.isEmpty()) || + (extractInsertData.extracts.isEmpty() && extractInsertData.insertAndExtracts.isEmpty())) { + return; + } + + OrderedCapabilitiesWithMaxTransfer sortedInserts = sortTransfers( + extractInsertData.inserts, + (energyStorage, amount) -> energyStorage.receiveEnergy(amount, true) + ); + OrderedCapabilitiesWithMaxTransfer sortedExtracts = sortTransfers( + extractInsertData.extracts, + (energyStorage, amount) -> energyStorage.extractEnergy(amount, true) + ); + + if (sortedInserts.maxTransferAmount < sortedExtracts.maxTransferAmount) { + handleExcessPower(sortedInserts, sortedExtracts, extractInsertData); + } else if (sortedInserts.maxTransferAmount > sortedExtracts.maxTransferAmount) { + handlePowerDeficit(sortedInserts, sortedExtracts, extractInsertData); + } else { + for(var insert : sortedInserts.caps) insert.receiveEnergy(transferRate, false); + for(var extract : sortedExtracts.caps) extract.extractEnergy(transferRate, false); + } + } + + private void handleExcessPower( + OrderedCapabilitiesWithMaxTransfer sortedInserts, + OrderedCapabilitiesWithMaxTransfer sortedExtracts, + ExtractInsertData extractInsertData + ) { + int excessPower = sortedExtracts.maxTransferAmount - sortedInserts.maxTransferAmount; + OrderedCapabilitiesWithMaxTransfer sortedExtraInserts = sortTransfers( + extractInsertData.insertAndExtracts.stream().map(Tuple::getA).toList(), + (energyStorage, amount) -> energyStorage.receiveEnergy(amount, true) + ); + for(var insert : sortedInserts.caps) insert.receiveEnergy(transferRate, false); + if (excessPower > sortedExtraInserts.maxTransferAmount) { + for(var insert : sortedExtraInserts.caps) insert.receiveEnergy(transferRate, false); + + int leftToExtract = sortedInserts.maxTransferAmount + sortedExtraInserts.maxTransferAmount; + extractBalanced(sortedExtracts, leftToExtract); + } else { + for(var extract : sortedExtracts.caps) extract.extractEnergy(transferRate, false); + + insertBalanced(sortedExtraInserts, excessPower); + } + } + + private void handlePowerDeficit( + OrderedCapabilitiesWithMaxTransfer sortedInserts, OrderedCapabilitiesWithMaxTransfer sortedExtracts, + ExtractInsertData extractInsertData + ) { + int powerDeficit = sortedInserts.maxTransferAmount - sortedExtracts.maxTransferAmount; + OrderedCapabilitiesWithMaxTransfer sortedExtraExtracts = sortTransfers( + extractInsertData.insertAndExtracts.stream().map(Tuple::getB).toList(), + (energyStorage, amount) -> energyStorage.extractEnergy(amount, true) + ); + for(var extract : sortedExtracts.caps) extract.extractEnergy(transferRate, false); + if (powerDeficit > sortedExtraExtracts.maxTransferAmount) { + for(var extract : sortedExtraExtracts.caps) extract.extractEnergy(transferRate, false); + + int leftToInsert = sortedExtracts.maxTransferAmount + sortedExtraExtracts.maxTransferAmount; + insertBalanced(sortedInserts, leftToInsert); + } else { + for(var insert : sortedInserts.caps) insert.receiveEnergy(transferRate, false); + + extractBalanced(sortedExtraExtracts, powerDeficit); + } + } + + private void insertBalanced(OrderedCapabilitiesWithMaxTransfer sortedExtraInserts, int leftToInsert) { + for (int i = 0; i < sortedExtraInserts.caps.size(); i++) { + int toInsert = (int) Math.ceil(leftToInsert / (double) (sortedExtraInserts.caps.size() - i)); + leftToInsert -= sortedExtraInserts.caps.get(i).receiveEnergy(toInsert, false); + if (leftToInsert <= 0) break; + } + } + + private void extractBalanced(OrderedCapabilitiesWithMaxTransfer sortedExtracts, int leftToExtract) { + for (int i = 0; i < sortedExtracts.caps.size(); i++) { + int toExtract = (int) Math.ceil(leftToExtract / (double) (sortedExtracts.caps.size() - i)); + leftToExtract -= sortedExtracts.caps.get(i).extractEnergy(toExtract, false); + if (leftToExtract <= 0) break; + } + } + + private > Map getCapabilitiesByBlocks( + ServerLevel level, + List> inserts, + List> extracts + ) { + Map capabilitiesByBlocks = new IdentityHashMap<>( + inserts.size() + extracts.size()); + + for (IOAwareConduitTicker.Connection insert : inserts) { + BlockEntity be = level.getBlockEntity(insert.move()); + if (be == null) continue; + IEnergyStorage capability = be.getCapability(ForgeCapabilities.ENERGY, insert.dir().getOpposite()) + .resolve() + .orElse(null); + if (capability == null) continue; + BlockCapabilityConnections blockCapabilityConnections = capabilitiesByBlocks.computeIfAbsent( + be, + k -> new BlockCapabilityConnections(new CapabilityConnections(capability), null) + ); + Objects.requireNonNull(blockCapabilityConnections.getInsert()).increment(); + } + + if (!capabilitiesByBlocks.isEmpty()) { + for (IOAwareConduitTicker.Connection extract : extracts) { + BlockEntity be = level.getBlockEntity(extract.move()); + if (be == null) continue; + IEnergyStorage capability = be.getCapability(ForgeCapabilities.ENERGY, extract.dir().getOpposite()) + .resolve() + .orElse(null); + if (capability == null) continue; + BlockCapabilityConnections blockCapabilityConnections = capabilitiesByBlocks.computeIfAbsent( + be, + k -> new BlockCapabilityConnections(null, new CapabilityConnections(capability)) + ); + if (blockCapabilityConnections.getExtract() == null) blockCapabilityConnections.setExtract(new CapabilityConnections(capability)); + blockCapabilityConnections.getExtract().increment(); + } + } + + return capabilitiesByBlocks; + } + + private ExtractInsertData transformCapabilitiesByBlocks( + Map capabilitiesByBlock + ) { + List inserts = new ArrayList<>(); + List extracts = new ArrayList<>(); + List> insertAndExtracts = new ArrayList<>(); + + // I only use one capability for all energy transfer as I need to check if all the energy fits in the machine from all connected sides + for (var blockCapabilities : capabilitiesByBlock.values()) { + if (blockCapabilities.getInsert() == null) { + extracts.add(blockCapabilities.getExtract()); + } else if (blockCapabilities.getExtract() == null) { + inserts.add(blockCapabilities.getInsert()); + } else { + insertAndExtracts.add(new Tuple<>(blockCapabilities.getInsert(), blockCapabilities.getExtract())); + } + } + + return new ExtractInsertData(inserts, extracts, insertAndExtracts); + } + + private OrderedCapabilitiesWithMaxTransfer sortTransfers( + List capabilityConnectionsList, BiFunction getTransfer + ) { + int maxTransferAmount = 0; + List> capabilitiesWithInsertAmounts = new ArrayList<>(); + for (var capabilityConnections : capabilityConnectionsList) { + int transferAmount = getTransfer.apply( + capabilityConnections.capability, + transferRate * capabilityConnections.getCount() + ); + if (transferAmount == 0) continue; + maxTransferAmount += transferAmount; + capabilitiesWithInsertAmounts.addAll(split( + capabilityConnections.capability, + capabilityConnections.getCount(), + transferAmount + )); + } + capabilitiesWithInsertAmounts.sort(Comparator.comparingInt(Tuple::getB)); + + List sortedInserts = new ArrayList<>(); + for (var capabilityWithInsertAmount : capabilitiesWithInsertAmounts) { + sortedInserts.add(capabilityWithInsertAmount.getA()); + } + + return new OrderedCapabilitiesWithMaxTransfer(sortedInserts, maxTransferAmount); + } + + private List> split(IEnergyStorage storage, int partCount, int amountToSplit) { + int part = (int) Math.ceil((double) amountToSplit / partCount); + List> result = new ArrayList<>(partCount); + for (int i = 0; i < amountToSplit / part; i++) { + result.add(new Tuple<>(storage, part)); + } + if (amountToSplit % part != 0) result.add(new Tuple<>(storage, amountToSplit % part)); + return result; + } + + private record OrderedCapabilitiesWithMaxTransfer(List caps, int maxTransferAmount) {} + + private record ExtractInsertData(List inserts, + List extracts, + List> insertAndExtracts) {} + + private static class BlockCapabilityConnections { + private @Nullable CapabilityConnections insertCapabilityConnections; + private @Nullable CapabilityConnections extractCapabilityConnections; + + public BlockCapabilityConnections(@Nullable CapabilityConnections insertCapabilityConnections, @Nullable CapabilityConnections extractCapabilityConnections) { + this.insertCapabilityConnections = insertCapabilityConnections; + this.extractCapabilityConnections = extractCapabilityConnections; + } + + public @Nullable CapabilityConnections getInsert() { + return insertCapabilityConnections; + } + + public @Nullable CapabilityConnections getExtract() { + return extractCapabilityConnections; + } + + public void setInsert(@Nullable CapabilityConnections insertCapabilityConnections) { + this.insertCapabilityConnections = insertCapabilityConnections; + } + + public void setExtract(@Nullable CapabilityConnections extractCapabilityConnections) { + this.extractCapabilityConnections = extractCapabilityConnections; + } + } + + private static class CapabilityConnections { + public final IEnergyStorage capability; + private int count = 0; + + public CapabilityConnections(IEnergyStorage capability) { + this.capability = capability; + } + + public int getCount() { + return count; + } + + public void increment() { + count++; + } } } diff --git a/src/main/java/com/almostreliable/kubeio/mixin/EnergyConduitTickerMixin.java b/src/main/java/com/almostreliable/kubeio/mixin/EnergyConduitTickerMixin.java index d2bf438..b048785 100644 --- a/src/main/java/com/almostreliable/kubeio/mixin/EnergyConduitTickerMixin.java +++ b/src/main/java/com/almostreliable/kubeio/mixin/EnergyConduitTickerMixin.java @@ -1,7 +1,6 @@ package com.almostreliable.kubeio.mixin; import com.almostreliable.kubeio.enderio.CustomEnergyConduitTicker; -import com.almostreliable.kubeio.util.CapabilityConnections; import com.enderio.api.conduit.ColoredRedstoneProvider; import com.enderio.api.conduit.ConduitData; import com.enderio.api.conduit.ConduitGraph; @@ -10,18 +9,12 @@ import com.enderio.api.conduit.ticker.IOAwareConduitTicker; import com.enderio.api.misc.ColorControl; import net.minecraft.server.level.ServerLevel; -import net.minecraft.util.Tuple; -import net.minecraft.world.level.block.entity.BlockEntity; -import net.minecraftforge.common.capabilities.ForgeCapabilities; -import net.minecraftforge.energy.IEnergyStorage; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import java.util.*; -import java.util.concurrent.atomic.AtomicReference; -import java.util.stream.Stream; @Mixin(CapabilityAwareConduitTicker.class) public abstract class EnergyConduitTickerMixin, TCap> { @@ -34,207 +27,7 @@ public abstract class EnergyConduitTickerMixin, ColoredRedstoneProvider coloredRedstoneProvider, CallbackInfo ci ) { if (!((Object) this instanceof CustomEnergyConduitTicker ticker)) return; - Map> capabilitiesByBlock = new IdentityHashMap<>( - inserts.size() + extracts.size()); - - for (IOAwareConduitTicker.Connection insert : inserts) { - BlockEntity be = level.getBlockEntity(insert.move()); - if (be == null) continue; - IEnergyStorage capability = be.getCapability(ForgeCapabilities.ENERGY, insert.dir().getOpposite()) - .resolve() - .orElse(null); - if (capability == null) continue; - Tuple capabilityTuple = capabilitiesByBlock.computeIfAbsent( - be, - k -> new Tuple<>(new CapabilityConnections(capability), null) - ); - capabilityTuple.getA().increment(); - } - - if (!capabilitiesByBlock.isEmpty()) { - for (IOAwareConduitTicker.Connection extract : extracts) { - BlockEntity be = level.getBlockEntity(extract.move()); - if (be == null) continue; - IEnergyStorage capability = be.getCapability(ForgeCapabilities.ENERGY, extract.dir().getOpposite()) - .resolve() - .orElse(null); - if (capability == null) continue; - Tuple capabilityTuple = capabilitiesByBlock.computeIfAbsent( - be, - k -> new Tuple<>(null, new CapabilityConnections(capability)) - ); - if (capabilityTuple.getB() == null) capabilityTuple.setB(new CapabilityConnections(capability)); - capabilityTuple.getB().increment(); - } - } - - List energyInserts = new ArrayList<>(); - List energyExtracts = new ArrayList<>(); - List> energyInsertAndExtracts = new ArrayList<>(); - - // I only use one capability for all energy transfer as I need to check if all the energy fits in the machine from all connected sides - for (var tuple : capabilitiesByBlock.values()) { - if (tuple.getA() == null) { - energyExtracts.add(tuple.getB()); - } else if (tuple.getB() == null) { - energyInserts.add(tuple.getA()); - } else { - energyInsertAndExtracts.add(tuple); - } - } - - if ((!energyInserts.isEmpty() && !energyExtracts.isEmpty()) || (!energyInserts.isEmpty() && !energyInsertAndExtracts.isEmpty()) || (!energyExtracts.isEmpty() && !energyInsertAndExtracts.isEmpty())) { - tickEnergyGraph(ticker, energyInserts, energyExtracts, energyInsertAndExtracts); - } - + ticker.customTickEnergyGraph(level, inserts, extracts); ci.cancel(); } - - private void tickEnergyGraph( - CustomEnergyConduitTicker ticker, List inserts, List extracts, - List> insertAndExtracts - ) { - int transferRate = ticker.getTransferRate(); - - AtomicReference maxInsertAmount = new AtomicReference<>(0); - List sortedInserts = inserts.stream() - .flatMap(capabilityConnections -> { - int insertAmount = capabilityConnections.capability.receiveEnergy( - transferRate * capabilityConnections.getCount(), - true - ); - if (insertAmount == 0) return Stream.empty(); - maxInsertAmount.updateAndGet(v -> v + insertAmount); - return split(capabilityConnections.capability, capabilityConnections.getCount(), insertAmount).stream(); - }) - .filter(t -> t.getB() > 0) - .sorted(Comparator.comparingInt(Tuple::getB)) - .map(Tuple::getA) - .toList(); - - AtomicReference maxExtractAmount = new AtomicReference<>(0); - List sortedExtracts = extracts.stream() - .flatMap(capabilityConnections -> { - int extractAmount = capabilityConnections.capability.extractEnergy( - transferRate * capabilityConnections.getCount(), - true - ); - if (extractAmount == 0) return Stream.empty(); - maxExtractAmount.updateAndGet(v -> v + extractAmount); - return split( - capabilityConnections.capability, - capabilityConnections.getCount(), - extractAmount - ).stream(); - }) - .filter(t -> t.getB() > 0) - .sorted(Comparator.comparingInt(Tuple::getB)) - .map(Tuple::getA) - .toList(); - - if (maxInsertAmount.get() < maxExtractAmount.get()) { - int excessPower = maxExtractAmount.get() - maxInsertAmount.get(); - - AtomicReference maxExtraInsertAmount = new AtomicReference<>(0); - List extraSortedInserts = insertAndExtracts.stream() - .map(Tuple::getA) - .flatMap(capabilityConnections -> { - int insertAmount = capabilityConnections.capability.receiveEnergy( - transferRate * capabilityConnections.getCount(), - true - ); - if (insertAmount == 0) return Stream.empty(); - maxExtraInsertAmount.updateAndGet(v -> v + insertAmount); - return split( - capabilityConnections.capability, - capabilityConnections.getCount(), - insertAmount - ).stream(); - }) - .filter(t -> t.getB() > 0) - .sorted(Comparator.comparingInt(Tuple::getB)) - .map(Tuple::getA) - .toList(); - - sortedInserts.forEach(cap -> cap.receiveEnergy(transferRate, false)); - - if (excessPower > maxExtraInsertAmount.get()) { - extraSortedInserts.forEach(cap -> cap.receiveEnergy(transferRate, false)); - - int leftToExtract = maxInsertAmount.get() + maxExtraInsertAmount.get(); - for (int i = 0; i < sortedExtracts.size(); i++) { - int toExtract = (int) Math.ceil(leftToExtract / (double) (sortedExtracts.size() - i)); - leftToExtract -= sortedExtracts.get(i).extractEnergy(toExtract, false); - if (leftToExtract <= 0) break; - } - } else { - sortedExtracts.forEach(cap -> cap.extractEnergy(transferRate, false)); - - int leftToInsert = excessPower; - for (int i = 0; i < extraSortedInserts.size(); i++) { - int toInsert = (int) Math.ceil(leftToInsert / (double) (extraSortedInserts.size() - i)); - leftToInsert -= extraSortedInserts.get(i).receiveEnergy(toInsert, false); - if (leftToInsert <= 0) break; - } - } - } else if (maxInsertAmount.get() > maxExtractAmount.get()) { - int powerDeficit = maxInsertAmount.get() - maxExtractAmount.get(); - - AtomicReference maxExtraExtractAmount = new AtomicReference<>(0); - List extraSortedExtracts = insertAndExtracts.stream() - .map(Tuple::getB) - .flatMap(capabilityConnections -> { - int extractAmount = capabilityConnections.capability.extractEnergy( - transferRate * capabilityConnections.getCount(), - true - ); - if (extractAmount == 0) return Stream.empty(); - maxExtraExtractAmount.updateAndGet(v -> v + extractAmount); - return split( - capabilityConnections.capability, - capabilityConnections.getCount(), - extractAmount - ).stream(); - }) - .filter(t -> t.getB() > 0) - .sorted(Comparator.comparingInt(Tuple::getB)) - .map(Tuple::getA) - .toList(); - - sortedExtracts.forEach(cap -> cap.extractEnergy(transferRate, false)); - - if (powerDeficit > maxExtraExtractAmount.get()) { - extraSortedExtracts.forEach(cap -> cap.extractEnergy(transferRate, false)); - - int leftToInsert = maxExtractAmount.get() + maxExtraExtractAmount.get(); - for (int i = 0; i < sortedInserts.size(); i++) { - int toInsert = (int) Math.ceil(leftToInsert / (double) (sortedInserts.size() - i)); - leftToInsert -= sortedInserts.get(i).receiveEnergy(toInsert, false); - if (leftToInsert <= 0) break; - } - } else { - sortedInserts.forEach(cap -> cap.receiveEnergy(transferRate, false)); - - int leftToExtract = powerDeficit; - for (int i = 0; i < extraSortedExtracts.size(); i++) { - int toExtract = (int) Math.ceil(leftToExtract / (double) (extraSortedExtracts.size() - i)); - leftToExtract -= extraSortedExtracts.get(i).extractEnergy(toExtract, false); - if (leftToExtract <= 0) break; - } - } - } else { - sortedInserts.forEach(cap -> cap.receiveEnergy(transferRate, false)); - sortedExtracts.forEach(cap -> cap.extractEnergy(transferRate, false)); - } - } - - private List> split(IEnergyStorage storage, int partCount, int amountToSplit) { - int part = (int) Math.ceil((double) amountToSplit / partCount); - List> result = new ArrayList<>(partCount); - for (int i = 0; i < amountToSplit / part; i++) { - result.add(new Tuple<>(storage, part)); - } - if (amountToSplit % part != 0) result.add(new Tuple<>(storage, amountToSplit % part)); - return result; - } } diff --git a/src/main/java/com/almostreliable/kubeio/util/CapabilityConnections.java b/src/main/java/com/almostreliable/kubeio/util/CapabilityConnections.java deleted file mode 100644 index c5c6c0b..0000000 --- a/src/main/java/com/almostreliable/kubeio/util/CapabilityConnections.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.almostreliable.kubeio.util; - -import net.minecraftforge.energy.IEnergyStorage; - -public class CapabilityConnections -{ - public final IEnergyStorage capability; - private int count = 0; - - public CapabilityConnections(IEnergyStorage capability) { - this.capability = capability; - } - - public int getCount() { - return count; - } - - public void increment() { - count++; - } -}