From c9019c4e41dd8853c57bec1e8ae11fcebabc7ff7 Mon Sep 17 00:00:00 2001 From: Aedial Date: Thu, 14 May 2026 19:08:00 +0200 Subject: [PATCH] Add configurable polling rate to ME Hatches In Fixed polling mode (>0), the nextwork will only do IO at a fixed interval. In Adaptive polling mode (=0), the network will do IO at an adaptive interval between the 2 config values, but will not wake up on recipe end; it will instead wait for the next AE2 ticking request. --- .../client/gui/GuiMEBusPollingButton.java | 36 ++++ .../mmce/client/gui/GuiMEBusPollingRate.java | 128 +++++++++++++ .../mmce/client/gui/GuiMEFluidInputBus.java | 19 ++ .../mmce/client/gui/GuiMEFluidOutputBus.java | 19 ++ .../mmce/client/gui/GuiMEGasInputBus.java | 19 ++ .../mmce/client/gui/GuiMEGasOutputBus.java | 20 +++ .../mmce/client/gui/GuiMEItemInputBus.java | 26 +++ .../mmce/client/gui/GuiMEItemOutputBus.java | 18 +- .../block/appeng/BlockMEFluidOutputBus.java | 10 +- .../block/appeng/BlockMEGasOutputBus.java | 10 +- .../container/ContainerMEBusPollingRate.java | 62 +++++++ .../mmce/common/network/PktOpenMEBusGui.java | 85 +++++++++ .../network/PktSetMEBusPollingRate.java | 44 +++++ .../network/PktSwitchGuiMEOutputBus.java | 2 - .../mmce/common/tile/MEFluidInputBus.java | 80 ++++----- .../mmce/common/tile/MEFluidOutputBus.java | 40 +++-- .../mmce/common/tile/MEGasInputBus.java | 95 +++++----- .../mmce/common/tile/MEGasOutputBus.java | 48 +++-- .../mmce/common/tile/MEItemInputBus.java | 75 ++++---- .../mmce/common/tile/MEItemOutputBus.java | 36 ++-- .../mmce/common/tile/base/MEFluidBus.java | 2 +- .../mmce/common/tile/base/MEGasBus.java | 2 +- .../mmce/common/tile/base/MEItemBus.java | 2 +- .../tile/base/MEPollingMachineComponent.java | 170 ++++++++++++++++++ .../mmce/common/util/PollingRateUtils.java | 48 +++++ .../mmce/common/util/TickManagerHelper.java | 103 +++++++++++ .../modularmachinery/ModularMachinery.java | 2 + .../modularmachinery/client/ClientProxy.java | 7 + .../modularmachinery/common/CommonProxy.java | 9 + .../modularmachinery/common/data/Config.java | 20 +++ .../assets/modularmachinery/lang/en_US.lang | 4 + 31 files changed, 1038 insertions(+), 203 deletions(-) create mode 100644 src/main/java/github/kasuminova/mmce/client/gui/GuiMEBusPollingButton.java create mode 100644 src/main/java/github/kasuminova/mmce/client/gui/GuiMEBusPollingRate.java create mode 100644 src/main/java/github/kasuminova/mmce/common/container/ContainerMEBusPollingRate.java create mode 100644 src/main/java/github/kasuminova/mmce/common/network/PktOpenMEBusGui.java create mode 100644 src/main/java/github/kasuminova/mmce/common/network/PktSetMEBusPollingRate.java create mode 100644 src/main/java/github/kasuminova/mmce/common/tile/base/MEPollingMachineComponent.java create mode 100644 src/main/java/github/kasuminova/mmce/common/util/PollingRateUtils.java create mode 100644 src/main/java/github/kasuminova/mmce/common/util/TickManagerHelper.java diff --git a/src/main/java/github/kasuminova/mmce/client/gui/GuiMEBusPollingButton.java b/src/main/java/github/kasuminova/mmce/client/gui/GuiMEBusPollingButton.java new file mode 100644 index 00000000..10542cd4 --- /dev/null +++ b/src/main/java/github/kasuminova/mmce/client/gui/GuiMEBusPollingButton.java @@ -0,0 +1,36 @@ +package github.kasuminova.mmce.client.gui; + +import appeng.api.config.FullnessMode; +import appeng.api.config.Settings; +import appeng.client.gui.widgets.GuiImgButton; +import github.kasuminova.mmce.common.util.PollingRateUtils; +import hellfirepvp.modularmachinery.common.data.Config; +import net.minecraft.client.resources.I18n; + +import java.util.function.IntSupplier; + + +public class GuiMEBusPollingButton extends GuiImgButton { + + private final IntSupplier pollingRateSupplier; + + public GuiMEBusPollingButton(final int x, final int y, final IntSupplier pollingRateSupplier) { + super(x, y, Settings.FULLNESS_MODE, FullnessMode.FULL); + this.pollingRateSupplier = pollingRateSupplier; + } + + @Override + public String getMessage() { + int pollingRate = Math.max(0, this.pollingRateSupplier.getAsInt()); + int maxAdaptive = Config.meHatchAdaptivePollingMin; + int minAdaptive = Config.meHatchAdaptivePollingMax; + + String currentValue = pollingRate <= 0 + ? I18n.format("gui.mehatch.polling_rate.current_adaptive", maxAdaptive, minAdaptive) + : I18n.format("gui.mehatch.polling_rate.current", PollingRateUtils.format(pollingRate)); + + return I18n.format("gui.mehatch.polling_rate.name") + "\n\n" + + currentValue + "\n" + + I18n.format("gui.mehatch.polling_rate.tooltip"); + } +} \ No newline at end of file diff --git a/src/main/java/github/kasuminova/mmce/client/gui/GuiMEBusPollingRate.java b/src/main/java/github/kasuminova/mmce/client/gui/GuiMEBusPollingRate.java new file mode 100644 index 00000000..d824928f --- /dev/null +++ b/src/main/java/github/kasuminova/mmce/client/gui/GuiMEBusPollingRate.java @@ -0,0 +1,128 @@ +package github.kasuminova.mmce.client.gui; + +import appeng.client.gui.AEBaseGui; +import appeng.client.gui.widgets.GuiTabButton; +import github.kasuminova.mmce.common.container.ContainerMEBusPollingRate; +import github.kasuminova.mmce.common.network.PktOpenMEBusGui; +import github.kasuminova.mmce.common.network.PktSetMEBusPollingRate; +import github.kasuminova.mmce.common.tile.base.MEPollingMachineComponent; +import github.kasuminova.mmce.common.util.PollingRateUtils; +import hellfirepvp.modularmachinery.ModularMachinery; +import net.minecraft.client.gui.GuiButton; +import net.minecraft.client.resources.I18n; +import net.minecraft.entity.player.InventoryPlayer; +import org.jetbrains.annotations.NotNull; + +import java.io.IOException; + + +public class GuiMEBusPollingRate extends AEBaseGui implements ContainerMEBusPollingRate.IPollingRateListener { + + private GuiTabButton originalGuiBtn; + + private GuiButton plusTick; + private GuiButton plusSecond; + private GuiButton plusMinute; + private GuiButton plusHour; + private GuiButton plusDay; + private GuiButton minusTick; + private GuiButton minusSecond; + private GuiButton minusMinute; + private GuiButton minusHour; + private GuiButton minusDay; + + private final MEPollingMachineComponent host; + private int currentPollingRate = 0; + + public GuiMEBusPollingRate(final InventoryPlayer inventoryPlayer, final MEPollingMachineComponent host) { + super(new ContainerMEBusPollingRate(inventoryPlayer, host)); + this.host = host; + } + + @Override + public void initGui() { + super.initGui(); + + ((ContainerMEBusPollingRate) this.inventorySlots).setListener(this); + + this.buttonList.add(this.plusTick = new GuiButton(0, this.guiLeft + 23, this.guiTop + 32, 24, 20, "+1t")); + this.buttonList.add(this.plusSecond = new GuiButton(1, this.guiLeft + 49, this.guiTop + 32, 24, 20, "+1s")); + this.buttonList.add(this.plusMinute = new GuiButton(2, this.guiLeft + 75, this.guiTop + 32, 24, 20, "+1m")); + this.buttonList.add(this.plusHour = new GuiButton(3, this.guiLeft + 101, this.guiTop + 32, 24, 20, "+1h")); + this.buttonList.add(this.plusDay = new GuiButton(4, this.guiLeft + 127, this.guiTop + 32, 24, 20, "+1d")); + + this.buttonList.add(this.minusTick = new GuiButton(5, this.guiLeft + 23, this.guiTop + 69, 24, 20, "-1t")); + this.buttonList.add(this.minusSecond = new GuiButton(6, this.guiLeft + 49, this.guiTop + 69, 24, 20, "-1s")); + this.buttonList.add(this.minusMinute = new GuiButton(7, this.guiLeft + 75, this.guiTop + 69, 24, 20, "-1m")); + this.buttonList.add(this.minusHour = new GuiButton(8, this.guiLeft + 101, this.guiTop + 69, 24, 20, "-1h")); + this.buttonList.add(this.minusDay = new GuiButton(9, this.guiLeft + 127, this.guiTop + 69, 24, 20, "-1d")); + + this.buttonList.add(this.originalGuiBtn = new GuiTabButton( + this.guiLeft + 154, + this.guiTop, + this.host.getVisualItemStack(), + this.host.getVisualItemStack().getDisplayName(), + this.itemRender + )); + } + + @Override + public void onPollingRateChanged(int pollingRate) { + this.currentPollingRate = pollingRate; + } + + @Override + public void drawFG(final int offsetX, final int offsetY, final int mouseX, final int mouseY) { + this.fontRenderer.drawString(I18n.format("gui.mehatch.polling_rate.name"), 8, 6, 0x404040); + + String display = PollingRateUtils.format(this.currentPollingRate); + int textWidth = this.fontRenderer.getStringWidth(display); + this.fontRenderer.drawString(display, (this.xSize - textWidth) / 2, 57, 0xFFFFFF); + } + + @Override + public void drawBG(final int offsetX, final int offsetY, final int mouseX, final int mouseY) { + this.bindTexture("guis/priority.png"); + this.drawTexturedModalRect(offsetX, offsetY, 0, 0, this.xSize, this.ySize); + } + + @Override + protected void actionPerformed(@NotNull final GuiButton btn) throws IOException { + super.actionPerformed(btn); + + if (btn == this.originalGuiBtn) { + ModularMachinery.NET_CHANNEL.sendToServer( + new PktOpenMEBusGui(this.host.getPos(), this.host.getMainGuiType()) + ); + return; + } + + int delta = this.getButtonDelta(btn); + if (delta != 0) this.addPollingRate(delta); + } + + private int getButtonDelta(final GuiButton btn) { + if (btn == this.plusTick) return PollingRateUtils.TICKS_PER_TICK; + if (btn == this.plusSecond) return PollingRateUtils.TICKS_PER_SECOND; + if (btn == this.plusMinute) return PollingRateUtils.TICKS_PER_MINUTE; + if (btn == this.plusHour) return PollingRateUtils.TICKS_PER_HOUR; + if (btn == this.plusDay) return PollingRateUtils.TICKS_PER_DAY; + if (btn == this.minusTick) return -PollingRateUtils.TICKS_PER_TICK; + if (btn == this.minusSecond) return -PollingRateUtils.TICKS_PER_SECOND; + if (btn == this.minusMinute) return -PollingRateUtils.TICKS_PER_MINUTE; + if (btn == this.minusHour) return -PollingRateUtils.TICKS_PER_HOUR; + if (btn == this.minusDay) return -PollingRateUtils.TICKS_PER_DAY; + + return 0; + } + + private void addPollingRate(final int delta) { + long result = (long) this.currentPollingRate + delta; + result = Math.max(0L, Math.min(Integer.MAX_VALUE, result)); + this.currentPollingRate = (int) result; + + ModularMachinery.NET_CHANNEL.sendToServer( + new PktSetMEBusPollingRate(this.currentPollingRate) + ); + } +} \ No newline at end of file diff --git a/src/main/java/github/kasuminova/mmce/client/gui/GuiMEFluidInputBus.java b/src/main/java/github/kasuminova/mmce/client/gui/GuiMEFluidInputBus.java index 2ccc1317..e7bccf44 100644 --- a/src/main/java/github/kasuminova/mmce/client/gui/GuiMEFluidInputBus.java +++ b/src/main/java/github/kasuminova/mmce/client/gui/GuiMEFluidInputBus.java @@ -7,21 +7,26 @@ import appeng.fluids.client.gui.widgets.GuiFluidTank; import appeng.fluids.util.IAEFluidTank; import github.kasuminova.mmce.common.container.ContainerMEFluidInputBus; +import github.kasuminova.mmce.common.network.PktOpenMEBusGui; import github.kasuminova.mmce.common.tile.MEFluidInputBus; import github.kasuminova.mmce.common.tile.base.MEFluidBus; import hellfirepvp.modularmachinery.ModularMachinery; +import hellfirepvp.modularmachinery.common.CommonProxy.GuiType; import hellfirepvp.modularmachinery.common.base.Mods; +import net.minecraft.client.gui.GuiButton; import net.minecraft.client.resources.I18n; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.util.ResourceLocation; import net.minecraftforge.fml.common.ObfuscationReflectionHelper; import java.util.List; +import java.io.IOException; public class GuiMEFluidInputBus extends GuiUpgradeable { private static final ResourceLocation TEXTURES_INPUT_BUS = new ResourceLocation(ModularMachinery.MODID, "textures/gui/mefluidinputbus.png"); private final MEFluidInputBus bus; + private GuiMEBusPollingButton pollingRateBtn; public GuiMEFluidInputBus(final MEFluidInputBus te, final EntityPlayer player) { super(new ContainerMEFluidInputBus(te, player)); @@ -60,6 +65,20 @@ public void initGui() { @Override protected void addButtons() { + this.pollingRateBtn = new GuiMEBusPollingButton( + this.guiLeft - 18, this.guiTop + 8, this.bus::getPollingRate); + this.buttonList.add(this.pollingRateBtn); + } + + @Override + protected void actionPerformed(final GuiButton btn) throws IOException { + super.actionPerformed(btn); + + if (btn == this.pollingRateBtn) { + ModularMachinery.NET_CHANNEL.sendToServer( + new PktOpenMEBusGui(this.bus.getPos(), GuiType.ME_BUS_POLLING) + ); + } } @Override diff --git a/src/main/java/github/kasuminova/mmce/client/gui/GuiMEFluidOutputBus.java b/src/main/java/github/kasuminova/mmce/client/gui/GuiMEFluidOutputBus.java index 884ac284..9c6b1b9f 100644 --- a/src/main/java/github/kasuminova/mmce/client/gui/GuiMEFluidOutputBus.java +++ b/src/main/java/github/kasuminova/mmce/client/gui/GuiMEFluidOutputBus.java @@ -6,20 +6,25 @@ import appeng.fluids.client.gui.widgets.GuiFluidTank; import appeng.fluids.util.IAEFluidTank; import github.kasuminova.mmce.common.container.ContainerMEFluidOutputBus; +import github.kasuminova.mmce.common.network.PktOpenMEBusGui; import github.kasuminova.mmce.common.tile.MEFluidOutputBus; import hellfirepvp.modularmachinery.ModularMachinery; +import hellfirepvp.modularmachinery.common.CommonProxy.GuiType; import hellfirepvp.modularmachinery.common.base.Mods; +import net.minecraft.client.gui.GuiButton; import net.minecraft.client.resources.I18n; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.util.ResourceLocation; import net.minecraftforge.fml.common.ObfuscationReflectionHelper; import java.util.List; +import java.io.IOException; public class GuiMEFluidOutputBus extends GuiUpgradeable { private static final ResourceLocation TEXTURES_OUTPUT_BUS = new ResourceLocation(ModularMachinery.MODID, "textures/gui/mefluidoutputbus.png"); private final MEFluidOutputBus bus; + private GuiMEBusPollingButton pollingRateBtn; public GuiMEFluidOutputBus(final MEFluidOutputBus te, final EntityPlayer player) { super(new ContainerMEFluidOutputBus(te, player)); @@ -55,6 +60,20 @@ public void initGui() { @Override protected void addButtons() { + this.pollingRateBtn = new GuiMEBusPollingButton( + this.guiLeft - 18, this.guiTop + 8, this.bus::getPollingRate); + this.buttonList.add(this.pollingRateBtn); + } + + @Override + protected void actionPerformed(final GuiButton btn) throws IOException { + super.actionPerformed(btn); + + if (btn == this.pollingRateBtn) { + ModularMachinery.NET_CHANNEL.sendToServer( + new PktOpenMEBusGui(this.bus.getPos(), GuiType.ME_BUS_POLLING) + ); + } } @Override diff --git a/src/main/java/github/kasuminova/mmce/client/gui/GuiMEGasInputBus.java b/src/main/java/github/kasuminova/mmce/client/gui/GuiMEGasInputBus.java index 78fbf2c7..7824f402 100644 --- a/src/main/java/github/kasuminova/mmce/client/gui/GuiMEGasInputBus.java +++ b/src/main/java/github/kasuminova/mmce/client/gui/GuiMEGasInputBus.java @@ -12,9 +12,12 @@ import com.mekeng.github.network.packet.CGasSlotSync; import com.mekeng.github.util.Utils; import github.kasuminova.mmce.common.container.ContainerMEGasInputBus; +import github.kasuminova.mmce.common.network.PktOpenMEBusGui; import github.kasuminova.mmce.common.tile.MEGasInputBus; import github.kasuminova.mmce.common.tile.base.MEFluidBus; import hellfirepvp.modularmachinery.ModularMachinery; +import hellfirepvp.modularmachinery.common.CommonProxy.GuiType; +import net.minecraft.client.gui.GuiButton; import mekanism.api.gas.GasStack; import mezz.jei.api.gui.IGhostIngredientHandler; import net.minecraft.client.resources.I18n; @@ -24,6 +27,7 @@ import javax.annotation.Nonnull; import java.awt.Rectangle; +import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -32,6 +36,7 @@ public class GuiMEGasInputBus extends GuiUpgradeable implements IJEIGhostIngredi private static final ResourceLocation TEXTURES_INPUT_BUS = new ResourceLocation(ModularMachinery.MODID, "textures/gui/mefluidinputbus.png"); private final MEGasInputBus bus; + private GuiMEBusPollingButton pollingRateBtn; public GuiMEGasInputBus(final MEGasInputBus te, final EntityPlayer player) { super(new ContainerMEGasInputBus(te, player)); @@ -54,6 +59,20 @@ public void initGui() { @Override protected void addButtons() { + this.pollingRateBtn = new GuiMEBusPollingButton( + this.guiLeft - 18, this.guiTop + 8, this.bus::getPollingRate); + this.buttonList.add(this.pollingRateBtn); + } + + @Override + protected void actionPerformed(final GuiButton btn) throws IOException { + super.actionPerformed(btn); + + if (btn == this.pollingRateBtn) { + ModularMachinery.NET_CHANNEL.sendToServer( + new PktOpenMEBusGui(this.bus.getPos(), GuiType.ME_BUS_POLLING) + ); + } } @Override diff --git a/src/main/java/github/kasuminova/mmce/client/gui/GuiMEGasOutputBus.java b/src/main/java/github/kasuminova/mmce/client/gui/GuiMEGasOutputBus.java index d981f53b..3d003764 100644 --- a/src/main/java/github/kasuminova/mmce/client/gui/GuiMEGasOutputBus.java +++ b/src/main/java/github/kasuminova/mmce/client/gui/GuiMEGasOutputBus.java @@ -5,16 +5,22 @@ import com.mekeng.github.client.slots.SlotGasTank; import com.mekeng.github.common.me.inventory.impl.GasInventory; import github.kasuminova.mmce.common.container.ContainerMEGasOutputBus; +import github.kasuminova.mmce.common.network.PktOpenMEBusGui; import github.kasuminova.mmce.common.tile.MEGasOutputBus; import hellfirepvp.modularmachinery.ModularMachinery; +import hellfirepvp.modularmachinery.common.CommonProxy.GuiType; +import net.minecraft.client.gui.GuiButton; import net.minecraft.client.resources.I18n; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.util.ResourceLocation; +import java.io.IOException; + public class GuiMEGasOutputBus extends GuiUpgradeable { private static final ResourceLocation TEXTURES_OUTPUT_BUS = new ResourceLocation(ModularMachinery.MODID, "textures/gui/mefluidoutputbus.png"); private final MEGasOutputBus bus; + private GuiMEBusPollingButton pollingRateBtn; public GuiMEGasOutputBus(final MEGasOutputBus te, final EntityPlayer player) { super(new ContainerMEGasOutputBus(te, player)); @@ -35,6 +41,20 @@ public void initGui() { @Override protected void addButtons() { + this.pollingRateBtn = new GuiMEBusPollingButton( + this.guiLeft - 18, this.guiTop + 8, this.bus::getPollingRate); + this.buttonList.add(this.pollingRateBtn); + } + + @Override + protected void actionPerformed(final GuiButton btn) throws IOException { + super.actionPerformed(btn); + + if (btn == this.pollingRateBtn) { + ModularMachinery.NET_CHANNEL.sendToServer( + new PktOpenMEBusGui(this.bus.getPos(), GuiType.ME_BUS_POLLING) + ); + } } @Override diff --git a/src/main/java/github/kasuminova/mmce/client/gui/GuiMEItemInputBus.java b/src/main/java/github/kasuminova/mmce/client/gui/GuiMEItemInputBus.java index 24ae90d3..e57a6c1d 100644 --- a/src/main/java/github/kasuminova/mmce/client/gui/GuiMEItemInputBus.java +++ b/src/main/java/github/kasuminova/mmce/client/gui/GuiMEItemInputBus.java @@ -11,15 +11,18 @@ import appeng.util.item.AEItemStack; import github.kasuminova.mmce.common.container.ContainerMEItemInputBus; import github.kasuminova.mmce.common.network.PktMEInputBusInvAction; +import github.kasuminova.mmce.common.network.PktOpenMEBusGui; import github.kasuminova.mmce.common.tile.MEItemInputBus; import hellfirepvp.modularmachinery.ModularMachinery; import hellfirepvp.modularmachinery.client.ClientProxy; +import hellfirepvp.modularmachinery.common.CommonProxy.GuiType; import hellfirepvp.modularmachinery.common.util.MiscUtils; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectArrayList; import it.unimi.dsi.fastutil.objects.ObjectLists; import mezz.jei.api.gui.IGhostIngredientHandler; import net.minecraft.client.gui.FontRenderer; +import net.minecraft.client.gui.GuiButton; import net.minecraft.client.renderer.GlStateManager; import net.minecraft.client.resources.I18n; import net.minecraft.entity.player.EntityPlayer; @@ -43,12 +46,35 @@ public class GuiMEItemInputBus extends GuiMEItemBus implements IJEIGhostIngredie private static final ResourceLocation TEXTURES_INPUT_BUS = new ResourceLocation(ModularMachinery.MODID, "textures/gui/meiteminputbus.png"); protected final Map, Object> mapTargetSlot = new Object2ObjectOpenHashMap<>(); + private final MEItemInputBus bus; + private GuiMEBusPollingButton pollingRateBtn; public GuiMEItemInputBus(final MEItemInputBus te, final EntityPlayer player) { super(new ContainerMEItemInputBus(te, player)); + this.bus = te; this.ySize = 204; } + @Override + public void initGui() { + super.initGui(); + + this.pollingRateBtn = new GuiMEBusPollingButton( + this.guiLeft - 18, this.guiTop + 8, this.bus::getPollingRate); + this.buttonList.add(this.pollingRateBtn); + } + + @Override + protected void actionPerformed(@Nonnull final GuiButton btn) throws IOException { + super.actionPerformed(btn); + + if (btn == this.pollingRateBtn) { + ModularMachinery.NET_CHANNEL.sendToServer( + new PktOpenMEBusGui(this.bus.getPos(), GuiType.ME_BUS_POLLING) + ); + } + } + private static List getAddActionInfo() { List tooltip = new ArrayList<>(); tooltip.add(TextFormatting.GRAY + I18n.format("gui.meiteminputbus.inv_action")); diff --git a/src/main/java/github/kasuminova/mmce/client/gui/GuiMEItemOutputBus.java b/src/main/java/github/kasuminova/mmce/client/gui/GuiMEItemOutputBus.java index 2a14981a..cfc31e93 100644 --- a/src/main/java/github/kasuminova/mmce/client/gui/GuiMEItemOutputBus.java +++ b/src/main/java/github/kasuminova/mmce/client/gui/GuiMEItemOutputBus.java @@ -5,9 +5,11 @@ import appeng.client.gui.widgets.GuiImgButton; import appeng.core.localization.GuiText; import github.kasuminova.mmce.common.container.ContainerMEItemOutputBus; +import github.kasuminova.mmce.common.network.PktOpenMEBusGui; import github.kasuminova.mmce.common.network.PktSwitchGuiMEOutputBus; import github.kasuminova.mmce.common.tile.MEItemOutputBus; import hellfirepvp.modularmachinery.ModularMachinery; +import hellfirepvp.modularmachinery.common.CommonProxy.GuiType; import net.minecraft.client.gui.GuiButton; import net.minecraft.client.renderer.GlStateManager; import net.minecraft.client.resources.I18n; @@ -21,6 +23,7 @@ public class GuiMEItemOutputBus extends GuiMEItemBus { private static final ResourceLocation TEXTURES_OUTPUT_BUS = new ResourceLocation("appliedenergistics2", "textures/guis/skychest.png"); private CustomStackSizeButton stackSizeBtn; + private GuiMEBusPollingButton pollingRateBtn; private final MEItemOutputBus outputBus; public GuiMEItemOutputBus(final MEItemOutputBus te, final EntityPlayer player) { @@ -33,11 +36,12 @@ public GuiMEItemOutputBus(final MEItemOutputBus te, final EntityPlayer player) { public void initGui() { super.initGui(); - this.stackSizeBtn = new CustomStackSizeButton( - this.guiLeft - 18, - this.guiTop + 8 - ); + this.stackSizeBtn = new CustomStackSizeButton(this.guiLeft - 18, this.guiTop + 8); this.buttonList.add(this.stackSizeBtn); + + this.pollingRateBtn = new GuiMEBusPollingButton( + this.guiLeft - 18, this.guiTop + 28, this.outputBus::getPollingRate); + this.buttonList.add(this.pollingRateBtn); } @Override @@ -46,7 +50,11 @@ protected void actionPerformed(@NotNull final GuiButton btn) throws IOException if (btn == this.stackSizeBtn) { ModularMachinery.NET_CHANNEL.sendToServer( - new PktSwitchGuiMEOutputBus(this.outputBus.getPos(), 1) + new PktSwitchGuiMEOutputBus(this.outputBus.getPos(), 1) + ); + } else if (btn == this.pollingRateBtn) { + ModularMachinery.NET_CHANNEL.sendToServer( + new PktOpenMEBusGui(this.outputBus.getPos(), GuiType.ME_BUS_POLLING) ); } } diff --git a/src/main/java/github/kasuminova/mmce/common/block/appeng/BlockMEFluidOutputBus.java b/src/main/java/github/kasuminova/mmce/common/block/appeng/BlockMEFluidOutputBus.java index 52d832d0..7b6d3116 100644 --- a/src/main/java/github/kasuminova/mmce/common/block/appeng/BlockMEFluidOutputBus.java +++ b/src/main/java/github/kasuminova/mmce/common/block/appeng/BlockMEFluidOutputBus.java @@ -1,8 +1,10 @@ package github.kasuminova.mmce.common.block.appeng; +import appeng.api.implementations.items.IMemoryCard; import github.kasuminova.mmce.common.tile.MEFluidOutputBus; import hellfirepvp.modularmachinery.ModularMachinery; import hellfirepvp.modularmachinery.common.CommonProxy; +import net.minecraft.item.ItemStack; import net.minecraft.block.state.IBlockState; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.tileentity.TileEntity; @@ -20,7 +22,13 @@ public class BlockMEFluidOutputBus extends BlockMEFluidBus { public boolean onBlockActivated(@Nonnull final World worldIn, @Nonnull final BlockPos pos, @Nonnull final IBlockState state, @Nonnull final EntityPlayer playerIn, @Nonnull final EnumHand hand, @Nonnull final EnumFacing facing, final float hitX, final float hitY, final float hitZ) { if (!worldIn.isRemote) { TileEntity te = worldIn.getTileEntity(pos); - if (te instanceof MEFluidOutputBus) { + if (te instanceof MEFluidOutputBus outputBus) { + ItemStack heldItem = playerIn.getHeldItem(hand); + if (heldItem.getItem() instanceof IMemoryCard memoryCard) { + boolean handled = handleSettingsTransfer(outputBus, memoryCard, playerIn, heldItem); + if (handled) return true; + } + playerIn.openGui(ModularMachinery.MODID, CommonProxy.GuiType.ME_FLUID_OUTPUT_BUS.ordinal(), worldIn, pos.getX(), pos.getY(), pos.getZ()); } } diff --git a/src/main/java/github/kasuminova/mmce/common/block/appeng/BlockMEGasOutputBus.java b/src/main/java/github/kasuminova/mmce/common/block/appeng/BlockMEGasOutputBus.java index fb1a5634..69c02343 100644 --- a/src/main/java/github/kasuminova/mmce/common/block/appeng/BlockMEGasOutputBus.java +++ b/src/main/java/github/kasuminova/mmce/common/block/appeng/BlockMEGasOutputBus.java @@ -1,10 +1,12 @@ package github.kasuminova.mmce.common.block.appeng; +import appeng.api.implementations.items.IMemoryCard; import github.kasuminova.mmce.common.tile.MEGasOutputBus; import hellfirepvp.modularmachinery.ModularMachinery; import hellfirepvp.modularmachinery.common.CommonProxy; import net.minecraft.block.state.IBlockState; import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.ItemStack; import net.minecraft.tileentity.TileEntity; import net.minecraft.util.EnumFacing; import net.minecraft.util.EnumHand; @@ -20,7 +22,13 @@ public class BlockMEGasOutputBus extends BlockMEGasBus { public boolean onBlockActivated(@Nonnull final World worldIn, @Nonnull final BlockPos pos, @Nonnull final IBlockState state, @Nonnull final EntityPlayer playerIn, @Nonnull final EnumHand hand, @Nonnull final EnumFacing facing, final float hitX, final float hitY, final float hitZ) { if (!worldIn.isRemote) { TileEntity te = worldIn.getTileEntity(pos); - if (te instanceof MEGasOutputBus) { + if (te instanceof MEGasOutputBus outputBus) { + ItemStack heldItem = playerIn.getHeldItem(hand); + if (heldItem.getItem() instanceof IMemoryCard memoryCard) { + boolean handled = handleSettingsTransfer(outputBus, memoryCard, playerIn, heldItem); + if (handled) return true; + } + playerIn.openGui(ModularMachinery.MODID, CommonProxy.GuiType.ME_GAS_OUTPUT_BUS.ordinal(), worldIn, pos.getX(), pos.getY(), pos.getZ()); } } diff --git a/src/main/java/github/kasuminova/mmce/common/container/ContainerMEBusPollingRate.java b/src/main/java/github/kasuminova/mmce/common/container/ContainerMEBusPollingRate.java new file mode 100644 index 00000000..4e64dad4 --- /dev/null +++ b/src/main/java/github/kasuminova/mmce/common/container/ContainerMEBusPollingRate.java @@ -0,0 +1,62 @@ +package github.kasuminova.mmce.common.container; + +import appeng.api.config.SecurityPermissions; +import appeng.container.AEBaseContainer; +import appeng.container.guisync.GuiSync; +import appeng.util.Platform; +import github.kasuminova.mmce.common.tile.base.MEPollingMachineComponent; +import net.minecraft.entity.player.InventoryPlayer; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; + + +public class ContainerMEBusPollingRate extends AEBaseContainer { + + private final MEPollingMachineComponent host; + + @SideOnly(Side.CLIENT) + private IPollingRateListener listener; + + @GuiSync(0) + public int pollingRate; + + public ContainerMEBusPollingRate(final InventoryPlayer inventoryPlayer, final MEPollingMachineComponent host) { + super(inventoryPlayer, host); + this.host = host; + this.pollingRate = host.getPollingRate(); + } + + @SideOnly(Side.CLIENT) + public void setListener(final IPollingRateListener listener) { + this.listener = listener; + this.listener.onPollingRateChanged(this.pollingRate); + } + + public void setPollingRate(final int pollingRate) { + this.host.setPollingRate(pollingRate); + this.pollingRate = this.host.getPollingRate(); + } + + @Override + public void detectAndSendChanges() { + this.verifyPermissions(SecurityPermissions.BUILD, false); + + super.detectAndSendChanges(); + + if (Platform.isServer()) this.pollingRate = this.host.getPollingRate(); + } + + @Override + public void onUpdate(final String field, final Object oldValue, final Object newValue) { + if (field.equals("pollingRate") && this.listener != null) { + this.listener.onPollingRateChanged(this.pollingRate); + } + + super.onUpdate(field, oldValue, newValue); + } + + @SideOnly(Side.CLIENT) + public interface IPollingRateListener { + void onPollingRateChanged(int pollingRate); + } +} \ No newline at end of file diff --git a/src/main/java/github/kasuminova/mmce/common/network/PktOpenMEBusGui.java b/src/main/java/github/kasuminova/mmce/common/network/PktOpenMEBusGui.java new file mode 100644 index 00000000..1bf42e44 --- /dev/null +++ b/src/main/java/github/kasuminova/mmce/common/network/PktOpenMEBusGui.java @@ -0,0 +1,85 @@ +package github.kasuminova.mmce.common.network; + +import github.kasuminova.mmce.common.integration.ModIntegrationAE2; +import github.kasuminova.mmce.common.tile.base.MEPollingMachineComponent; +import hellfirepvp.modularmachinery.ModularMachinery; +import hellfirepvp.modularmachinery.common.CommonProxy.GuiType; +import io.netty.buffer.ByteBuf; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.math.BlockPos; +import net.minecraftforge.fml.common.network.simpleimpl.IMessage; +import net.minecraftforge.fml.common.network.simpleimpl.IMessageHandler; +import net.minecraftforge.fml.common.network.simpleimpl.MessageContext; + + +public class PktOpenMEBusGui implements IMessage, IMessageHandler { + + private BlockPos pos = BlockPos.ORIGIN; + private int guiType = 0; + + public PktOpenMEBusGui() { + } + + public PktOpenMEBusGui(final BlockPos pos, final GuiType guiType) { + this.pos = pos; + this.guiType = guiType.ordinal(); + } + + @Override + public void fromBytes(final ByteBuf buf) { + this.pos = BlockPos.fromLong(buf.readLong()); + this.guiType = buf.readInt(); + } + + @Override + public void toBytes(final ByteBuf buf) { + buf.writeLong(this.pos.toLong()); + buf.writeInt(this.guiType); + } + + @Override + public IMessage onMessage(final PktOpenMEBusGui message, final MessageContext ctx) { + EntityPlayerMP player = ctx.getServerHandler().player; + + player.getServerWorld().addScheduledTask(() -> { + GuiType[] guiTypes = GuiType.values(); + if (message.guiType < 0 || message.guiType >= guiTypes.length) { + return; + } + + GuiType guiType = guiTypes[message.guiType]; + if (!isSupportedGui(guiType)) return; + + TileEntity tileEntity = player.world.getTileEntity(message.pos); + if (!(tileEntity instanceof MEPollingMachineComponent host)) { + return; + } + + if (ModIntegrationAE2.securityCheck(player, host.getProxy())) { + return; + } + + player.openGui( + ModularMachinery.MODID, + guiType.ordinal(), + player.world, + message.pos.getX(), + message.pos.getY(), + message.pos.getZ() + ); + }); + + return null; + } + + private static boolean isSupportedGui(GuiType guiType) { + return guiType == GuiType.ME_ITEM_OUTPUT_BUS + || guiType == GuiType.ME_ITEM_INPUT_BUS + || guiType == GuiType.ME_FLUID_OUTPUT_BUS + || guiType == GuiType.ME_FLUID_INPUT_BUS + || guiType == GuiType.ME_GAS_OUTPUT_BUS + || guiType == GuiType.ME_GAS_INPUT_BUS + || guiType == GuiType.ME_BUS_POLLING; + } +} \ No newline at end of file diff --git a/src/main/java/github/kasuminova/mmce/common/network/PktSetMEBusPollingRate.java b/src/main/java/github/kasuminova/mmce/common/network/PktSetMEBusPollingRate.java new file mode 100644 index 00000000..1d35a21f --- /dev/null +++ b/src/main/java/github/kasuminova/mmce/common/network/PktSetMEBusPollingRate.java @@ -0,0 +1,44 @@ +package github.kasuminova.mmce.common.network; + +import github.kasuminova.mmce.common.container.ContainerMEBusPollingRate; +import io.netty.buffer.ByteBuf; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraftforge.fml.common.network.simpleimpl.IMessage; +import net.minecraftforge.fml.common.network.simpleimpl.IMessageHandler; +import net.minecraftforge.fml.common.network.simpleimpl.MessageContext; + + +public class PktSetMEBusPollingRate implements IMessage, IMessageHandler { + + private int pollingRate; + + public PktSetMEBusPollingRate() { + } + + public PktSetMEBusPollingRate(int pollingRate) { + this.pollingRate = pollingRate; + } + + @Override + public void fromBytes(ByteBuf buf) { + this.pollingRate = buf.readInt(); + } + + @Override + public void toBytes(ByteBuf buf) { + buf.writeInt(this.pollingRate); + } + + @Override + public IMessage onMessage(final PktSetMEBusPollingRate message, final MessageContext ctx) { + EntityPlayerMP player = ctx.getServerHandler().player; + + player.getServerWorld().addScheduledTask(() -> { + if (player.openContainer instanceof ContainerMEBusPollingRate container) { + container.setPollingRate(Math.max(0, message.pollingRate)); + } + }); + + return null; + } +} \ No newline at end of file diff --git a/src/main/java/github/kasuminova/mmce/common/network/PktSwitchGuiMEOutputBus.java b/src/main/java/github/kasuminova/mmce/common/network/PktSwitchGuiMEOutputBus.java index b3745127..f01c6f5a 100644 --- a/src/main/java/github/kasuminova/mmce/common/network/PktSwitchGuiMEOutputBus.java +++ b/src/main/java/github/kasuminova/mmce/common/network/PktSwitchGuiMEOutputBus.java @@ -46,8 +46,6 @@ public IMessage onMessage(final PktSwitchGuiMEOutputBus message, final MessageCo return; } - player.closeScreen(); - if (message.guiType == 0) { player.openGui( ModularMachinery.MODID, diff --git a/src/main/java/github/kasuminova/mmce/common/tile/MEFluidInputBus.java b/src/main/java/github/kasuminova/mmce/common/tile/MEFluidInputBus.java index cc9bf3ff..d08f598d 100644 --- a/src/main/java/github/kasuminova/mmce/common/tile/MEFluidInputBus.java +++ b/src/main/java/github/kasuminova/mmce/common/tile/MEFluidInputBus.java @@ -10,7 +10,7 @@ import appeng.me.GridAccessException; import appeng.util.Platform; import github.kasuminova.mmce.common.tile.base.MEFluidBus; -import hellfirepvp.modularmachinery.ModularMachinery; +import hellfirepvp.modularmachinery.common.CommonProxy.GuiType; import hellfirepvp.modularmachinery.common.lib.ItemsMM; import hellfirepvp.modularmachinery.common.machine.IOType; import hellfirepvp.modularmachinery.common.machine.MachineComponent; @@ -52,44 +52,17 @@ public IAEFluidTank getConfig() { @Nonnull @Override public TickingRequest getTickingRequest(@Nonnull final IGridNode node) { - return new TickingRequest(10, 120, !needsUpdate(), true); - } - - private boolean needsUpdate() { - int capacity = tanks.getCapacity(); - - for (int slot = 0; slot < config.getSlots(); slot++) { - IAEFluidStack cfgStack = config.getFluidInSlot(slot); - IAEFluidStack invStack = tanks.getFluidInSlot(slot); - - if (cfgStack == null) { - if (invStack != null) { - return true; - } - continue; - } - - if (invStack == null) { - return true; - } - - if (!cfgStack.equals(invStack) || invStack.getStackSize() != capacity) { - return true; - } - } - return false; + return this.getPollingTickingRequest(); } @Nonnull @Override public TickRateModulation tickingRequest(@Nonnull final IGridNode node, final int ticksSinceLastCall) { - if (!proxy.isActive()) { - return TickRateModulation.IDLE; - } + if (!proxy.isActive()) return this.getInactiveTickRateModulation(); int[] needUpdateSlots = getNeedUpdateSlots(); if (needUpdateSlots.length == 0) { - return TickRateModulation.SLOWER; + return this.getNoWorkTickRateModulation(ticksSinceLastCall); } ReadWriteLock rwLock = tanks.getRWLock(); @@ -162,7 +135,7 @@ public TickRateModulation tickingRequest(@Nonnull final IGridNode node, final in inTick = false; rwLock.writeLock().unlock(); - return successAtLeastOnce ? TickRateModulation.FASTER : TickRateModulation.SLOWER; + return this.getWorkTickRateModulation(successAtLeastOnce, ticksSinceLastCall); } catch (GridAccessException e) { inTick = false; changedSlots = new boolean[TANK_SLOT_AMOUNT]; @@ -171,6 +144,29 @@ public TickRateModulation tickingRequest(@Nonnull final IGridNode node, final in } } + @Override + protected boolean hasWorkToDo() { + int capacity = tanks.getCapacity(); + + for (int slot = 0; slot < config.getSlots(); slot++) { + IAEFluidStack cfgStack = config.getFluidInSlot(slot); + IAEFluidStack invStack = tanks.getFluidInSlot(slot); + + if (cfgStack == null) { + if (invStack != null) return true; + continue; + } + + if (invStack == null) return true; + + if (!cfgStack.equals(invStack) || invStack.getStackSize() != capacity) { + return true; + } + } + + return false; + } + private IAEFluidStack extractStackFromAE(final IMEMonitor inv, final IAEFluidStack stack) throws GridAccessException { return Platform.poweredExtraction(proxy.getEnergy(), inv, stack.copy(), source); } @@ -200,34 +196,24 @@ public boolean canGroupInput() { return true; } + @Nonnull @Override - public void markNoUpdate() { - if (needsUpdate()) { - try { - proxy.getTick().alertDevice(proxy.getNode()); - } catch (GridAccessException e) { - // NO-OP - } - } - - super.markNoUpdate(); + public GuiType getMainGuiType() { + return GuiType.ME_FLUID_INPUT_BUS; } @Override public NBTTagCompound downloadSettings() { NBTTagCompound tag = new NBTTagCompound(); config.writeToNBT(tag, CONFIG_TAG_KEY); + this.writePollingSettings(tag); return tag; } @Override public void uploadSettings(NBTTagCompound settings) { config.readFromNBT(settings, CONFIG_TAG_KEY); + this.readPollingSettings(settings); this.markForUpdate(); - try { - proxy.getTick().alertDevice(proxy.getNode()); - } catch (GridAccessException e) { - ModularMachinery.log.warn("Error while uploading settings", e); - } } } diff --git a/src/main/java/github/kasuminova/mmce/common/tile/MEFluidOutputBus.java b/src/main/java/github/kasuminova/mmce/common/tile/MEFluidOutputBus.java index dce6e8e8..cf19191f 100644 --- a/src/main/java/github/kasuminova/mmce/common/tile/MEFluidOutputBus.java +++ b/src/main/java/github/kasuminova/mmce/common/tile/MEFluidOutputBus.java @@ -8,17 +8,19 @@ import appeng.me.GridAccessException; import appeng.util.Platform; import github.kasuminova.mmce.common.tile.base.MEFluidBus; +import hellfirepvp.modularmachinery.common.CommonProxy.GuiType; import hellfirepvp.modularmachinery.common.lib.ItemsMM; import hellfirepvp.modularmachinery.common.machine.IOType; import hellfirepvp.modularmachinery.common.machine.MachineComponent; import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTTagCompound; import net.minecraftforge.fluids.capability.IFluidHandler; import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.util.concurrent.locks.ReadWriteLock; -public class MEFluidOutputBus extends MEFluidBus { +public class MEFluidOutputBus extends MEFluidBus implements SettingsTransfer { public MEFluidOutputBus() { this.tanks.setOneFluidOneSlot(true); @@ -43,18 +45,18 @@ public IFluidHandler getContainerProvider() { @Nonnull @Override public TickingRequest getTickingRequest(@Nonnull final IGridNode node) { - return new TickingRequest(5, 60, !hasFluid(), true); + return this.getPollingTickingRequest(); } @Nonnull @Override public TickRateModulation tickingRequest(@Nonnull final IGridNode node, final int ticksSinceLastCall) { if (!proxy.isActive()) { - return TickRateModulation.IDLE; + return this.getInactiveTickRateModulation(); } int[] needUpdateSlots = getNeedUpdateSlots(); if (needUpdateSlots.length == 0) { - return TickRateModulation.SLOWER; + return this.getNoWorkTickRateModulation(ticksSinceLastCall); } ReadWriteLock rwLock = tanks.getRWLock(); @@ -88,7 +90,7 @@ public TickRateModulation tickingRequest(@Nonnull final IGridNode node, final in inTick = false; rwLock.writeLock().unlock(); - return successAtLeastOnce ? TickRateModulation.FASTER : TickRateModulation.SLOWER; + return this.getWorkTickRateModulation(successAtLeastOnce, ticksSinceLastCall); } catch (GridAccessException e) { inTick = false; changedSlots = new boolean[TANK_SLOT_AMOUNT]; @@ -97,6 +99,17 @@ public TickRateModulation tickingRequest(@Nonnull final IGridNode node, final in } } + @Nonnull + @Override + public GuiType getMainGuiType() { + return GuiType.ME_FLUID_OUTPUT_BUS; + } + + @Override + protected boolean hasWorkToDo() { + return this.hasFluid(); + } + public boolean hasFluid() { for (int i = 0; i < tanks.getSlots(); i++) { IAEFluidStack stack = tanks.getFluidInSlot(i); @@ -108,16 +121,15 @@ public boolean hasFluid() { } @Override - public void markNoUpdate() { - if (hasFluid()) { - try { - proxy.getTick().alertDevice(proxy.getNode()); - } catch (GridAccessException e) { - // NO-OP - } - } + public NBTTagCompound downloadSettings() { + NBTTagCompound tag = new NBTTagCompound(); + this.writePollingSettings(tag); + return tag; + } - super.markNoUpdate(); + @Override + public void uploadSettings(NBTTagCompound settings) { + this.readPollingSettings(settings); } diff --git a/src/main/java/github/kasuminova/mmce/common/tile/MEGasInputBus.java b/src/main/java/github/kasuminova/mmce/common/tile/MEGasInputBus.java index f43a5a5c..722a32bd 100644 --- a/src/main/java/github/kasuminova/mmce/common/tile/MEGasInputBus.java +++ b/src/main/java/github/kasuminova/mmce/common/tile/MEGasInputBus.java @@ -11,7 +11,7 @@ import com.mekeng.github.common.me.inventory.impl.GasInventory; import github.kasuminova.mmce.common.tile.base.MEGasBus; import github.kasuminova.mmce.common.util.IExtendedGasHandler; -import hellfirepvp.modularmachinery.ModularMachinery; +import hellfirepvp.modularmachinery.common.CommonProxy.GuiType; import hellfirepvp.modularmachinery.common.crafting.ComponentType; import hellfirepvp.modularmachinery.common.lib.ComponentTypesMM; import hellfirepvp.modularmachinery.common.lib.ItemsMM; @@ -56,40 +56,21 @@ public GasInventory getConfig() { @Nonnull @Override public TickingRequest getTickingRequest(@Nonnull final IGridNode node) { - return new TickingRequest(10, 120, !needsUpdate(), true); - } - - private boolean needsUpdate() { - int capacity = tanks.getTanks()[0].getMaxGas(); - - for (int slot = 0; slot < config.size(); slot++) { - GasStack cfgStack = config.getGasStack(slot); - GasStack invStack = tanks.getGasStack(slot); - - if (cfgStack == null) { - if (invStack != null) { - return true; - } - continue; - } - - if (invStack == null) { - return true; - } - - if (!cfgStack.isGasEqual(invStack) || invStack.amount != capacity) { - return true; - } - } - return false; + return this.getPollingTickingRequest(); } @Nonnull @Override public TickRateModulation tickingRequest(@Nonnull final IGridNode node, final int ticksSinceLastCall) { if (!proxy.isActive()) { - return TickRateModulation.IDLE; + return this.getInactiveTickRateModulation(); } + + int[] needUpdateSlots = getNeedUpdateSlots(); + if (needUpdateSlots.length == 0) { + return this.getNoWorkTickRateModulation(ticksSinceLastCall); + } + inTick = true; try { boolean successAtLeastOnce = false; @@ -98,7 +79,7 @@ public TickRateModulation tickingRequest(@Nonnull final IGridNode node, final in int capacity = tanks.getTanks()[0].getMaxGas(); synchronized (tanks) { - for (final int slot : getNeedUpdateSlots()) { + for (final int slot : needUpdateSlots) { changedSlots[slot] = false; GasStack cfgStack = config.getGasStack(slot); GasStack invStack = tanks.getGasStack(slot); @@ -166,7 +147,7 @@ public TickRateModulation tickingRequest(@Nonnull final IGridNode node, final in } } inTick = false; - return successAtLeastOnce ? TickRateModulation.FASTER : TickRateModulation.SLOWER; + return this.getWorkTickRateModulation(successAtLeastOnce, ticksSinceLastCall); } catch (GridAccessException e) { inTick = false; changedSlots = new boolean[TANK_SLOT_AMOUNT]; @@ -174,6 +155,33 @@ public TickRateModulation tickingRequest(@Nonnull final IGridNode node, final in } } + @Override + protected boolean hasWorkToDo() { + int capacity = tanks.getTanks()[0].getMaxGas(); + + for (int slot = 0; slot < config.size(); slot++) { + GasStack cfgStack = config.getGasStack(slot); + GasStack invStack = tanks.getGasStack(slot); + + if (cfgStack == null) { + if (invStack != null) { + return true; + } + continue; + } + + if (invStack == null) { + return true; + } + + if (!cfgStack.isGasEqual(invStack) || invStack.amount != capacity) { + return true; + } + } + + return false; + } + private IAEGasStack extractStackFromAE(final IMEMonitor inv, final GasStack stack) throws GridAccessException { return Platform.poweredExtraction(proxy.getEnergy(), inv, Objects.requireNonNull(AEGasStack.of(stack)), source); } @@ -187,6 +195,12 @@ public boolean canGroupInput() { return true; } + @Nonnull + @Override + public GuiType getMainGuiType() { + return GuiType.ME_GAS_INPUT_BUS; + } + @Nullable @Override public MachineComponent provideComponent() { @@ -208,33 +222,18 @@ public long getGroupID() { }; } - @Override - public void markNoUpdate() { - if (needsUpdate()) { - try { - proxy.getTick().alertDevice(proxy.getNode()); - } catch (GridAccessException e) { - // NO-OP - } - } - - super.markNoUpdate(); - } - @Override public NBTTagCompound downloadSettings() { NBTTagCompound tag = new NBTTagCompound(); tag.setTag(CONFIG_TAG_KEY, config.save()); + this.writePollingSettings(tag); return tag; } @Override public void uploadSettings(NBTTagCompound settings) { config.load(settings.getCompoundTag(CONFIG_TAG_KEY)); - try { - proxy.getTick().alertDevice(proxy.getNode()); - } catch (GridAccessException e) { - ModularMachinery.log.warn("Error while uploading settings", e); - } + this.readPollingSettings(settings); + this.markForUpdate(); } } diff --git a/src/main/java/github/kasuminova/mmce/common/tile/MEGasOutputBus.java b/src/main/java/github/kasuminova/mmce/common/tile/MEGasOutputBus.java index 9cf3361f..d223475e 100644 --- a/src/main/java/github/kasuminova/mmce/common/tile/MEGasOutputBus.java +++ b/src/main/java/github/kasuminova/mmce/common/tile/MEGasOutputBus.java @@ -10,6 +10,7 @@ import com.mekeng.github.common.me.data.impl.AEGasStack; import github.kasuminova.mmce.common.tile.base.MEGasBus; import github.kasuminova.mmce.common.util.IExtendedGasHandler; +import hellfirepvp.modularmachinery.common.CommonProxy.GuiType; import hellfirepvp.modularmachinery.common.crafting.ComponentType; import hellfirepvp.modularmachinery.common.lib.ComponentTypesMM; import hellfirepvp.modularmachinery.common.lib.ItemsMM; @@ -17,12 +18,13 @@ import hellfirepvp.modularmachinery.common.machine.MachineComponent; import mekanism.api.gas.GasStack; import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTTagCompound; import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.util.Objects; -public class MEGasOutputBus extends MEGasBus { +public class MEGasOutputBus extends MEGasBus implements SettingsTransfer { public MEGasOutputBus() { } @@ -51,14 +53,19 @@ public IExtendedGasHandler getContainerProvider() { @Nonnull @Override public TickingRequest getTickingRequest(@Nonnull final IGridNode node) { - return new TickingRequest(5, 60, !hasGas(), true); + return this.getPollingTickingRequest(); } @Nonnull @Override public TickRateModulation tickingRequest(@Nonnull final IGridNode node, final int ticksSinceLastCall) { if (!proxy.isActive()) { - return TickRateModulation.IDLE; + return this.getInactiveTickRateModulation(); + } + + int[] needUpdateSlots = getNeedUpdateSlots(); + if (needUpdateSlots.length == 0) { + return this.getNoWorkTickRateModulation(ticksSinceLastCall); } inTick = true; @@ -67,7 +74,7 @@ public TickRateModulation tickingRequest(@Nonnull final IGridNode node, final in try { IMEMonitor inv = proxy.getStorage().getInventory(channel); synchronized (tanks) { - for (final int slot : getNeedUpdateSlots()) { + for (final int slot : needUpdateSlots) { changedSlots[slot] = false; GasStack gas = tanks.getGasStack(slot); @@ -95,30 +102,35 @@ public TickRateModulation tickingRequest(@Nonnull final IGridNode node, final in } inTick = false; - return successAtLeastOnce ? TickRateModulation.FASTER : TickRateModulation.SLOWER; + return this.getWorkTickRateModulation(successAtLeastOnce, ticksSinceLastCall); + } + + @Nonnull + @Override + public GuiType getMainGuiType() { + return GuiType.ME_GAS_OUTPUT_BUS; } - public boolean hasGas() { + @Override + protected boolean hasWorkToDo() { for (int i = 0; i < tanks.size(); i++) { GasStack stack = tanks.getGasStack(i); - if (stack != null) { - return true; - } + if (stack != null) return true; } + return false; } @Override - public void markNoUpdate() { - if (hasGas()) { - try { - proxy.getTick().alertDevice(proxy.getNode()); - } catch (GridAccessException e) { - // NO-OP - } - } + public NBTTagCompound downloadSettings() { + NBTTagCompound tag = new NBTTagCompound(); + this.writePollingSettings(tag); + return tag; + } - super.markNoUpdate(); + @Override + public void uploadSettings(NBTTagCompound settings) { + this.readPollingSettings(settings); } } diff --git a/src/main/java/github/kasuminova/mmce/common/tile/MEItemInputBus.java b/src/main/java/github/kasuminova/mmce/common/tile/MEItemInputBus.java index 33b3a2da..d0cbb488 100644 --- a/src/main/java/github/kasuminova/mmce/common/tile/MEItemInputBus.java +++ b/src/main/java/github/kasuminova/mmce/common/tile/MEItemInputBus.java @@ -8,7 +8,7 @@ import appeng.me.GridAccessException; import appeng.util.Platform; import github.kasuminova.mmce.common.tile.base.MEItemBus; -import hellfirepvp.modularmachinery.ModularMachinery; +import hellfirepvp.modularmachinery.common.CommonProxy.GuiType; import hellfirepvp.modularmachinery.common.lib.ItemsMM; import hellfirepvp.modularmachinery.common.machine.IOType; import hellfirepvp.modularmachinery.common.machine.MachineComponent; @@ -115,42 +115,19 @@ public boolean canGroupInput() { @Nonnull @Override public TickingRequest getTickingRequest(@Nonnull final IGridNode node) { - return new TickingRequest(10, 120, !needsUpdate(), true); - } - - private boolean needsUpdate() { - for (int slot = 0; slot < configInventory.getSlots(); slot++) { - ItemStack cfgStack = configInventory.getStackInSlot(slot); - ItemStack invStack = inventory.getStackInSlot(slot); - - if (cfgStack.isEmpty()) { - if (!invStack.isEmpty()) { - return true; - } - continue; - } - - if (invStack.isEmpty()) { - return true; - } - - if (!ItemUtils.matchStacks(cfgStack, invStack) || invStack.getCount() != cfgStack.getCount()) { - return true; - } - } - return false; + return this.getPollingTickingRequest(); } @Nonnull @Override public TickRateModulation tickingRequest(@Nonnull final IGridNode node, final int ticksSinceLastCall) { if (!proxy.isActive()) { - return TickRateModulation.IDLE; + return this.getInactiveTickRateModulation(); } int[] needUpdateSlots = getNeedUpdateSlots(); if (needUpdateSlots.length == 0) { - return TickRateModulation.SLOWER; + return this.getNoWorkTickRateModulation(ticksSinceLastCall); } ReadWriteLock rwLock = inventory.getRWLock(); @@ -219,7 +196,7 @@ public TickRateModulation tickingRequest(@Nonnull final IGridNode node, final in inTick = false; rwLock.writeLock().unlock(); - return successAtLeastOnce ? TickRateModulation.FASTER : TickRateModulation.SLOWER; + return this.getWorkTickRateModulation(successAtLeastOnce, ticksSinceLastCall); } catch (GridAccessException e) { inTick = false; changedSlots = new boolean[changedSlots.length]; @@ -228,6 +205,27 @@ public TickRateModulation tickingRequest(@Nonnull final IGridNode node, final in } } + @Override + protected boolean hasWorkToDo() { + for (int slot = 0; slot < configInventory.getSlots(); slot++) { + ItemStack cfgStack = configInventory.getStackInSlot(slot); + ItemStack invStack = inventory.getStackInSlot(slot); + + if (cfgStack.isEmpty()) { + if (!invStack.isEmpty()) return true; + continue; + } + + if (invStack.isEmpty()) return true; + + if (!ItemUtils.matchStacks(cfgStack, invStack) || invStack.getCount() != cfgStack.getCount()) { + return true; + } + } + + return false; + } + private ItemStack extractStackFromAE(final IMEMonitor inv, final ItemStack stack) throws GridAccessException { IAEItemStack aeStack = createStack(stack); if (aeStack == null) { @@ -258,17 +256,10 @@ private IAEItemStack createStack(final ItemStack stack) { return AE_STACK_CACHE.computeIfAbsent(stack, v -> channel.createStack(stack)); } + @Nonnull @Override - public void markNoUpdate() { - if (hasChangedSlots()) { - try { - proxy.getTick().alertDevice(proxy.getNode()); - } catch (GridAccessException e) { - // NO-OP - } - } - - super.markNoUpdate(); + public GuiType getMainGuiType() { + return GuiType.ME_ITEM_INPUT_BUS; } public boolean configInvHasItem() { @@ -300,16 +291,14 @@ public void readConfigInventoryNBT(final NBTTagCompound compound) { public NBTTagCompound downloadSettings() { NBTTagCompound tag = new NBTTagCompound(); tag.setTag(CONFIG_TAG_KEY, configInventory.writeNBT()); + this.writePollingSettings(tag); return tag; } @Override public void uploadSettings(NBTTagCompound settings) { readConfigInventoryNBT(settings.getCompoundTag(CONFIG_TAG_KEY)); - try { - proxy.getTick().alertDevice(proxy.getNode()); - } catch (GridAccessException e) { - ModularMachinery.log.warn("Error while uploading settings", e); - } + this.readPollingSettings(settings); + this.markForUpdate(); } } diff --git a/src/main/java/github/kasuminova/mmce/common/tile/MEItemOutputBus.java b/src/main/java/github/kasuminova/mmce/common/tile/MEItemOutputBus.java index 0f2d7c66..f3bbc0ce 100644 --- a/src/main/java/github/kasuminova/mmce/common/tile/MEItemOutputBus.java +++ b/src/main/java/github/kasuminova/mmce/common/tile/MEItemOutputBus.java @@ -9,6 +9,7 @@ import appeng.util.Platform; import github.kasuminova.mmce.common.tile.base.MEItemBus; import hellfirepvp.modularmachinery.common.lib.ItemsMM; +import hellfirepvp.modularmachinery.common.CommonProxy.GuiType; import hellfirepvp.modularmachinery.common.machine.IOType; import hellfirepvp.modularmachinery.common.machine.MachineComponent; import hellfirepvp.modularmachinery.common.util.IOInventory; @@ -61,19 +62,17 @@ public IOInventory getContainerProvider() { @Nonnull @Override public TickingRequest getTickingRequest(@Nonnull final IGridNode node) { - return new TickingRequest(5, 60, !hasItem(), true); + return this.getPollingTickingRequest(); } @Nonnull @Override public TickRateModulation tickingRequest(@Nonnull final IGridNode node, final int ticksSinceLastCall) { - if (!proxy.isActive()) { - return TickRateModulation.IDLE; - } + if (!proxy.isActive()) return this.getInactiveTickRateModulation(); int[] needUpdateSlots = getNeedUpdateSlots(); if (needUpdateSlots.length == 0) { - return TickRateModulation.SLOWER; + return this.getNoWorkTickRateModulation(ticksSinceLastCall); } inTick = true; @@ -118,7 +117,7 @@ public TickRateModulation tickingRequest(@Nonnull final IGridNode node, final in inTick = false; rwLock.writeLock().unlock(); - return successAtLeastOnce ? TickRateModulation.FASTER : TickRateModulation.SLOWER; + return this.getWorkTickRateModulation(successAtLeastOnce, ticksSinceLastCall); } catch (GridAccessException e) { inTick = false; changedSlots = new boolean[changedSlots.length]; @@ -127,17 +126,15 @@ public TickRateModulation tickingRequest(@Nonnull final IGridNode node, final in } } + @Nonnull @Override - public void markNoUpdate() { - if (hasChangedSlots()) { - try { - proxy.getTick().alertDevice(proxy.getNode()); - } catch (GridAccessException e) { - // NO-OP - } - } + public GuiType getMainGuiType() { + return GuiType.ME_ITEM_OUTPUT_BUS; + } - super.markNoUpdate(); + @Override + protected boolean hasWorkToDo() { + return this.hasItem(); } public int getConfiguredStackSize() { @@ -182,6 +179,7 @@ public void writeCustomNBT(final NBTTagCompound compound) { public NBTTagCompound downloadSettings() { NBTTagCompound tag = new NBTTagCompound(); tag.setInteger("configuredStackSize", this.configuredStackSize); + this.writePollingSettings(tag); return tag; } @@ -189,12 +187,8 @@ public NBTTagCompound downloadSettings() { public void uploadSettings(NBTTagCompound settings) { if (settings.hasKey("configuredStackSize")) { setConfiguredStackSize(settings.getInteger("configuredStackSize")); - - try { - proxy.getTick().alertDevice(proxy.getNode()); - } catch (GridAccessException e) { - // NO-OP - } } + + this.readPollingSettings(settings); } } \ No newline at end of file diff --git a/src/main/java/github/kasuminova/mmce/common/tile/base/MEFluidBus.java b/src/main/java/github/kasuminova/mmce/common/tile/base/MEFluidBus.java index 8f1a490f..a6f452ed 100644 --- a/src/main/java/github/kasuminova/mmce/common/tile/base/MEFluidBus.java +++ b/src/main/java/github/kasuminova/mmce/common/tile/base/MEFluidBus.java @@ -32,7 +32,7 @@ import javax.annotation.Nullable; import java.util.stream.IntStream; -public abstract class MEFluidBus extends MEMachineComponent implements +public abstract class MEFluidBus extends MEPollingMachineComponent implements IAEFluidInventory, IUpgradeableHost, IConfigManagerHost, diff --git a/src/main/java/github/kasuminova/mmce/common/tile/base/MEGasBus.java b/src/main/java/github/kasuminova/mmce/common/tile/base/MEGasBus.java index 1ae622ba..b52716b9 100644 --- a/src/main/java/github/kasuminova/mmce/common/tile/base/MEGasBus.java +++ b/src/main/java/github/kasuminova/mmce/common/tile/base/MEGasBus.java @@ -32,7 +32,7 @@ import javax.annotation.Nullable; import java.util.stream.IntStream; -public abstract class MEGasBus extends MEMachineComponent implements +public abstract class MEGasBus extends MEPollingMachineComponent implements IGasInventoryHost, IUpgradeableHost, IConfigManagerHost, diff --git a/src/main/java/github/kasuminova/mmce/common/tile/base/MEItemBus.java b/src/main/java/github/kasuminova/mmce/common/tile/base/MEItemBus.java index 918f3c90..ff4fa878 100644 --- a/src/main/java/github/kasuminova/mmce/common/tile/base/MEItemBus.java +++ b/src/main/java/github/kasuminova/mmce/common/tile/base/MEItemBus.java @@ -17,7 +17,7 @@ import javax.annotation.Nullable; import java.util.stream.IntStream; -public abstract class MEItemBus extends MEMachineComponent implements IGridTickable { +public abstract class MEItemBus extends MEPollingMachineComponent implements IGridTickable { protected final IItemStorageChannel channel = AEApi.instance().storage().getStorageChannel(IItemStorageChannel.class); diff --git a/src/main/java/github/kasuminova/mmce/common/tile/base/MEPollingMachineComponent.java b/src/main/java/github/kasuminova/mmce/common/tile/base/MEPollingMachineComponent.java new file mode 100644 index 00000000..94fae7e2 --- /dev/null +++ b/src/main/java/github/kasuminova/mmce/common/tile/base/MEPollingMachineComponent.java @@ -0,0 +1,170 @@ +package github.kasuminova.mmce.common.tile.base; + +import appeng.api.networking.IGridNode; +import appeng.api.networking.ticking.IGridTickable; +import appeng.api.networking.ticking.ITickManager; +import appeng.api.networking.ticking.TickRateModulation; +import appeng.api.networking.ticking.TickingRequest; +import appeng.me.GridAccessException; +import github.kasuminova.mmce.common.util.TickManagerHelper; +import hellfirepvp.modularmachinery.common.CommonProxy.GuiType; +import hellfirepvp.modularmachinery.common.data.Config; +import net.minecraft.nbt.NBTTagCompound; + +import javax.annotation.Nonnull; + + +public abstract class MEPollingMachineComponent extends MEMachineComponent { + + private static final String POLLING_RATE_NBT_KEY = "pollingRate"; + + private int pollingRate = 0; + private boolean isSleeping = false; + + public int getPollingRate() { + return this.pollingRate; + } + + public void setPollingRate(int ticks) { + int sanitized = Math.max(0, ticks); + if (this.pollingRate == sanitized) return; + + this.pollingRate = sanitized; + this.isSleeping = false; + this.markForUpdate(); + this.reRegisterTickable(); + } + + public boolean isAdaptivePolling() { + return this.pollingRate <= 0; + } + + @Nonnull + public TickingRequest getPollingTickingRequest() { + if (!this.isAdaptivePolling()) { + this.isSleeping = false; + return new TickingRequest(this.pollingRate, this.pollingRate, false, true); + } + + int min = Math.max(1, Config.meHatchAdaptivePollingMin); + int max = Math.max(min, Config.meHatchAdaptivePollingMax); + boolean sleepImmediately = !Config.meHatchAdaptivePollingWaitForSleep && !this.hasWorkToDo(); + + this.isSleeping = sleepImmediately; + return new TickingRequest(min, max, sleepImmediately, true); + } + + @Nonnull + public TickRateModulation getInactiveTickRateModulation() { + this.isSleeping = false; + return this.isAdaptivePolling() ? TickRateModulation.SLOWER : TickRateModulation.IDLE; + } + + @Nonnull + public TickRateModulation getNoWorkTickRateModulation(final int ticksSinceLastCall) { + if (!this.isAdaptivePolling()) { + this.isSleeping = false; + return TickRateModulation.SAME; + } + + if (this.hasWorkToDo()) { + this.isSleeping = false; + return TickRateModulation.SLOWER; + } + + if (!Config.meHatchAdaptivePollingWaitForSleep) { + this.isSleeping = true; + return TickRateModulation.SLEEP; + } + + // Only sleep if we're at max delay and still have no work to do. This prevents the flow of : + // 1. Work to do, wake up + // 2. No work to do, sleep (after 5 ticks) + // 3. Work to do, wake up immediately + // Which would cause constant sleeping and waking if the speed is at the right interval, + // negating the optimization that adaptive polling is supposed to provide. + // We only wake up on recipe completion if we're sleeping. + int adaptiveMax = Math.max(1, Math.max(Config.meHatchAdaptivePollingMin, Config.meHatchAdaptivePollingMax)); + if (ticksSinceLastCall >= adaptiveMax) { + this.isSleeping = true; + return TickRateModulation.SLEEP; + } + + this.isSleeping = false; + return TickRateModulation.SLOWER; + } + + @Nonnull + public TickRateModulation getWorkTickRateModulation(final boolean didWork, final int ticksSinceLastCall) { + if (!this.isAdaptivePolling()) { + this.isSleeping = false; + return TickRateModulation.SAME; + } + + if (didWork) { + this.isSleeping = false; + return TickRateModulation.FASTER; + } + + return this.getNoWorkTickRateModulation(ticksSinceLastCall); + } + + @Override + public void markNoUpdate() { + super.markNoUpdate(); + this.wakeUpIfAdaptive(); + } + + public void wakeUpIfAdaptive() { + if (!this.isAdaptivePolling() || !this.isSleeping || !this.proxy.isReady() || !this.hasWorkToDo()) { + return; + } + + try { + IGridNode node = this.proxy.getNode(); + ITickManager tickManager = this.proxy.getTick(); + if (!tickManager.alertDevice(node)) tickManager.wakeDevice(node); + this.isSleeping = false; + } catch (GridAccessException e) { + // Not connected to the grid yet. + } + } + + public void writePollingSettings(NBTTagCompound tag) { + tag.setInteger(POLLING_RATE_NBT_KEY, this.pollingRate); + } + + public void readPollingSettings(NBTTagCompound tag) { + if (tag.hasKey(POLLING_RATE_NBT_KEY)) { + this.setPollingRate(tag.getInteger(POLLING_RATE_NBT_KEY)); + } + } + + @Override + public void readCustomNBT(final NBTTagCompound compound) { + super.readCustomNBT(compound); + + if (compound.hasKey(POLLING_RATE_NBT_KEY)) { + this.pollingRate = Math.max(0, compound.getInteger(POLLING_RATE_NBT_KEY)); + } + } + + @Override + public void writeCustomNBT(final NBTTagCompound compound) { + super.writeCustomNBT(compound); + compound.setInteger(POLLING_RATE_NBT_KEY, this.pollingRate); + } + + private void reRegisterTickable() { + if (!(this instanceof IGridTickable tickable) || !this.proxy.isReady()) { + return; + } + + TickManagerHelper.reRegisterTickable(this.proxy.getNode(), tickable); + } + + protected abstract boolean hasWorkToDo(); + + @Nonnull + public abstract GuiType getMainGuiType(); +} \ No newline at end of file diff --git a/src/main/java/github/kasuminova/mmce/common/util/PollingRateUtils.java b/src/main/java/github/kasuminova/mmce/common/util/PollingRateUtils.java new file mode 100644 index 00000000..a200fa96 --- /dev/null +++ b/src/main/java/github/kasuminova/mmce/common/util/PollingRateUtils.java @@ -0,0 +1,48 @@ +package github.kasuminova.mmce.common.util; + + +public final class PollingRateUtils { + + public static final int TICKS_PER_TICK = 1; + public static final int TICKS_PER_SECOND = 20; + public static final int TICKS_PER_MINUTE = TICKS_PER_SECOND * 60; + public static final int TICKS_PER_HOUR = TICKS_PER_MINUTE * 60; + public static final int TICKS_PER_DAY = TICKS_PER_HOUR * 24; + + private PollingRateUtils() { + } + + public static String format(long ticks) { + if (ticks <= 0) return "0"; + + StringBuilder builder = new StringBuilder(); + + if (ticks >= TICKS_PER_DAY) { + long days = ticks / TICKS_PER_DAY; + builder.append(days).append('d').append(' '); + ticks %= TICKS_PER_DAY; + } + + if (ticks >= TICKS_PER_HOUR) { + long hours = ticks / TICKS_PER_HOUR; + builder.append(hours).append('h').append(' '); + ticks %= TICKS_PER_HOUR; + } + + if (ticks >= TICKS_PER_MINUTE) { + long minutes = ticks / TICKS_PER_MINUTE; + builder.append(minutes).append('m').append(' '); + ticks %= TICKS_PER_MINUTE; + } + + if (ticks >= TICKS_PER_SECOND) { + long seconds = ticks / TICKS_PER_SECOND; + builder.append(seconds).append('s').append(' '); + ticks %= TICKS_PER_SECOND; + } + + if (ticks > 0) builder.append(ticks).append('t'); + + return builder.toString().trim(); + } +} \ No newline at end of file diff --git a/src/main/java/github/kasuminova/mmce/common/util/TickManagerHelper.java b/src/main/java/github/kasuminova/mmce/common/util/TickManagerHelper.java new file mode 100644 index 00000000..5d00a1c5 --- /dev/null +++ b/src/main/java/github/kasuminova/mmce/common/util/TickManagerHelper.java @@ -0,0 +1,103 @@ +package github.kasuminova.mmce.common.util; + +import appeng.api.networking.IGridHost; +import appeng.api.networking.IGridNode; +import appeng.api.networking.ticking.IGridTickable; +import appeng.api.networking.ticking.ITickManager; +import appeng.me.cache.TickManagerCache; +import appeng.me.cache.helpers.TickTracker; +import hellfirepvp.modularmachinery.ModularMachinery; + +import java.lang.reflect.Field; +import java.util.PriorityQueue; + + +/** + * Workaround for an AE2 bug in {@link TickManagerCache}. + *

+ * {@link TickManagerCache#removeNode} removes a node's {@link TickTracker} from + * the {@code awake}/{@code sleeping}/{@code alertable} HashMaps, but does NOT + * remove it from the {@code upcomingTicks} PriorityQueue. When {@code addNode} + * is called immediately after (to re-register with new tick bounds), a new + * TickTracker is created and added to the queue alongside the stale one. + *

+ * The staleness guard in {@code onUpdateTick} checks + * {@code awake.containsKey(tt.getNode())}, which passes for the stale tracker + * because the NEW tracker is registered under the same node key. As a result, + * the stale tracker keeps firing and is re-added to the queue every cycle. + * Each {@code removeNode/addNode} cycle accumulates another phantom tracker, + * causing the node to be ticked N times per cycle after N rate changes. + * The issue resolves on world reload since the tick manager's state is rebuilt. + *

+ * This helper uses reflection to access the {@code upcomingTicks} PriorityQueue + * and purge any stale TickTracker entries for a given node before re-registering. + */ +public final class TickManagerHelper { + + private static Field upcomingTicksField; + private static boolean reflectionFailed = false; + + private TickManagerHelper() { + } + + /** + * Safely re-register a tickable node with the tick manager. + * Cleans up stale TickTracker entries from AE2's internal PriorityQueue + * before calling removeNode/addNode, preventing phantom tick accumulation. + *

+ * Callers should ensure the node is valid (proxy.isReady()) before calling. + * If the node is null or has no grid, returns false without error. + * + * @param node the grid node to re-register + * @param tickable the tickable machine (must also implement IGridHost at runtime) + * @return true if re-registration succeeded, false if skipped or failed + */ + public static boolean reRegisterTickable(IGridNode node, IGridTickable tickable) { + if (node == null) return false; + + if (!(tickable instanceof IGridHost)) { + ModularMachinery.log.warn("Tickable is not an IGridHost: {}", tickable); + return false; + } + + ITickManager tickManager = node.getGrid().getCache(ITickManager.class); + purgeStaleTrackers(tickManager, node); + + IGridHost gridHost = (IGridHost) tickable; + tickManager.removeNode(node, gridHost); + tickManager.addNode(node, gridHost); + return true; + } + + /** + * Remove all TickTracker entries for the given node from the tick manager's + * internal PriorityQueue. This prevents stale trackers from accumulating + * when a node is removed and re-added. + */ + @SuppressWarnings("unchecked") + private static void purgeStaleTrackers(ITickManager tickManager, IGridNode node) { + if (reflectionFailed || !(tickManager instanceof TickManagerCache)) { + return; + } + + try { + if (upcomingTicksField == null) { + upcomingTicksField = TickManagerCache.class.getDeclaredField("upcomingTicks"); + upcomingTicksField.setAccessible(true); + } + + PriorityQueue upcomingTicks = + (PriorityQueue) upcomingTicksField.get(tickManager); + upcomingTicks.removeIf(tracker -> tracker.getNode() == node); + } catch (NoSuchFieldException | IllegalAccessException | ClassCastException e) { + // If reflection fails (e.g. AE2 internals changed), log a warning once + // and fall back to the buggy removeNode/addNode behavior. The issue will + // self-resolve on world reload, so this is an acceptable degradation. + reflectionFailed = true; + ModularMachinery.log.warn( + "Failed to purge stale AE2 tick trackers. Polling rate changes may tick incorrectly until reload.", + e + ); + } + } +} \ No newline at end of file diff --git a/src/main/java/hellfirepvp/modularmachinery/ModularMachinery.java b/src/main/java/hellfirepvp/modularmachinery/ModularMachinery.java index 4b5fe9b1..5d4bb76b 100644 --- a/src/main/java/hellfirepvp/modularmachinery/ModularMachinery.java +++ b/src/main/java/hellfirepvp/modularmachinery/ModularMachinery.java @@ -134,6 +134,8 @@ public void preInit(FMLPreInitializationEvent event) { if (Mods.AE2.isPresent()) { NET_CHANNEL.registerMessage(PktMEOutputBusStackSizeChange.class, PktMEOutputBusStackSizeChange.class, 107, Side.SERVER); NET_CHANNEL.registerMessage(PktSwitchGuiMEOutputBus.class, PktSwitchGuiMEOutputBus.class, 108, Side.SERVER); + NET_CHANNEL.registerMessage(PktOpenMEBusGui.class, PktOpenMEBusGui.class, 109, Side.SERVER); + NET_CHANNEL.registerMessage(PktSetMEBusPollingRate.class, PktSetMEBusPollingRate.class, 110, Side.SERVER); } CommonProxy.loadModData(event.getModConfigurationDirectory()); diff --git a/src/main/java/hellfirepvp/modularmachinery/client/ClientProxy.java b/src/main/java/hellfirepvp/modularmachinery/client/ClientProxy.java index e62da3d9..f3202470 100644 --- a/src/main/java/hellfirepvp/modularmachinery/client/ClientProxy.java +++ b/src/main/java/hellfirepvp/modularmachinery/client/ClientProxy.java @@ -19,6 +19,7 @@ import github.kasuminova.mmce.common.tile.MEItemInputBus; import github.kasuminova.mmce.common.tile.MEItemOutputBus; import github.kasuminova.mmce.common.tile.MEPatternProvider; +import github.kasuminova.mmce.common.tile.base.MEPollingMachineComponent; import hellfirepvp.modularmachinery.ModularMachinery; import hellfirepvp.modularmachinery.client.gui.GuiContainerEnergyHatch; import hellfirepvp.modularmachinery.client.gui.GuiContainerFluidHatch; @@ -347,6 +348,12 @@ public Object getClientGuiElement(int ID, EntityPlayer player, World world, int } return new GuiMEGasInputBus((MEGasInputBus) present, player); } + case ME_BUS_POLLING -> { + if (!Mods.AE2.isPresent()) { + return null; + } + return new GuiMEBusPollingRate(player.inventory, (MEPollingMachineComponent) present); + } case ME_PATTERN_PROVIDER -> { if (!Mods.AE2.isPresent()) { return null; diff --git a/src/main/java/hellfirepvp/modularmachinery/common/CommonProxy.java b/src/main/java/hellfirepvp/modularmachinery/common/CommonProxy.java index c9bb9cd2..94d314b2 100644 --- a/src/main/java/hellfirepvp/modularmachinery/common/CommonProxy.java +++ b/src/main/java/hellfirepvp/modularmachinery/common/CommonProxy.java @@ -18,6 +18,7 @@ import github.kasuminova.mmce.common.container.ContainerMEItemInputBus; import github.kasuminova.mmce.common.container.ContainerMEItemOutputBus; import github.kasuminova.mmce.common.container.ContainerMEItemOutputBusStackSize; +import github.kasuminova.mmce.common.container.ContainerMEBusPollingRate; import github.kasuminova.mmce.common.container.ContainerMEPatternProvider; import github.kasuminova.mmce.common.handler.EventHandler; import github.kasuminova.mmce.common.handler.UpgradeEventHandler; @@ -30,6 +31,7 @@ import github.kasuminova.mmce.common.tile.MEItemInputBus; import github.kasuminova.mmce.common.tile.MEItemOutputBus; import github.kasuminova.mmce.common.tile.MEPatternProvider; +import github.kasuminova.mmce.common.tile.base.MEPollingMachineComponent; import github.kasuminova.mmce.common.util.concurrent.Action; import github.kasuminova.mmce.common.world.MMWorldEventListener; import hellfirepvp.modularmachinery.ModularMachinery; @@ -313,6 +315,12 @@ public Object getServerGuiElement(int ID, EntityPlayer player, World world, int } return new ContainerMEItemOutputBusStackSize(player.inventory, (MEItemOutputBus) present); } + case ME_BUS_POLLING -> { + if (aeSecurityCheck(player, present)) return null; + if (present instanceof MEPollingMachineComponent host) { + return new ContainerMEBusPollingRate(player.inventory, host); + } + } case GUI_GROUP_INPUT_CONFIG -> { if (present instanceof MachineGroupInput m && m.canGroupInput()) { return new ContainerGroupInputConfig(present, player); @@ -347,6 +355,7 @@ public enum GuiType { ME_FLUID_INPUT_BUS(Mods.AE2.isPresent() ? MEFluidInputBus.class : null), ME_GAS_OUTPUT_BUS(Mods.AE2EL.isPresent() && Mods.MEKENG.isPresent() ? MEGasOutputBus.class : null), ME_GAS_INPUT_BUS(Mods.AE2EL.isPresent() && Mods.MEKENG.isPresent() ? MEGasInputBus.class : null), + ME_BUS_POLLING(Mods.AE2.isPresent() ? MEPollingMachineComponent.class : null), ME_PATTERN_PROVIDER(Mods.AE2.isPresent() ? MEPatternProvider.class : null), GUI_ESSENCE_PROVIDER(Mods.BM2.isPresent() ? TileLifeEssenceProvider.class : null), GUI_GROUP_INPUT_CONFIG(TileEntity.class) diff --git a/src/main/java/hellfirepvp/modularmachinery/common/data/Config.java b/src/main/java/hellfirepvp/modularmachinery/common/data/Config.java index fb628347..9ec2b2f5 100644 --- a/src/main/java/hellfirepvp/modularmachinery/common/data/Config.java +++ b/src/main/java/hellfirepvp/modularmachinery/common/data/Config.java @@ -38,12 +38,15 @@ public class Config { public static boolean machineParallelizeEnabledByDefault = true; public static boolean recipeParallelizeEnabledByDefault = true; public static boolean enableFluxNetworksIntegration = true; + public static boolean meHatchAdaptivePollingWaitForSleep = true; public static boolean enableFactoryControllerByDefault = false; public static boolean controllerOutputComparatorSignal = true; public static boolean asyncControllerModelRender = true; public static boolean enableDurationMultiplier = true; public static int machineColor; public static int maxMachineParallelism = 2048; + public static int meHatchAdaptivePollingMin = 5; + public static int meHatchAdaptivePollingMax = 60; public static int defaultFactoryMaxThread = 20; private static File lastReadFile; @@ -107,6 +110,23 @@ private static void load() { // FluxNetworks Integration enableFluxNetworksIntegration = lastReadConfig.getBoolean("enable-fluxnetworks-integration", "general", true, "When enabled, allows you to use the flux network to transfer larger amounts of energy than 2147483647."); + meHatchAdaptivePollingWaitForSleep = lastReadConfig.getBoolean( + "me-hatch-adaptive-polling-wait-for-sleep", "general", + true, + "When enabled, Adaptive polling waits until the configured max interval before sleeping. Disable to emulate the previous immediate sleep and wake behavior for polling rate 0." + ); + + // Adaptive Polling + meHatchAdaptivePollingMin = lastReadConfig.getInt( + "me-hatch-adaptive-polling-min", "general", + 5, 1, Integer.MAX_VALUE, + "The fastest adaptive poll interval, in ticks, used by ME input and output hatches when in Adaptive mode." + ); + meHatchAdaptivePollingMax = lastReadConfig.getInt( + "me-hatch-adaptive-polling-max", "general", + 60, meHatchAdaptivePollingMin, Integer.MAX_VALUE, + "The slowest adaptive poll interval, in ticks, used by ME input and output hatches when in Adaptive mode." + ); // Parallelize Feature machineParallelizeEnabledByDefault = lastReadConfig.getBoolean("machine-parallelize-enabled-bydefault", diff --git a/src/main/resources/assets/modularmachinery/lang/en_US.lang b/src/main/resources/assets/modularmachinery/lang/en_US.lang index 96281503..bfd672da 100644 --- a/src/main/resources/assets/modularmachinery/lang/en_US.lang +++ b/src/main/resources/assets/modularmachinery/lang/en_US.lang @@ -77,6 +77,10 @@ gui.meitembus.item_cached=Items Cached: %s gui.meitembus.nbt_stored=Items have been stored internally. gui.meitembus.stack_size.name=Max Stack Size gui.meitembus.stack_size.config=Configure Max Stack Size +gui.mehatch.polling_rate.name=Polling Rate +gui.mehatch.polling_rate.current=Current: §bevery %s +gui.mehatch.polling_rate.current_adaptive=Current: §bevery %s-%s ticks§r (Adaptive) +gui.mehatch.polling_rate.tooltip=Controls how often the ME bus checks the ME Network for work (0 = Adaptive). gui.mefluidoutputbus.title=ME Machinery Fluid Output Bus gui.mefluidinputbus.title=ME Machinery Fluid Input Bus