PhysicalConnectivity/src/main/kotlin/net/shadowfacts/phycon/component/NetworkStackDispatcher.kt

85 lines
3.2 KiB
Kotlin

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
/**
* @author shadowfacts
*/
interface NetworkStackDispatcher<Insertion: NetworkStackDispatcher.PendingInsertion<Insertion>>: ItemStackPacketHandler {
val counter: Long
val dispatchStackTimeout: Long
val pendingInsertions: MutableList<Insertion>
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)) {
val remaining = finishInsertion(insertion)
// todo: do something with remaining
}
}
}
fun finishInsertion(insertion: Insertion): ItemStack {
pendingInsertions.remove(insertion)
// todo: also sort results by interface priority
val sortedResults = insertion.results.sortedBy { it.first }.toMutableList()
// 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, 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<Self: PendingInsertion<Self>>(
var stack: ItemStack,
val timestamp: Long
) {
val results = mutableSetOf<Pair<Int, NetworkStackReceiver>>()
val totalCapacity: Int
get() = results.fold(0) { acc, (amount, _) -> acc + amount }
fun isFinishable(owner: NetworkStackDispatcher<Self>): Boolean {
return totalCapacity >= stack.count || owner.counter - timestamp >= owner.dispatchStackTimeout
}
}
}
fun <Self, Insertion: NetworkStackDispatcher.PendingInsertion<Insertion>> Self.finishTimedOutPendingInsertions() where Self: BlockEntity, Self: NetworkStackDispatcher<Insertion> {
if (world!!.isClient) return
if (pendingInsertions.isEmpty()) return
val finishable = pendingInsertions.filter { it.isFinishable(this) }
// finishInsertion removes the object from pendingInsertions
finishable.forEach(::finishInsertion)
// todo: do something with remaining?
// todo: if a timed-out insertion can't be finished, we should probably retry after some time (exponential backoff?)
}