From f9196eea565cd3eeadfe8e8a4021a421b5b5d0b5 Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Wed, 17 Feb 2021 22:09:44 -0500 Subject: [PATCH] Extract network stack dispatching logic from terminal to other interface --- .../phycon/network/DeviceBlockEntity.kt | 2 +- .../block/terminal/TerminalBlockEntity.kt | 88 ++++++------------- .../component/ItemStackPacketHandler.kt | 2 +- .../component/NetworkStackDispatcher.kt | 80 +++++++++++++++++ 4 files changed, 109 insertions(+), 63 deletions(-) create mode 100644 src/main/kotlin/net/shadowfacts/phycon/network/component/NetworkStackDispatcher.kt diff --git a/src/main/kotlin/net/shadowfacts/phycon/network/DeviceBlockEntity.kt b/src/main/kotlin/net/shadowfacts/phycon/network/DeviceBlockEntity.kt index 03850b6..42a68b9 100644 --- a/src/main/kotlin/net/shadowfacts/phycon/network/DeviceBlockEntity.kt +++ b/src/main/kotlin/net/shadowfacts/phycon/network/DeviceBlockEntity.kt @@ -45,7 +45,7 @@ abstract class DeviceBlockEntity(type: BlockEntityType<*>): BlockEntity(type), private val arpTable = mutableMapOf() private val packetQueue = LinkedList() - protected var counter: Long = 0 + var counter: Long = 0 override fun getIPAddress() = ipAddress override fun getMACAddress() = macAddress 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 88631ac..c1ff204 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 @@ -3,7 +3,6 @@ package net.shadowfacts.phycon.network.block.terminal import alexiil.mc.lib.attributes.item.GroupedItemInvView import alexiil.mc.lib.attributes.item.ItemStackCollections import alexiil.mc.lib.attributes.item.ItemStackUtil -import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap import net.fabricmc.fabric.api.block.entity.BlockEntityClientSerializable import net.fabricmc.fabric.api.screenhandler.v1.ExtendedScreenHandlerFactory import net.minecraft.block.BlockState @@ -26,23 +25,26 @@ import net.shadowfacts.phycon.api.util.IPAddress import net.shadowfacts.phycon.init.PhyBlockEntities import net.shadowfacts.phycon.network.DeviceBlockEntity import net.shadowfacts.phycon.network.NetworkUtil -import net.shadowfacts.phycon.network.component.ItemStackPacketHandler -import net.shadowfacts.phycon.network.component.NetworkStackProvider -import net.shadowfacts.phycon.network.component.NetworkStackReceiver -import net.shadowfacts.phycon.network.component.handleItemStack +import net.shadowfacts.phycon.network.component.* import net.shadowfacts.phycon.network.packet.* import java.lang.ref.WeakReference import java.util.* import kotlin.math.min +import kotlin.properties.Delegates /** * @author shadowfacts */ -class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL), InventoryChangedListener, BlockEntityClientSerializable, Tickable, ItemStackPacketHandler { +class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL), + InventoryChangedListener, + BlockEntityClientSerializable, + Tickable, + ItemStackPacketHandler, + NetworkStackDispatcher { companion object { - val LOCATE_REQUEST_TIMEOUT = 40 // ticks - val INSERTION_TIMEOUT = 40 + val LOCATE_REQUEST_TIMEOUT: Long = 40 // ticks + val INSERTION_TIMEOUT: Long = 40 } private val inventoryCache = mutableMapOf() @@ -50,7 +52,8 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL), Invento private val locateRequestQueue = LinkedList() private val pendingRequests = LinkedList() - private val pendingInsertions = Int2ObjectArrayMap() + override val pendingInsertions = mutableListOf() + override val dispatchStackTimeout = INSERTION_TIMEOUT private var observers = 0 val cachedNetItems = ItemStackCollections.intMap() @@ -117,18 +120,6 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL), Invento return remaining } - private fun handleCapacity(packet: CapacityPacket) { - val insertion = pendingInsertions.values.firstOrNull { - ItemStackUtil.areEqualIgnoreAmounts(packet.stack, it.stack) - } - if (insertion != null) { - insertion.results.add(packet.capacity to packet.stackReceiver) - if (insertion.isFinishable(counter)) { - finishInsertion(insertion) - } - } - } - private fun updateNetItems() { cachedNetItems.clear() for (inventory in inventoryCache.values) { @@ -150,19 +141,11 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL), Invento for (slot in 0 until internalBuffer.size()) { if (internalBuffer.getMode(slot) != TerminalBufferInventory.Mode.TO_NETWORK) continue - if (slot in pendingInsertions) continue + if (pendingInsertions.any { it.bufferSlot == slot }) continue val stack = internalBuffer.getStack(slot) - pendingInsertions[slot] = PendingStackInsertion(slot, stack, counter) - sendPacket(CheckCapacityPacket(stack, ipAddress, IPAddress.BROADCAST)) - } - } - - private fun finishPendingInsertions() { - if (world!!.isClient) return - - for (insertion in pendingInsertions.values) { - if (!insertion.isFinishable(counter)) continue - finishInsertion(insertion) + dispatchItemStack(stack) { insertion -> + insertion.bufferSlot = slot + } } } @@ -202,7 +185,7 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL), Invento sendEnqueuedLocateRequests() finishPendingRequests() beginInsertions() - finishPendingInsertions() + finishTimedOutPendingInsertions() } if (observers > 0) { @@ -257,24 +240,17 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL), Invento } } - private fun finishInsertion(insertion: PendingStackInsertion) { - pendingInsertions.remove(insertion.bufferSlot) + override fun createPendingInsertion(stack: ItemStack) = PendingInsertion(stack, counter) - // todo: also sort results by interface priority - val sortedResults = insertion.results.sortedBy { it.first }.toMutableList() - val remaining = insertion.stack - while (!remaining.isEmpty && sortedResults.isNotEmpty()) { - val (capacity, receivingInterface) = sortedResults.removeAt(0) - if (capacity <= 0) continue - sendPacket(ItemStackPacket(remaining.copy(), ipAddress, receivingInterface.ipAddress)) - // todo: the interface should confirm how much was actually inserted, in case of race condition - remaining.count -= capacity - } - internalBuffer.setStack(insertion.bufferSlot, remaining) + override fun finishInsertion(insertion: PendingInsertion): ItemStack { + val remaining = super.finishInsertion(insertion) + 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() + + return remaining } override fun onInventoryChanged(inv: Inventory) { @@ -327,6 +303,10 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL), Invento fun netItemsChanged() } + class PendingInsertion(stack: ItemStack, timestamp: Long): NetworkStackDispatcher.PendingInsertion(stack, timestamp) { + var bufferSlot by Delegates.notNull() + } + } data class StackLocateRequest( @@ -342,17 +322,3 @@ data class StackLocateRequest( return totalResultAmount >= amount || currentTimestamp - timestamp >= TerminalBlockEntity.LOCATE_REQUEST_TIMEOUT } } - -data class PendingStackInsertion( - val bufferSlot: Int, - val stack: ItemStack, - val timestamp: Long, - val results: MutableSet> = mutableSetOf(), -) { - val totalCapacity: Int - get() = results.fold(0) { acc, (amount, _) -> acc + amount } - - fun isFinishable(currentTimestamp: Long): Boolean { - return totalCapacity >= stack.count || currentTimestamp - timestamp >= TerminalBlockEntity.INSERTION_TIMEOUT - } -} diff --git a/src/main/kotlin/net/shadowfacts/phycon/network/component/ItemStackPacketHandler.kt b/src/main/kotlin/net/shadowfacts/phycon/network/component/ItemStackPacketHandler.kt index dbff04d..a049719 100644 --- a/src/main/kotlin/net/shadowfacts/phycon/network/component/ItemStackPacketHandler.kt +++ b/src/main/kotlin/net/shadowfacts/phycon/network/component/ItemStackPacketHandler.kt @@ -26,6 +26,6 @@ fun BE.handleItemStack(packet: ItemStackPacket) where BE: BlockEntity, BE: val remainder = doHandleItemStack(packet) // if there are any items remaining, send them back to the source with incremented bounce count if (!remainder.isEmpty) { -// sendToSingle(ItemStackPacket(remainder, packet.bounceCount + 1, macAddress, packet.source)) + sendPacket(ItemStackPacket(remainder, packet.bounceCount + 1, ipAddress, packet.source)) } } \ No newline at end of file diff --git a/src/main/kotlin/net/shadowfacts/phycon/network/component/NetworkStackDispatcher.kt b/src/main/kotlin/net/shadowfacts/phycon/network/component/NetworkStackDispatcher.kt new file mode 100644 index 0000000..d193ad7 --- /dev/null +++ b/src/main/kotlin/net/shadowfacts/phycon/network/component/NetworkStackDispatcher.kt @@ -0,0 +1,80 @@ +package net.shadowfacts.phycon.network.component + +import alexiil.mc.lib.attributes.item.ItemStackUtil +import net.minecraft.block.entity.BlockEntity +import net.minecraft.item.ItemStack +import net.shadowfacts.phycon.api.util.IPAddress +import net.shadowfacts.phycon.network.packet.CapacityPacket +import net.shadowfacts.phycon.network.packet.CheckCapacityPacket +import net.shadowfacts.phycon.network.packet.ItemStackPacket + +/** + * @author shadowfacts + */ +interface NetworkStackDispatcher>: ItemStackPacketHandler { + + val counter: Long + val dispatchStackTimeout: Long + val pendingInsertions: MutableList + + fun createPendingInsertion(stack: ItemStack): Insertion + + fun dispatchItemStack(stack: ItemStack, modifyInsertion: ((Insertion) -> Unit)? = null) { + val insertion = createPendingInsertion(stack) + modifyInsertion?.invoke(insertion) + pendingInsertions.add(insertion) + sendPacket(CheckCapacityPacket(insertion.stack, ipAddress, IPAddress.BROADCAST)) + } + + fun handleCapacity(packet: CapacityPacket) { + val insertion = pendingInsertions.firstOrNull { + ItemStackUtil.areEqualIgnoreAmounts(packet.stack, it.stack) + } + if (insertion != null) { + insertion.results.add(packet.capacity to packet.stackReceiver) + if (insertion.isFinishable(this)) { + finishInsertion(insertion) + } + } + } + + fun finishInsertion(insertion: Insertion): ItemStack { + pendingInsertions.remove(insertion) + + // todo: also sort results by interface priority + val sortedResults = insertion.results.sortedBy { it.first }.toMutableList() + val remaining = insertion.stack + while (!remaining.isEmpty && sortedResults.isNotEmpty()) { + val (capacity, receivingInterface) = sortedResults.removeFirst() + if (capacity <= 0) continue + sendPacket(ItemStackPacket(remaining.copy(), ipAddress, receivingInterface.ipAddress)) + // todo: the destination should confirm how much was actually inserted, in case of race condition + remaining.count -= capacity + } + + return remaining + } + + open class PendingInsertion>( + val stack: ItemStack, + val timestamp: Long + ) { + val results = mutableSetOf>() + + val totalCapacity: Int + get() = results.fold(0) { acc, (amount, _) -> acc + amount } + + fun isFinishable(owner: NetworkStackDispatcher): Boolean { + return totalCapacity >= stack.count || owner.counter - timestamp >= owner.dispatchStackTimeout + } + } +} + +fun > Self.finishTimedOutPendingInsertions() where Self: BlockEntity, Self: NetworkStackDispatcher { + if (world!!.isClient) return + + for (insertion in pendingInsertions) { + if (!insertion.isFinishable(this)) continue + finishInsertion(insertion) + } +}