From f9befe9549229c59b5d3e8c48bffefd363082444 Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Sun, 21 Feb 2021 12:00:32 -0500 Subject: [PATCH] Calculate terminal displayed items on the server Avoids sending giant S2C packets containing all items in the network when the network contains a great variety of different items --- .../phycon/PhysicalConnectivity.kt | 2 + .../phycon/PhysicalConnectivityClient.kt | 11 +++ .../block/terminal/TerminalBlockEntity.kt | 84 +++++++++---------- .../network/block/terminal/TerminalScreen.kt | 22 +++-- .../block/terminal/TerminalScreenHandler.kt | 48 ++++++++--- .../C2STerminalUpdateDisplayedItems.kt | 50 +++++++++++ .../phycon/networking/ClientReceiver.kt | 11 +++ .../S2CTerminalUpdateDisplayedItems.kt | 57 +++++++++++++ .../net/shadowfacts/phycon/util/SortMode.kt | 2 +- 9 files changed, 223 insertions(+), 64 deletions(-) create mode 100644 src/main/kotlin/net/shadowfacts/phycon/networking/C2STerminalUpdateDisplayedItems.kt create mode 100644 src/main/kotlin/net/shadowfacts/phycon/networking/ClientReceiver.kt create mode 100644 src/main/kotlin/net/shadowfacts/phycon/networking/S2CTerminalUpdateDisplayedItems.kt diff --git a/src/main/kotlin/net/shadowfacts/phycon/PhysicalConnectivity.kt b/src/main/kotlin/net/shadowfacts/phycon/PhysicalConnectivity.kt index 1a1459b..19ba8c5 100644 --- a/src/main/kotlin/net/shadowfacts/phycon/PhysicalConnectivity.kt +++ b/src/main/kotlin/net/shadowfacts/phycon/PhysicalConnectivity.kt @@ -7,6 +7,7 @@ import net.shadowfacts.phycon.init.PhyBlocks import net.shadowfacts.phycon.init.PhyItems import net.shadowfacts.phycon.init.PhyScreens import net.shadowfacts.phycon.networking.C2STerminalRequestItem +import net.shadowfacts.phycon.networking.C2STerminalUpdateDisplayedItems import net.shadowfacts.phycon.networking.ServerReceiver /** @@ -23,6 +24,7 @@ object PhysicalConnectivity: ModInitializer { PhyScreens.init() registerGlobalReceiver(C2STerminalRequestItem) + registerGlobalReceiver(C2STerminalUpdateDisplayedItems) } private fun registerGlobalReceiver(receiver: ServerReceiver) { diff --git a/src/main/kotlin/net/shadowfacts/phycon/PhysicalConnectivityClient.kt b/src/main/kotlin/net/shadowfacts/phycon/PhysicalConnectivityClient.kt index 4575fbb..c32c7b4 100644 --- a/src/main/kotlin/net/shadowfacts/phycon/PhysicalConnectivityClient.kt +++ b/src/main/kotlin/net/shadowfacts/phycon/PhysicalConnectivityClient.kt @@ -2,19 +2,30 @@ package net.shadowfacts.phycon import net.fabricmc.api.ClientModInitializer import net.fabricmc.fabric.api.blockrenderlayer.v1.BlockRenderLayerMap +import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking import net.fabricmc.fabric.api.client.screenhandler.v1.ScreenRegistry import net.minecraft.client.render.RenderLayer import net.shadowfacts.phycon.init.PhyBlocks import net.shadowfacts.phycon.init.PhyScreens import net.shadowfacts.phycon.network.block.terminal.TerminalScreen +import net.shadowfacts.phycon.networking.ClientReceiver +import net.shadowfacts.phycon.networking.S2CTerminalUpdateDisplayedItems /** * @author shadowfacts */ object PhysicalConnectivityClient: ClientModInitializer { + override fun onInitializeClient() { BlockRenderLayerMap.INSTANCE.putBlock(PhyBlocks.CABLE, RenderLayer.getTranslucent()) ScreenRegistry.register(PhyScreens.TERMINAL_SCREEN_HANDLER, ::TerminalScreen) + + registerGlobalReceiver(S2CTerminalUpdateDisplayedItems) } + + private fun registerGlobalReceiver(receiver: ClientReceiver) { + ClientPlayNetworking.registerGlobalReceiver(receiver.CHANNEL, receiver) + } + } diff --git a/src/main/kotlin/net/shadowfacts/phycon/network/block/terminal/TerminalBlockEntity.kt b/src/main/kotlin/net/shadowfacts/phycon/network/block/terminal/TerminalBlockEntity.kt index eab52cf..3d71587 100644 --- a/src/main/kotlin/net/shadowfacts/phycon/network/block/terminal/TerminalBlockEntity.kt +++ b/src/main/kotlin/net/shadowfacts/phycon/network/block/terminal/TerminalBlockEntity.kt @@ -56,7 +56,7 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL), private var observers = 0 val cachedNetItems = ItemStackCollections.intMap() - var cachedSortedNetItems = listOf() +// var cachedSortedNetItems = listOf() var netItemObserver: WeakReference? = null @@ -86,14 +86,12 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL), private fun handleReadInventory(packet: ReadInventoryPacket) { inventoryCache[packet.source] = packet.inventory - updateNetItems() - sync() + updateAndSync() } private fun handleDeviceRemoved(packet: DeviceRemovedPacket) { inventoryCache.remove(packet.source) - updateNetItems() - sync() + updateAndSync() } private fun handleStackLocation(packet: StackLocationPacket) { @@ -113,12 +111,17 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL), // this happens outside the normal update loop because by receiving the item stack packet // we "know" how much the count in the source inventory has changed - updateNetItems() - sync() + updateAndSync() return remaining } + private fun updateAndSync() { + updateNetItems() + sync() + netItemObserver?.get()?.netItemsChanged() + } + private fun updateNetItems() { cachedNetItems.clear() for (inventory in inventoryCache.values) { @@ -127,12 +130,12 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL), cachedNetItems.mergeInt(stack, amount) { a, b -> a + b } } } - // todo: is the map necessary or is just the sorted list enough? - cachedSortedNetItems = cachedNetItems.object2IntEntrySet().sortedByDescending { it.intValue }.map { - val stack = it.key.copy() - stack.count = it.intValue - stack - } +// // todo: is the map necessary or is just the sorted list enough? +// cachedSortedNetItems = cachedNetItems.object2IntEntrySet().sortedByDescending { it.intValue }.map { +// val stack = it.key.copy() +// stack.count = it.intValue +// stack +// } } private fun beginInsertions() { @@ -175,21 +178,15 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL), finishTimedOutPendingInsertions() } - if (observers > 0) { - if (world!!.isClient) { - println(cachedNetItems) - } else { - updateNetItems() - sync() - } + if (observers > 0 && !world!!.isClient) { + updateAndSync() } } } fun onActivate(player: PlayerEntity) { if (!world!!.isClient) { - updateNetItems() - sync() + updateAndSync() inventoryCache.clear() sendPacket(RequestInventoryPacket(ipAddress)) @@ -238,8 +235,7 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL), internalBuffer.setStack(insertion.bufferSlot, remaining) // as with extracting, we "know" the new amounts and so can update instantly without actually sending out packets - updateNetItems() - sync() + updateAndSync() return remaining } @@ -263,31 +259,31 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL), override fun toClientTag(tag: CompoundTag): CompoundTag { tag.put("InternalBuffer", internalBuffer.toTag()) - val list = ListTag() - tag.put("CachedNetItems", list) - for ((stack, amount) in cachedNetItems) { - val entryTag = stack.toTag(CompoundTag()) - entryTag.putInt("NetAmount", amount) - list.add(entryTag) - } +// val list = ListTag() +// tag.put("CachedNetItems", list) +// for ((stack, amount) in cachedNetItems) { +// val entryTag = stack.toTag(CompoundTag()) +// entryTag.putInt("NetAmount", amount) +// list.add(entryTag) +// } return tag } override fun fromClientTag(tag: CompoundTag) { internalBuffer.fromTag(tag.getCompound("InternalBuffer")) - val list = tag.getList("CachedNetItems", 10) - cachedNetItems.clear() - for (entryTag in list) { - val stack = ItemStack.fromTag(entryTag as CompoundTag) - val netAmount = entryTag.getInt("NetAmount") - cachedNetItems[stack] = netAmount - } - netItemObserver?.get()?.netItemsChanged() - cachedSortedNetItems = cachedNetItems.object2IntEntrySet().sortedByDescending { it.intValue }.map { - val stack = it.key.copy() - stack.count = it.intValue - stack - } +// val list = tag.getList("CachedNetItems", 10) +// cachedNetItems.clear() +// for (entryTag in list) { +// val stack = ItemStack.fromTag(entryTag as CompoundTag) +// val netAmount = entryTag.getInt("NetAmount") +// cachedNetItems[stack] = netAmount +// } +// netItemObserver?.get()?.netItemsChanged() +// cachedSortedNetItems = cachedNetItems.object2IntEntrySet().sortedByDescending { it.intValue }.map { +// val stack = it.key.copy() +// stack.count = it.intValue +// stack +// } } interface NetItemObserver { diff --git a/src/main/kotlin/net/shadowfacts/phycon/network/block/terminal/TerminalScreen.kt b/src/main/kotlin/net/shadowfacts/phycon/network/block/terminal/TerminalScreen.kt index 298e588..6dd5ee6 100644 --- a/src/main/kotlin/net/shadowfacts/phycon/network/block/terminal/TerminalScreen.kt +++ b/src/main/kotlin/net/shadowfacts/phycon/network/block/terminal/TerminalScreen.kt @@ -17,6 +17,7 @@ import net.minecraft.text.LiteralText import net.minecraft.text.Text import net.minecraft.util.Identifier import net.shadowfacts.phycon.PhysicalConnectivity +import net.shadowfacts.phycon.networking.C2STerminalUpdateDisplayedItems import net.shadowfacts.phycon.util.SortMode import org.lwjgl.glfw.GLFW import java.lang.NumberFormatException @@ -34,6 +35,7 @@ class TerminalScreen(handler: TerminalScreenHandler, playerInv: PlayerInventory, } private lateinit var searchBox: TextFieldWidget + private lateinit var sortButton: SortButton private lateinit var amountBox: TextFieldWidget private var dialogStack = ItemStack.EMPTY private var showingAmountDialog = false @@ -77,9 +79,8 @@ class TerminalScreen(handler: TerminalScreenHandler, playerInv: PlayerInventory, searchBox.setEditableColor(0xffffff) addChild(searchBox) - val sortButton = SortButton(x + 256, y + 0, handler.sortMode, { - handler.sortMode = it - handler.netItemsChanged() + sortButton = SortButton(x + 256, y + 0, handler.sortMode, { + requestUpdatedItems() }, ::renderTooltip) addButton(sortButton) @@ -138,6 +139,13 @@ class TerminalScreen(handler: TerminalScreenHandler, playerInv: PlayerInventory, doDialogRequest() } dialogChildren.add(request) + + requestUpdatedItems() + } + + private fun requestUpdatedItems() { + val player = MinecraftClient.getInstance().player!! + player.networkHandler.sendPacket(C2STerminalUpdateDisplayedItems(handler.terminal, searchBox.text, sortButton.mode)) } override fun tick() { @@ -279,7 +287,7 @@ class TerminalScreen(handler: TerminalScreenHandler, playerInv: PlayerInventory, val oldText = searchBox.text if (searchBox.charTyped(c, i)) { if (searchBox.text != oldText) { - search() + requestUpdatedItems() } return true } @@ -307,7 +315,7 @@ class TerminalScreen(handler: TerminalScreenHandler, playerInv: PlayerInventory, val oldText = searchBox.text if (searchBox.keyPressed(key, j, k)) { if (searchBox.text != oldText) { - search() + requestUpdatedItems() } return true } @@ -319,10 +327,6 @@ class TerminalScreen(handler: TerminalScreenHandler, playerInv: PlayerInventory, } } - private fun search() { - screenHandler.search(searchBox.text) - } - private fun doDialogRequest() { showingAmountDialog = false handler.requestItem(client!!.player!!, dialogStack, amountBox.intValue) diff --git a/src/main/kotlin/net/shadowfacts/phycon/network/block/terminal/TerminalScreenHandler.kt b/src/main/kotlin/net/shadowfacts/phycon/network/block/terminal/TerminalScreenHandler.kt index 7fe7b2c..33756e7 100644 --- a/src/main/kotlin/net/shadowfacts/phycon/network/block/terminal/TerminalScreenHandler.kt +++ b/src/main/kotlin/net/shadowfacts/phycon/network/block/terminal/TerminalScreenHandler.kt @@ -8,12 +8,14 @@ import net.minecraft.entity.player.PlayerInventory import net.minecraft.item.ItemStack import net.minecraft.network.PacketByteBuf import net.minecraft.screen.ScreenHandler +import net.minecraft.server.network.ServerPlayerEntity import net.minecraft.util.Identifier import net.minecraft.util.registry.Registry import net.shadowfacts.phycon.PhysicalConnectivity import net.shadowfacts.phycon.init.PhyBlocks import net.shadowfacts.phycon.init.PhyScreens import net.shadowfacts.phycon.networking.C2STerminalRequestItem +import net.shadowfacts.phycon.networking.S2CTerminalUpdateDisplayedItems import net.shadowfacts.phycon.util.SortMode import java.lang.ref.WeakReference import kotlin.math.ceil @@ -22,7 +24,7 @@ import kotlin.math.min /** * @author shadowfacts */ -class TerminalScreenHandler(syncId: Int, playerInv: PlayerInventory, val terminal: TerminalBlockEntity): ScreenHandler(PhyScreens.TERMINAL_SCREEN_HANDLER, syncId), +class TerminalScreenHandler(syncId: Int, val playerInv: PlayerInventory, val terminal: TerminalBlockEntity): ScreenHandler(PhyScreens.TERMINAL_SCREEN_HANDLER, syncId), TerminalBlockEntity.NetItemObserver { companion object { @@ -32,6 +34,18 @@ class TerminalScreenHandler(syncId: Int, playerInv: PlayerInventory, val termina private val fakeInv = FakeInventory(this) private var searchQuery: String = "" var sortMode = SortMode.COUNT_HIGH_FIRST + private set + private var itemEntries = listOf() + set(value) { + field = value + if (terminal.world!!.isClient) { + itemsForDisplay = value.map { + val stack = it.stack.copy() + stack.count = it.amount + stack + } + } + } var itemsForDisplay = listOf() private set @@ -39,8 +53,10 @@ class TerminalScreenHandler(syncId: Int, playerInv: PlayerInventory, val termina this(syncId, playerInv, PhyBlocks.TERMINAL.getBlockEntity(playerInv.player.world, buf.readBlockPos())!!) init { - terminal.netItemObserver = WeakReference(this) - netItemsChanged() + if (!terminal.world!!.isClient) { + terminal.netItemObserver = WeakReference(this) + netItemsChanged() + } // network for (y in 0 until 6) { @@ -69,6 +85,9 @@ class TerminalScreenHandler(syncId: Int, playerInv: PlayerInventory, val termina } override fun netItemsChanged() { + val player = playerInv.player + assert(player is ServerPlayerEntity) + val filtered = terminal.cachedNetItems.object2IntEntrySet().filter { if (searchQuery.isBlank()) return@filter true if (searchQuery.startsWith('@')) { @@ -88,18 +107,25 @@ class TerminalScreenHandler(syncId: Int, playerInv: PlayerInventory, val termina SortMode.ALPHABETICAL -> filtered.sortedBy { it.key.name.string } } - itemsForDisplay = sorted.map { - val stack = it.key.copy() - stack.count = it.intValue - stack - } + itemEntries = sorted.map { Entry(it.key, it.intValue) } + + (player as ServerPlayerEntity).networkHandler.sendPacket(S2CTerminalUpdateDisplayedItems(terminal, itemEntries, searchQuery, sortMode)) } - fun search(query: String) { - searchQuery = query + fun sendUpdatedItemsToClient(player: ServerPlayerEntity, query: String, sortMode: SortMode) { + this.searchQuery = query + this.sortMode = sortMode netItemsChanged() } + fun receivedUpdatedItemsFromServer(entries: List, query: String, sortMode: SortMode) { + assert(playerInv.player.world.isClient) + + this.searchQuery = query + this.sortMode = sortMode + itemEntries = entries + } + override fun canUse(player: PlayerEntity): Boolean { return true } @@ -214,4 +240,6 @@ class TerminalScreenHandler(syncId: Int, playerInv: PlayerInventory, val termina fun isNetworkSlot(id: Int) = id in 0 until bufferSlotsStart fun isBufferSlot(id: Int) = id in bufferSlotsStart until playerSlotsStart fun isPlayerSlot(id: Int) = id >= playerSlotsStart + + data class Entry(val stack: ItemStack, val amount: Int) } diff --git a/src/main/kotlin/net/shadowfacts/phycon/networking/C2STerminalUpdateDisplayedItems.kt b/src/main/kotlin/net/shadowfacts/phycon/networking/C2STerminalUpdateDisplayedItems.kt new file mode 100644 index 0000000..eed71b9 --- /dev/null +++ b/src/main/kotlin/net/shadowfacts/phycon/networking/C2STerminalUpdateDisplayedItems.kt @@ -0,0 +1,50 @@ +package net.shadowfacts.phycon.networking + +import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking +import net.fabricmc.fabric.api.networking.v1.PacketByteBufs +import net.fabricmc.fabric.api.networking.v1.PacketSender +import net.minecraft.network.Packet +import net.minecraft.network.PacketByteBuf +import net.minecraft.server.MinecraftServer +import net.minecraft.server.network.ServerPlayNetworkHandler +import net.minecraft.server.network.ServerPlayerEntity +import net.minecraft.util.Identifier +import net.shadowfacts.phycon.PhysicalConnectivity +import net.shadowfacts.phycon.network.block.terminal.TerminalBlockEntity +import net.shadowfacts.phycon.network.block.terminal.TerminalScreenHandler +import net.shadowfacts.phycon.util.SortMode + +/** + * @author shadowfacts + */ +object C2STerminalUpdateDisplayedItems: ServerReceiver { + + override val CHANNEL = Identifier(PhysicalConnectivity.MODID, "terminal_update_displayed") + + operator fun invoke(terminal: TerminalBlockEntity, query: String, sortMode: SortMode): Packet<*> { + val buf = PacketByteBufs.create() + + buf.writeIdentifier(terminal.world!!.registryKey.value) + buf.writeBlockPos(terminal.pos) + + buf.writeString(query) + buf.writeVarInt(sortMode.ordinal) + + return ClientPlayNetworking.createC2SPacket(CHANNEL, buf) + } + + override fun receive(server: MinecraftServer, player: ServerPlayerEntity, handler: ServerPlayNetworkHandler, buf: PacketByteBuf, responseSender: PacketSender) { + val dimID = buf.readIdentifier() + val pos = buf.readBlockPos() + val query = buf.readString() + val sortMode = SortMode.values()[buf.readVarInt()] + + server.execute { + if (player.world.registryKey.value != dimID) return@execute + val screenHandler = player.currentScreenHandler + if (screenHandler !is TerminalScreenHandler) return@execute + if (screenHandler.terminal.pos != pos) return@execute + screenHandler.sendUpdatedItemsToClient(player, query, sortMode) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/net/shadowfacts/phycon/networking/ClientReceiver.kt b/src/main/kotlin/net/shadowfacts/phycon/networking/ClientReceiver.kt new file mode 100644 index 0000000..9fa0cf6 --- /dev/null +++ b/src/main/kotlin/net/shadowfacts/phycon/networking/ClientReceiver.kt @@ -0,0 +1,11 @@ +package net.shadowfacts.phycon.networking + +import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking +import net.minecraft.util.Identifier + +/** + * @author shadowfacts + */ +interface ClientReceiver: ClientPlayNetworking.PlayChannelHandler { + val CHANNEL: Identifier +} \ No newline at end of file diff --git a/src/main/kotlin/net/shadowfacts/phycon/networking/S2CTerminalUpdateDisplayedItems.kt b/src/main/kotlin/net/shadowfacts/phycon/networking/S2CTerminalUpdateDisplayedItems.kt new file mode 100644 index 0000000..db0efe9 --- /dev/null +++ b/src/main/kotlin/net/shadowfacts/phycon/networking/S2CTerminalUpdateDisplayedItems.kt @@ -0,0 +1,57 @@ +package net.shadowfacts.phycon.networking + +import net.fabricmc.fabric.api.networking.v1.PacketByteBufs +import net.fabricmc.fabric.api.networking.v1.PacketSender +import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking +import net.minecraft.client.MinecraftClient +import net.minecraft.client.network.ClientPlayNetworkHandler +import net.minecraft.network.Packet +import net.minecraft.network.PacketByteBuf +import net.shadowfacts.phycon.network.block.terminal.TerminalBlockEntity +import net.shadowfacts.phycon.network.block.terminal.TerminalScreenHandler +import net.shadowfacts.phycon.util.SortMode + +/** + * @author shadowfacts + */ +object S2CTerminalUpdateDisplayedItems: ClientReceiver { + override val CHANNEL = C2STerminalUpdateDisplayedItems.CHANNEL + + operator fun invoke(terminal: TerminalBlockEntity, entries: List, query: String, sortMode: SortMode): Packet<*> { + val buf = PacketByteBufs.create() + + buf.writeIdentifier(terminal.world!!.registryKey.value) + buf.writeBlockPos(terminal.pos) + + buf.writeVarInt(entries.size) + for (e in entries) { + buf.writeItemStack(e.stack) + buf.writeVarInt(e.amount) + } + + buf.writeString(query) + buf.writeVarInt(sortMode.ordinal) + + return ServerPlayNetworking.createS2CPacket(CHANNEL, buf) + } + + override fun receive(client: MinecraftClient, handler: ClientPlayNetworkHandler, buf: PacketByteBuf, responseSender: PacketSender) { + val dimID = buf.readIdentifier() + val pos = buf.readBlockPos() + val entryCount = buf.readVarInt() + val entries = ArrayList(entryCount) + for (i in 0 until entryCount) { + entries.add(TerminalScreenHandler.Entry(buf.readItemStack(), buf.readVarInt())) + } + val query = buf.readString() + val sortMode = SortMode.values()[buf.readVarInt()] + + client.execute { + if (client.player!!.world.registryKey.value != dimID) return@execute + val screenHandler = client.player!!.currentScreenHandler + if (screenHandler !is TerminalScreenHandler) return@execute + if (screenHandler.terminal.pos != pos) return@execute + screenHandler.receivedUpdatedItemsFromServer(entries, query, sortMode) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/net/shadowfacts/phycon/util/SortMode.kt b/src/main/kotlin/net/shadowfacts/phycon/util/SortMode.kt index ad4ad54..7b61025 100644 --- a/src/main/kotlin/net/shadowfacts/phycon/util/SortMode.kt +++ b/src/main/kotlin/net/shadowfacts/phycon/util/SortMode.kt @@ -12,7 +12,7 @@ enum class SortMode { ALPHABETICAL; val prev: SortMode - get() = values()[(ordinal - 1) % values().size] + get() = values()[(ordinal - 1 + values().size) % values().size] val next: SortMode get() = values()[(ordinal + 1) % values().size]