88 lines
3.2 KiB
Kotlin
88 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
|
|
import kotlin.math.min
|
|
|
|
/**
|
|
* @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
|
|
val copy = remaining.copy()
|
|
copy.count = min(capacity, copy.count)
|
|
sendPacket(ItemStackPacket(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?)
|
|
}
|