From e1b3f20078cff5f9d49709925308789ed594c4b2 Mon Sep 17 00:00:00 2001 From: firenoo <49818773+firenoo@users.noreply.github.com> Date: Sat, 1 Apr 2023 05:04:39 -0700 Subject: [PATCH] Network throttle: Force ME update packets to be sent once every N ms. (#110) * Network throttle: Force ME update packets to be sent once every 50ms. 1. Item and fluid packets are separated to their own classes, as they are not even allowed to be mixed together in the first place. This will enforce a strict separation of the two. 2. New network worker thread for handling ME inventory updates. Instead of creating packets directly, classes should schedule packet(s) to be sent at the next 50ms boundary. The packet has a limit of 256 items to prevent killing the client, but this can be changed in the config. In fact, the packet send rate is also configurable. 3. Upon opening a terminal GUI, ME update packets are split. Only the last received packet will trigger a terminal sorting, so if the ME system is large, it may take several seconds (!) to load all the items. * Update SPacketMEFluidInvUpdate.java --------- Co-authored-by: Martin Robertz --- .../java/com/glodblock/github/FluidCraft.java | 11 ++ .../gui/container/ContainerFluidMonitor.java | 48 +++---- .../gui/container/ContainerItemMonitor.java | 52 +++---- .../container/base/FCContainerMonitor.java | 5 + .../com/glodblock/github/common/Config.java | 5 + .../network/CPacketLevelMaintainer.java | 12 +- .../network/SPacketMEFluidInvUpdate.java | 14 +- .../network/SPacketMEItemInvUpdate.java | 10 +- .../github/network/SPacketMEUpdateBuffer.java | 130 ++++++++++++++++++ .../glodblock/github/proxy/CommonProxy.java | 3 + 10 files changed, 200 insertions(+), 90 deletions(-) create mode 100644 src/main/java/com/glodblock/github/network/SPacketMEUpdateBuffer.java diff --git a/src/main/java/com/glodblock/github/FluidCraft.java b/src/main/java/com/glodblock/github/FluidCraft.java index 71435ba7..1cd4f76a 100644 --- a/src/main/java/com/glodblock/github/FluidCraft.java +++ b/src/main/java/com/glodblock/github/FluidCraft.java @@ -14,6 +14,7 @@ import com.glodblock.github.loader.ChannelLoader; import com.glodblock.github.loader.ItemAndBlockHolder; import com.glodblock.github.loader.RecipeLoader; import com.glodblock.github.loader.filter.FluidFilter; +import com.glodblock.github.network.SPacketMEUpdateBuffer; import com.glodblock.github.proxy.CommonProxy; import com.glodblock.github.util.ModAndClassUtil; @@ -82,6 +83,16 @@ public class FluidCraft { proxy.postInit(event); } + @Mod.EventHandler + public void onServerStart(FMLServerStartingEvent event) { + SPacketMEUpdateBuffer.init(); + } + + @Mod.EventHandler + public void onServerStop(FMLServerStoppedEvent event) { + SPacketMEUpdateBuffer.disable(); + } + @Mod.EventHandler public void onLoadComplete(FMLLoadCompleteEvent event) {} diff --git a/src/main/java/com/glodblock/github/client/gui/container/ContainerFluidMonitor.java b/src/main/java/com/glodblock/github/client/gui/container/ContainerFluidMonitor.java index aebc0dc4..9474fae9 100644 --- a/src/main/java/com/glodblock/github/client/gui/container/ContainerFluidMonitor.java +++ b/src/main/java/com/glodblock/github/client/gui/container/ContainerFluidMonitor.java @@ -1,7 +1,8 @@ package com.glodblock.github.client.gui.container; -import java.nio.BufferOverflowException; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; import net.minecraft.entity.player.EntityPlayer; @@ -43,7 +44,7 @@ import com.glodblock.github.client.gui.container.base.FCContainerMonitor; import com.glodblock.github.common.item.ItemFluidDrop; import com.glodblock.github.network.CPacketFluidUpdate; import com.glodblock.github.network.SPacketFluidUpdate; -import com.glodblock.github.network.SPacketMEFluidInvUpdate; +import com.glodblock.github.network.SPacketMEUpdateBuffer; import com.glodblock.github.util.Util; public class ContainerFluidMonitor extends FCContainerMonitor { @@ -126,33 +127,22 @@ public class ContainerFluidMonitor extends FCContainerMonitor { protected void processItemList() { if (!this.fluids.isEmpty()) { final IItemList monitorCache = this.monitor.getStorageList(); - SPacketMEFluidInvUpdate packet = new SPacketMEFluidInvUpdate(); + List toSend = new ArrayList<>(); for (final IAEFluidStack is : this.fluids) { final IAEFluidStack send = monitorCache.findPrecise(is); - try { - if (send != null) { - packet.appendFluid(send); - } else { - is.setStackSize(0); - packet.appendFluid(is); - } - } catch (BufferOverflowException e) { - for (final Object c : this.crafters) { - FluidCraft.proxy.netHandler.sendTo(packet, (EntityPlayerMP) c); - } - packet = new SPacketMEFluidInvUpdate(); - if (send != null) { - packet.appendFluid(send); - } else { - is.setStackSize(0); - packet.appendFluid(is); - } - + if (send != null) { + toSend.add(send); + } else { + is.setStackSize(0); + toSend.add(is); } } for (final Object c : this.crafters) { - FluidCraft.proxy.netHandler.sendTo(packet, (EntityPlayerMP) c); + if (c instanceof EntityPlayerMP) { + SPacketMEUpdateBuffer.scheduleFluidUpdate((EntityPlayerMP) c, toSend); + } } + this.fluids.resetStatus(); } } @@ -160,17 +150,11 @@ public class ContainerFluidMonitor extends FCContainerMonitor { protected void queueInventory(final ICrafting c) { if (Platform.isServer() && c instanceof EntityPlayerMP && this.monitor != null) { final IItemList monitorCache = this.monitor.getStorageList(); - SPacketMEFluidInvUpdate packet = new SPacketMEFluidInvUpdate(); + List toSend = new ArrayList<>(); for (final IAEFluidStack is : monitorCache) { - try { - packet.appendFluid(is); - } catch (BufferOverflowException e) { - FluidCraft.proxy.netHandler.sendTo(packet, (EntityPlayerMP) c); - packet = new SPacketMEFluidInvUpdate(); - packet.appendFluid(is); - } + toSend.add(is); } - FluidCraft.proxy.netHandler.sendTo(packet, (EntityPlayerMP) c); + SPacketMEUpdateBuffer.scheduleFluidUpdate((EntityPlayerMP) c, toSend); } } diff --git a/src/main/java/com/glodblock/github/client/gui/container/ContainerItemMonitor.java b/src/main/java/com/glodblock/github/client/gui/container/ContainerItemMonitor.java index acad6098..6cba6ea5 100644 --- a/src/main/java/com/glodblock/github/client/gui/container/ContainerItemMonitor.java +++ b/src/main/java/com/glodblock/github/client/gui/container/ContainerItemMonitor.java @@ -1,6 +1,7 @@ package com.glodblock.github.client.gui.container; -import java.nio.BufferOverflowException; +import java.util.ArrayList; +import java.util.List; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.player.EntityPlayerMP; @@ -25,9 +26,8 @@ import appeng.container.slot.SlotRestrictedInput; import appeng.me.helpers.ChannelPowerSrc; import appeng.util.Platform; -import com.glodblock.github.FluidCraft; import com.glodblock.github.client.gui.container.base.FCContainerMonitor; -import com.glodblock.github.network.SPacketMEItemInvUpdate; +import com.glodblock.github.network.SPacketMEUpdateBuffer; public class ContainerItemMonitor extends FCContainerMonitor { @@ -95,34 +95,19 @@ public class ContainerItemMonitor extends FCContainerMonitor { protected void processItemList() { if (!this.items.isEmpty()) { final IItemList monitorCache = this.monitor.getStorageList(); - SPacketMEItemInvUpdate packet = new SPacketMEItemInvUpdate(); + List toSend = new ArrayList<>(); for (final IAEItemStack is : this.items) { final IAEItemStack send = monitorCache.findPrecise(is); - try { - if (send != null) { - packet.appendItem(send); - } else { - is.setStackSize(0); - packet.appendItem(is); - } - } catch (BufferOverflowException e) { - for (final Object c : this.crafters) { - if (c instanceof EntityPlayerMP) { - FluidCraft.proxy.netHandler.sendTo(packet, (EntityPlayerMP) c); - } - } - packet = new SPacketMEItemInvUpdate(); - if (send != null) { - packet.appendItem(send); - } else { - is.setStackSize(0); - packet.appendItem(is); - } + if (send != null) { + toSend.add(send); + } else { + is.setStackSize(0); + toSend.add(is); } } for (final Object c : this.crafters) { - if (c instanceof EntityPlayerMP) { - FluidCraft.proxy.netHandler.sendTo(packet, (EntityPlayerMP) c); + if (c instanceof EntityPlayer) { + SPacketMEUpdateBuffer.scheduleItemUpdate((EntityPlayerMP) c, toSend); } } this.items.resetStatus(); @@ -133,17 +118,11 @@ public class ContainerItemMonitor extends FCContainerMonitor { protected void queueInventory(final ICrafting c) { if (Platform.isServer() && c instanceof EntityPlayer && this.monitor != null) { final IItemList monitorCache = this.monitor.getStorageList(); - SPacketMEItemInvUpdate packet = new SPacketMEItemInvUpdate(); + List toSend = new ArrayList<>(); for (final IAEItemStack is : monitorCache) { - try { - packet.appendItem(is); - } catch (BufferOverflowException e) { - FluidCraft.proxy.netHandler.sendTo(packet, (EntityPlayerMP) c); - packet = new SPacketMEItemInvUpdate(); - packet.appendItem(is); - } + toSend.add(is); } - FluidCraft.proxy.netHandler.sendTo(packet, (EntityPlayerMP) c); + SPacketMEUpdateBuffer.scheduleItemUpdate((EntityPlayerMP) c, toSend); } } @@ -152,6 +131,9 @@ public class ContainerItemMonitor extends FCContainerMonitor { super.removeCraftingFromCrafters(c); if (this.crafters.isEmpty() && this.monitor != null) { this.monitor.removeListener(this); + if (c instanceof EntityPlayerMP) { + SPacketMEUpdateBuffer.clear((EntityPlayerMP) c); + } } } diff --git a/src/main/java/com/glodblock/github/client/gui/container/base/FCContainerMonitor.java b/src/main/java/com/glodblock/github/client/gui/container/base/FCContainerMonitor.java index a2cd9fb7..407d0115 100644 --- a/src/main/java/com/glodblock/github/client/gui/container/base/FCContainerMonitor.java +++ b/src/main/java/com/glodblock/github/client/gui/container/base/FCContainerMonitor.java @@ -30,6 +30,7 @@ import appeng.util.IConfigManagerHost; import appeng.util.Platform; import com.glodblock.github.inventory.item.IWirelessTerminal; +import com.glodblock.github.network.SPacketMEUpdateBuffer; public abstract class FCContainerMonitor> extends FCBaseContainer implements IConfigManagerHost, IConfigurableObject, IMEMonitorHandlerReceiver { @@ -158,6 +159,10 @@ public abstract class FCContainerMonitor> extends FCBaseCo super.onContainerClosed(player); if (this.monitor != null) { this.monitor.removeListener(this); + if (player instanceof EntityPlayerMP && Platform.isServer()) { + SPacketMEUpdateBuffer.clear((EntityPlayerMP) player); + } + } } diff --git a/src/main/java/com/glodblock/github/common/Config.java b/src/main/java/com/glodblock/github/common/Config.java index 573c5a3f..01ee7ca7 100644 --- a/src/main/java/com/glodblock/github/common/Config.java +++ b/src/main/java/com/glodblock/github/common/Config.java @@ -18,6 +18,7 @@ public class Config { public static boolean blacklistEssentiaGas; public static double portableCellBattery; public static int packetSize; + public static int packetRate; public static void run() { loadCategory(); @@ -55,6 +56,10 @@ public class Config { packetSize = Config.get("Fluid Craft for AE2", "packetSize", 256, "Number of items to be sent per packet") .getInt(); if (packetSize <= 0) packetSize = 256; + packetRate = Config + .get("Fluid Craft for AE2", "packetRate", 50, "Period at which packets are dispatched, in ms.") + .getInt(); + if (packetRate <= 0) packetRate = 50; if (Config.hasChanged()) Config.save(); } diff --git a/src/main/java/com/glodblock/github/network/CPacketLevelMaintainer.java b/src/main/java/com/glodblock/github/network/CPacketLevelMaintainer.java index 76a009bd..55c80d4d 100644 --- a/src/main/java/com/glodblock/github/network/CPacketLevelMaintainer.java +++ b/src/main/java/com/glodblock/github/network/CPacketLevelMaintainer.java @@ -1,5 +1,8 @@ package com.glodblock.github.network; +import java.util.ArrayList; +import java.util.List; + import javax.annotation.Nullable; import net.minecraft.entity.player.EntityPlayerMP; @@ -9,7 +12,6 @@ import net.minecraft.nbt.NBTTagCompound; import appeng.api.storage.data.IAEItemStack; import appeng.util.item.AEItemStack; -import com.glodblock.github.FluidCraft; import com.glodblock.github.client.gui.container.ContainerLevelMaintainer; import com.glodblock.github.common.tile.TileLevelMaintainer; @@ -88,7 +90,7 @@ public class CPacketLevelMaintainer implements IMessage { public static class Handler implements IMessageHandler { private void refresh(ContainerLevelMaintainer cca, EntityPlayerMP player) { - SPacketMEItemInvUpdate packet = new SPacketMEItemInvUpdate(); + List toSend = new ArrayList<>(TileLevelMaintainer.REQ_COUNT); for (int i = 0; i < TileLevelMaintainer.REQ_COUNT; i++) { IAEItemStack is = cca.getTile().requests.getRequestQtyStack(i); IAEItemStack is1 = cca.getTile().requests.getRequestBatches().getStack(i); @@ -96,7 +98,7 @@ public class CPacketLevelMaintainer implements IMessage { if (is1 != null) { NBTTagCompound data; data = is1.getItemStack().getTagCompound(); - packet.appendItem( + toSend.add( setTag( is, is1.getStackSize(), @@ -104,11 +106,11 @@ public class CPacketLevelMaintainer implements IMessage { data.getBoolean("Enable"), cca.getTile().requests.getState(i).ordinal())); } else { - packet.appendItem(setTag(is, 0, i, true, 0)); + toSend.add(setTag(is, 0, i, true, 0)); } } } - FluidCraft.proxy.netHandler.sendTo(packet, player); + SPacketMEUpdateBuffer.scheduleItemUpdate(player, toSend); } @Nullable diff --git a/src/main/java/com/glodblock/github/network/SPacketMEFluidInvUpdate.java b/src/main/java/com/glodblock/github/network/SPacketMEFluidInvUpdate.java index 0980b805..e72c2ad2 100644 --- a/src/main/java/com/glodblock/github/network/SPacketMEFluidInvUpdate.java +++ b/src/main/java/com/glodblock/github/network/SPacketMEFluidInvUpdate.java @@ -1,7 +1,6 @@ package com.glodblock.github.network; import java.io.IOException; -import java.nio.BufferOverflowException; import java.util.ArrayList; import java.util.List; @@ -13,7 +12,6 @@ import appeng.util.item.AEFluidStack; import com.glodblock.github.client.gui.GuiFluidCraftConfirm; import com.glodblock.github.client.gui.GuiFluidMonitor; -import com.glodblock.github.common.Config; import cpw.mods.fml.common.network.simpleimpl.IMessage; import cpw.mods.fml.common.network.simpleimpl.IMessageHandler; @@ -40,8 +38,8 @@ public class SPacketMEFluidInvUpdate implements IMessage { /** * If resort, call "updateView()". Used when multiple packets are sent to open an inventory; only the last packet - * should resort. - * + * should resort + * * @param resort whether this packet should resort the term or not */ public SPacketMEFluidInvUpdate(boolean resort) { @@ -82,12 +80,8 @@ public class SPacketMEFluidInvUpdate implements IMessage { this.resort = resort; } - public void appendFluid(final IAEFluidStack is) throws BufferOverflowException { - if (list.size() <= Config.packetSize) { - list.add(is); - } else { - throw new BufferOverflowException(); - } + public void appendFluid(final IAEFluidStack is) { + list.add(is); } public boolean isEmpty() { diff --git a/src/main/java/com/glodblock/github/network/SPacketMEItemInvUpdate.java b/src/main/java/com/glodblock/github/network/SPacketMEItemInvUpdate.java index 0c48f9dd..b42e0e8f 100644 --- a/src/main/java/com/glodblock/github/network/SPacketMEItemInvUpdate.java +++ b/src/main/java/com/glodblock/github/network/SPacketMEItemInvUpdate.java @@ -1,7 +1,6 @@ package com.glodblock.github.network; import java.io.IOException; -import java.nio.BufferOverflowException; import java.util.ArrayList; import java.util.List; @@ -14,7 +13,6 @@ import appeng.util.item.AEItemStack; import com.glodblock.github.client.gui.GuiFluidCraftConfirm; import com.glodblock.github.client.gui.GuiItemMonitor; import com.glodblock.github.client.gui.GuiLevelMaintainer; -import com.glodblock.github.common.Config; import cpw.mods.fml.common.network.simpleimpl.IMessage; import cpw.mods.fml.common.network.simpleimpl.IMessageHandler; @@ -83,12 +81,8 @@ public class SPacketMEItemInvUpdate implements IMessage { this.resort = resort; } - public void appendItem(final IAEItemStack is) throws BufferOverflowException { - if (list.size() <= Config.packetSize) { - list.add(is); - } else { - throw new BufferOverflowException(); - } + public void appendItem(final IAEItemStack is) { + list.add(is); } public boolean isEmpty() { diff --git a/src/main/java/com/glodblock/github/network/SPacketMEUpdateBuffer.java b/src/main/java/com/glodblock/github/network/SPacketMEUpdateBuffer.java new file mode 100644 index 00000000..8b6169bb --- /dev/null +++ b/src/main/java/com/glodblock/github/network/SPacketMEUpdateBuffer.java @@ -0,0 +1,130 @@ +package com.glodblock.github.network; + +import java.util.*; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +import net.minecraft.entity.player.EntityPlayerMP; + +import appeng.api.storage.data.IAEFluidStack; +import appeng.api.storage.data.IAEItemStack; + +import com.glodblock.github.FluidCraft; +import com.glodblock.github.common.Config; + +import cpw.mods.fml.common.eventhandler.SubscribeEvent; +import cpw.mods.fml.common.gameevent.PlayerEvent; + +/** + * Only should exist on the server side! Schedules packets to be sent every `packetRate` ms. + */ +public class SPacketMEUpdateBuffer { + + private static ScheduledExecutorService executor; + private static ScheduledFuture task; + + private static final Map> itemBuffer = new HashMap<>(); + + private static final Map> fluidBuffer = new HashMap<>(); + + public static void init() { + executor = Executors.newSingleThreadScheduledExecutor(r -> { + Thread thread = new Thread(r); + thread.setName("AE2FC Network worker"); + thread.setDaemon(true); + thread.setPriority(6); + return thread; + }); + task = executor + .scheduleAtFixedRate(SPacketMEUpdateBuffer::sendBuffer, 0, Config.packetRate, TimeUnit.MILLISECONDS); + } + + public static void disable() { + task.cancel(false); + executor.shutdown(); + } + + public static void scheduleItemUpdate(EntityPlayerMP player, List stacks) { + synchronized (itemBuffer) { + if (!itemBuffer.containsKey(player)) { + itemBuffer.put(player, new LinkedHashSet<>(1024)); + } + LinkedHashSet buffer = itemBuffer.get(player); + stacks.forEach(s -> buffer.add(s.copy())); + } + } + + public static void scheduleFluidUpdate(EntityPlayerMP player, List stacks) { + synchronized (itemBuffer) { + if (!fluidBuffer.containsKey(player)) { + fluidBuffer.put(player, new LinkedHashSet<>(1024)); + } + LinkedHashSet buffer = fluidBuffer.get(player); + stacks.forEach(s -> buffer.add(s.copy())); + } + } + + public static void sendBuffer() { + synchronized (itemBuffer) { + itemBuffer.forEach((player, updates) -> { + if (updates.isEmpty()) return; + int i = 0; + SPacketMEItemInvUpdate packet = new SPacketMEItemInvUpdate(false); + Iterator it = updates.iterator(); + while (it.hasNext()) { + if (i < Config.packetSize) { + packet.appendItem(it.next()); + it.remove(); + i++; + } else { + FluidCraft.proxy.netHandler.sendTo(packet, player); + packet = new SPacketMEItemInvUpdate(false); + i = 0; + } + } + packet.setResort(true); + FluidCraft.proxy.netHandler.sendTo(packet, player); + }); + fluidBuffer.forEach((player, updates) -> { + if (updates.isEmpty()) return; + int i = 0; + SPacketMEFluidInvUpdate packet = new SPacketMEFluidInvUpdate(false); + Iterator it = updates.iterator(); + while (it.hasNext()) { + if (i < Config.packetSize) { + packet.appendFluid(it.next()); + it.remove(); + i++; + } else { + FluidCraft.proxy.netHandler.sendTo(packet, player); + packet = new SPacketMEFluidInvUpdate(false); + i = 0; + } + } + packet.setResort(true); + FluidCraft.proxy.netHandler.sendTo(packet, player); + }); + + } + } + + public static void clear(EntityPlayerMP player) { + synchronized (itemBuffer) { + if (itemBuffer.containsKey(player)) { + itemBuffer.get(player).clear(); + } + if (fluidBuffer.containsKey(player)) { + fluidBuffer.get(player).clear(); + } + } + } + + @SubscribeEvent + public void onPlayerLeave(PlayerEvent.PlayerLoggedOutEvent event) { + if (event.player instanceof EntityPlayerMP) { + clear((EntityPlayerMP) event.player); + } + } +} diff --git a/src/main/java/com/glodblock/github/proxy/CommonProxy.java b/src/main/java/com/glodblock/github/proxy/CommonProxy.java index ac106808..d218aebe 100644 --- a/src/main/java/com/glodblock/github/proxy/CommonProxy.java +++ b/src/main/java/com/glodblock/github/proxy/CommonProxy.java @@ -11,8 +11,10 @@ import com.glodblock.github.common.Config; import com.glodblock.github.common.tile.TileWalrus; import com.glodblock.github.inventory.external.AEFluidInterfaceHandler; import com.glodblock.github.loader.ItemAndBlockHolder; +import com.glodblock.github.network.SPacketMEUpdateBuffer; import com.glodblock.github.util.ModAndClassUtil; +import cpw.mods.fml.common.FMLCommonHandler; import cpw.mods.fml.common.event.FMLInitializationEvent; import cpw.mods.fml.common.event.FMLPostInitializationEvent; import cpw.mods.fml.common.event.FMLPreInitializationEvent; @@ -27,6 +29,7 @@ public class CommonProxy { public void init(FMLInitializationEvent event) { this.registerMovables(); + FMLCommonHandler.instance().bus().register(SPacketMEUpdateBuffer.class); } public void postInit(FMLPostInitializationEvent event) {