package net.shadowfacts.phycon.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.packet.CapacityPacket import net.shadowfacts.phycon.packet.CheckCapacityPacket import net.shadowfacts.phycon.packet.ItemStackPacket import net.shadowfacts.phycon.util.copyWithCount import kotlin.math.min /** * @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) { pendingInsertions.firstOrNull { insertion -> ItemStackUtil.areEqualIgnoreAmounts(packet.stack, insertion.stack) && insertion.results.none { it.second.ipAddress == packet.source } }?.also { insertion -> insertion.results.add(packet.capacity to packet.stackReceiver) if (insertion.isFinishable(this)) { val remaining = finishInsertion(insertion) if (!remaining.isEmpty) { pendingInsertions.add(createPendingInsertion(remaining)) } } } } fun finishInsertion(insertion: Insertion): ItemStack { pendingInsertions.remove(insertion) val sortedResults = insertion.results.toMutableList()//.sortedBy { it.first }.toMutableList() sortedResults.sortWith { a, b -> // sort results first by receiver priority, and then by capacity if (a.second.receiverPriority == b.second.receiverPriority) { b.first - a.first } else { b.second.receiverPriority - a.second.receiverPriority } } // copy the insertion stack so subclasses that override this method can still see the originally dispatched stack after the super call val remaining = insertion.stack.copy() while (!remaining.isEmpty && sortedResults.isNotEmpty()) { val (capacity, receiver) = sortedResults.removeFirst() if (capacity <= 0) continue val sentCount = min(capacity, remaining.count) val copy = remaining.copyWithCount(sentCount) sendPacket(ItemStackPacket(copy, ipAddress, receiver.ipAddress)) // todo: the destination should confirm how much was actually inserted, in case of race condition remaining.count -= sentCount } return remaining } open class PendingInsertion>( var stack: ItemStack, val timestamp: Long ) { val results = mutableSetOf>() val totalCapacity: Int get() = results.fold(0) { acc, (amount, _) -> acc + amount } fun isFinishable(owner: NetworkStackDispatcher): Boolean { // can't check totalCapacity >= stack.count because we need to wait for all responses to correctly sort by priority return owner.counter - timestamp >= owner.dispatchStackTimeout } } } fun > Self.finishTimedOutPendingInsertions() where Self: BlockEntity, Self: NetworkStackDispatcher { if (world!!.isClient) return if (pendingInsertions.isEmpty()) return val finishable = pendingInsertions.filter { it.isFinishable(this) } // finishInsertion removes the object from pendingInsertions for (insertion in finishable) { val remaining = finishInsertion(insertion) if (!remaining.isEmpty) { pendingInsertions.add(createPendingInsertion(remaining)) } } // todo: if a timed-out insertion can't be finished, we should probably retry after some time (exponential backoff?) }