Compare commits

...

4 Commits

15 changed files with 177 additions and 93 deletions

View File

@ -3,7 +3,7 @@ package net.shadowfacts.phycon.api;
import net.minecraft.block.BlockState;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.world.World;
import net.minecraft.world.WorldAccess;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@ -15,9 +15,9 @@ import java.util.Collection;
public interface NetworkComponentBlock {
@NotNull
Collection<Direction> getNetworkConnectedSides(@NotNull BlockState state, @NotNull World world, @NotNull BlockPos pos);
Collection<Direction> getNetworkConnectedSides(@NotNull BlockState state, @NotNull WorldAccess world, @NotNull BlockPos pos);
@Nullable
Interface getNetworkInterfaceForSide(@NotNull Direction side, @NotNull BlockState state, @NotNull World world, @NotNull BlockPos pos);
Interface getNetworkInterfaceForSide(@NotNull Direction side, @NotNull BlockState state, @NotNull WorldAccess world, @NotNull BlockPos pos);
}

View File

@ -5,6 +5,7 @@ import net.minecraft.entity.player.PlayerEntity
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction
import net.minecraft.world.World
import net.minecraft.world.WorldAccess
import net.shadowfacts.phycon.api.Interface
import net.shadowfacts.phycon.api.NetworkComponentBlock
import net.shadowfacts.phycon.block.BlockWithEntity
@ -14,9 +15,9 @@ import net.shadowfacts.phycon.block.BlockWithEntity
*/
abstract class DeviceBlock<T: DeviceBlockEntity>(settings: Settings): BlockWithEntity<T>(settings), NetworkComponentBlock {
abstract override fun getNetworkConnectedSides(state: BlockState, world: World, pos: BlockPos): Collection<Direction>
abstract override fun getNetworkConnectedSides(state: BlockState, world: WorldAccess, pos: BlockPos): Collection<Direction>
override fun getNetworkInterfaceForSide(side: Direction, state: BlockState, world: World, pos: BlockPos): Interface? {
override fun getNetworkInterfaceForSide(side: Direction, state: BlockState, world: WorldAccess, pos: BlockPos): Interface? {
return getBlockEntity(world, pos)!!
}

View File

@ -45,7 +45,7 @@ abstract class DeviceBlockEntity(type: BlockEntityType<*>): BlockEntity(type),
private val arpTable = mutableMapOf<IPAddress, MACAddress>()
private val packetQueue = LinkedList<PendingPacket>()
protected var counter: Long = 0
var counter: Long = 0
override fun getIPAddress() = ipAddress
override fun getMACAddress() = macAddress

View File

@ -67,7 +67,7 @@ class CableBlock: Block(
}
}
override fun getNetworkConnectedSides(state: BlockState, world: World, pos: BlockPos): Collection<Direction> {
override fun getNetworkConnectedSides(state: BlockState, world: WorldAccess, pos: BlockPos): Collection<Direction> {
val set = EnumSet.noneOf(Direction::class.java)
for ((side, prop) in CONNECTIONS) {
if (state[prop] == CableConnection.ON) {
@ -100,8 +100,10 @@ class CableBlock: Block(
}
private fun getConnectionStateInDirection(world: WorldAccess, pos: BlockPos, direction: Direction): CableConnection {
val state = world.getBlockState(pos.offset(direction))
return when (state.block) {
val offsetPos = pos.offset(direction)
val state = world.getBlockState(offsetPos)
val block = state.block
return when (block) {
this -> {
val prop = CONNECTIONS[direction.opposite]
when (state[prop]) {
@ -109,12 +111,18 @@ class CableBlock: Block(
else -> CableConnection.ON
}
}
is NetworkComponentBlock -> CableConnection.ON
is NetworkComponentBlock -> {
if (block.getNetworkConnectedSides(state, world, offsetPos).contains(direction.opposite)) {
CableConnection.ON
} else {
CableConnection.OFF
}
}
else -> CableConnection.OFF
}
}
override fun getNetworkInterfaceForSide(side: Direction, state: BlockState, world: World, pos: BlockPos): Interface? {
override fun getNetworkInterfaceForSide(side: Direction, state: BlockState, world: WorldAccess, pos: BlockPos): Interface? {
// cables don't have network interfaces
return null
}

View File

@ -16,6 +16,7 @@ import net.minecraft.util.shape.VoxelShape
import net.minecraft.util.shape.VoxelShapes
import net.minecraft.world.BlockView
import net.minecraft.world.World
import net.minecraft.world.WorldAccess
import net.shadowfacts.phycon.PhysicalConnectivity
import net.shadowfacts.phycon.api.Interface
import net.shadowfacts.phycon.network.DeviceBlock
@ -67,11 +68,11 @@ class ExtractorBlock: DeviceBlock<ExtractorBlockEntity>(Settings.of(Material.MET
}
}
override fun getNetworkConnectedSides(state: BlockState, world: World, pos: BlockPos): Collection<Direction> {
override fun getNetworkConnectedSides(state: BlockState, world: WorldAccess, pos: BlockPos): Collection<Direction> {
return EnumSet.of(state[FACING].opposite)
}
override fun getNetworkInterfaceForSide(side: Direction, state: BlockState, world: World, pos: BlockPos): Interface? {
override fun getNetworkInterfaceForSide(side: Direction, state: BlockState, world: WorldAccess, pos: BlockPos): Interface? {
return if (side == state[FACING].opposite) {
getBlockEntity(world, pos)
} else {

View File

@ -13,6 +13,7 @@ import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction
import net.minecraft.world.BlockView
import net.minecraft.world.World
import net.minecraft.world.WorldAccess
import net.shadowfacts.phycon.PhysicalConnectivity
import net.shadowfacts.phycon.api.Interface
import net.shadowfacts.phycon.network.DeviceBlock
@ -29,11 +30,11 @@ class MinerBlock: DeviceBlock<MinerBlockEntity>(Settings.of(Material.METAL)) {
val FACING = Properties.FACING
}
override fun getNetworkConnectedSides(state: BlockState, world: World, pos: BlockPos): Collection<Direction> {
override fun getNetworkConnectedSides(state: BlockState, world: WorldAccess, pos: BlockPos): Collection<Direction> {
return EnumSet.of(state[FACING].opposite)
}
override fun getNetworkInterfaceForSide(side: Direction, state: BlockState, world: World, pos: BlockPos): Interface? {
override fun getNetworkInterfaceForSide(side: Direction, state: BlockState, world: WorldAccess, pos: BlockPos): Interface? {
return if (side == state[FACING]) {
null
} else {

View File

@ -14,7 +14,11 @@ import net.shadowfacts.phycon.api.packet.Packet
import net.shadowfacts.phycon.api.util.IPAddress
import net.shadowfacts.phycon.init.PhyBlockEntities
import net.shadowfacts.phycon.network.DeviceBlockEntity
import net.shadowfacts.phycon.network.block.terminal.TerminalBlockEntity
import net.shadowfacts.phycon.network.component.NetworkStackDispatcher
import net.shadowfacts.phycon.network.component.NetworkStackProvider
import net.shadowfacts.phycon.network.component.handleItemStack
import net.shadowfacts.phycon.network.component.spawnItemStack
import net.shadowfacts.phycon.network.packet.*
import kotlin.math.min
@ -22,18 +26,24 @@ import kotlin.math.min
* @author shadowfacts
*/
class MinerBlockEntity: DeviceBlockEntity(PhyBlockEntities.MINER),
NetworkStackProvider {
NetworkStackProvider,
NetworkStackDispatcher<MinerBlockEntity.PendingInsertion> {
private val facing: Direction
get() = cachedState[MinerBlock.FACING]
private val invProxy = MinerInvProxy(this)
override val pendingInsertions = mutableListOf<PendingInsertion>()
override val dispatchStackTimeout = TerminalBlockEntity.INSERTION_TIMEOUT
override fun handle(packet: Packet) {
when (packet) {
is RequestInventoryPacket -> handleRequestInventory(packet)
is LocateStackPacket -> handleLocateStack(packet)
is ExtractStackPacket -> handleExtractStack(packet)
is CapacityPacket -> handleCapacity(packet)
is ItemStackPacket -> handleItemStack(packet)
}
}
@ -50,7 +60,7 @@ class MinerBlockEntity: DeviceBlockEntity(PhyBlockEntities.MINER),
private fun handleExtractStack(packet: ExtractStackPacket) {
// always recalculate immediately before breaking
val drops = invProxy.getDrops(true)
val drops = invProxy.getDrops(recalculate = true)
if (invProxy.getAmount(packet.stack) > 0) {
world!!.breakBlock(pos.offset(facing), false)
@ -77,11 +87,18 @@ class MinerBlockEntity: DeviceBlockEntity(PhyBlockEntities.MINER),
// dump any remaining drops into the network
for (droppedStack in drops) {
if (droppedStack.isEmpty) continue
sendPacket(ItemStackPacket(droppedStack, ipAddress, IPAddress.BROADCAST))
dispatchItemStack(droppedStack)
}
}
}
override fun doHandleItemStack(packet: ItemStackPacket): ItemStack {
// miner can't receive stacks, so remaining is the entire packet stack
return packet.stack
}
override fun createPendingInsertion(stack: ItemStack) = PendingInsertion(stack, counter)
class MinerInvProxy(val miner: MinerBlockEntity): GroupedItemInvView {
private var cachedState: BlockState? = null
private var cachedDrops: List<ItemStack>? = null
@ -96,7 +113,7 @@ class MinerBlockEntity: DeviceBlockEntity(PhyBlockEntities.MINER),
fun getDrops(recalculate: Boolean = false): List<ItemStack> {
val targetPos = pos.offset(facing)
val realState = world.getBlockState(targetPos)
// todo: does BlockState.equals actually work?
// todo: does BlockState.equals actually work or is reference equality fine for BlockStates?
if (cachedDrops == null || realState != cachedState || recalculate) {
cachedState = realState
val be = if (realState.block.hasBlockEntity()) world.getBlockEntity(targetPos) else null
@ -124,4 +141,7 @@ class MinerBlockEntity: DeviceBlockEntity(PhyBlockEntities.MINER),
}
}
class PendingInsertion(stack: ItemStack, timestamp: Long): NetworkStackDispatcher.PendingInsertion<PendingInsertion>(stack, timestamp) {
}
}

View File

@ -63,13 +63,13 @@ class InterfaceBlock: DeviceBlock<InterfaceBlockEntity>(Settings.of(Material.MET
}
}
override fun getNetworkConnectedSides(state: BlockState, world: World, pos: BlockPos): Collection<Direction> {
override fun getNetworkConnectedSides(state: BlockState, world: WorldAccess, pos: BlockPos): Collection<Direction> {
val set = EnumSet.of(state[CABLE_CONNECTION])
set.remove(state[FACING])
return set
}
override fun getNetworkInterfaceForSide(side: Direction, state: BlockState, world: World, pos: BlockPos): Interface? {
override fun getNetworkInterfaceForSide(side: Direction, state: BlockState, world: WorldAccess, pos: BlockPos): Interface? {
return if (side == state[FACING]) {
null
} else {

View File

@ -9,6 +9,7 @@ import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction
import net.minecraft.world.BlockView
import net.minecraft.world.World
import net.minecraft.world.WorldAccess
import net.shadowfacts.phycon.PhysicalConnectivity
import net.shadowfacts.phycon.api.Interface
import net.shadowfacts.phycon.api.NetworkComponentBlock
@ -26,11 +27,11 @@ class SwitchBlock: BlockWithEntity<SwitchBlockEntity>(Settings.of(Material.METAL
val ID = Identifier(PhysicalConnectivity.MODID, "switch")
}
override fun getNetworkConnectedSides(state: BlockState, world: World, pos: BlockPos): Collection<Direction> {
override fun getNetworkConnectedSides(state: BlockState, world: WorldAccess, pos: BlockPos): Collection<Direction> {
return EnumSet.allOf(Direction::class.java)
}
override fun getNetworkInterfaceForSide(side: Direction, state: BlockState, world: World, pos: BlockPos): Interface? {
override fun getNetworkInterfaceForSide(side: Direction, state: BlockState, world: WorldAccess, pos: BlockPos): Interface? {
return getBlockEntity(world, pos)?.interfaces?.find { it.side == side }
}

View File

@ -13,6 +13,7 @@ import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction
import net.minecraft.world.BlockView
import net.minecraft.world.World
import net.minecraft.world.WorldAccess
import net.shadowfacts.phycon.PhysicalConnectivity
import net.shadowfacts.phycon.api.NetworkComponentBlock
import net.shadowfacts.phycon.network.DeviceBlock
@ -29,7 +30,7 @@ class TerminalBlock: DeviceBlock<TerminalBlockEntity>(Settings.of(Material.METAL
val ID = Identifier(PhysicalConnectivity.MODID, "terminal")
}
override fun getNetworkConnectedSides(state: BlockState, world: World, pos: BlockPos): Collection<Direction> {
override fun getNetworkConnectedSides(state: BlockState, world: WorldAccess, pos: BlockPos): Collection<Direction> {
return EnumSet.allOf(Direction::class.java)
}

View File

@ -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<TerminalBlockEntity.PendingInsertion> {
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<IPAddress, GroupedItemInvView>()
@ -50,7 +52,8 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL), Invento
private val locateRequestQueue = LinkedList<StackLocateRequest>()
private val pendingRequests = LinkedList<StackLocateRequest>()
private val pendingInsertions = Int2ObjectArrayMap<PendingStackInsertion>()
override val pendingInsertions = mutableListOf<PendingInsertion>()
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<PendingInsertion>(stack, timestamp) {
var bufferSlot by Delegates.notNull<Int>()
}
}
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<Pair<Int, NetworkStackReceiver>> = 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
}
}

View File

@ -11,6 +11,7 @@ import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction
import net.minecraft.world.BlockView
import net.minecraft.world.World
import net.minecraft.world.WorldAccess
import net.shadowfacts.phycon.PhysicalConnectivity
import net.shadowfacts.phycon.network.DeviceBlock
import java.util.*
@ -28,7 +29,7 @@ class DestBlock: DeviceBlock<DestBlockEntity>(Settings.of(Material.METAL)) {
return DestBlockEntity()
}
override fun getNetworkConnectedSides(state: BlockState, world: World, pos: BlockPos): MutableCollection<Direction> {
override fun getNetworkConnectedSides(state: BlockState, world: WorldAccess, pos: BlockPos): MutableCollection<Direction> {
return EnumSet.allOf(Direction::class.java)
}

View File

@ -6,7 +6,7 @@ import net.minecraft.util.Identifier
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction
import net.minecraft.world.BlockView
import net.minecraft.world.World
import net.minecraft.world.WorldAccess
import net.shadowfacts.phycon.PhysicalConnectivity
import net.shadowfacts.phycon.network.DeviceBlock
import java.util.*
@ -24,7 +24,7 @@ class SourceBlock: DeviceBlock<SourceBlockEntity>(Settings.of(Material.METAL)) {
return SourceBlockEntity()
}
override fun getNetworkConnectedSides(state: BlockState, world: World, pos: BlockPos): MutableCollection<Direction> {
override fun getNetworkConnectedSides(state: BlockState, world: WorldAccess, pos: BlockPos): MutableCollection<Direction> {
return EnumSet.allOf(Direction::class.java)
}

View File

@ -14,18 +14,22 @@ interface ItemStackPacketHandler: PacketSink, PacketSource {
fun doHandleItemStack(packet: ItemStackPacket): ItemStack
}
fun <BE> BE.handleItemStack(packet: ItemStackPacket) where BE: BlockEntity, BE: ItemStackPacketHandler {
fun <Self> Self.handleItemStack(packet: ItemStackPacket) where Self: BlockEntity, Self: ItemStackPacketHandler {
// todo: is 5 a good number?
// the max bounce count should always be odd, so the item is spawned in-world at the
if (packet.bounceCount == 5) {
// todo: calculate entity spawn point by finding non-obstructed location
val entity = ItemEntity(world!!, pos.x.toDouble(), pos.y + 1.0, pos.z.toDouble(), packet.stack)
world!!.spawnEntity(entity)
spawnItemStack(packet.stack)
return
}
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))
}
}
fun <Self> Self.spawnItemStack(stack: ItemStack) where Self: BlockEntity, Self: ItemStackPacketHandler {
// todo: calculate entity spawn point by finding non-obstructed location
val entity = ItemEntity(world!!, pos.x.toDouble(), pos.y + 1.0, pos.z.toDouble(), stack)
world!!.spawnEntity(entity)
}

View File

@ -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<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)) {
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<Self: PendingInsertion<Self>>(
val 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
pendingInsertions
.filter { it.isFinishable(this) }
.forEach(::finishInsertion)
// todo: if a timed-out insertion can't be finished, we should probably retry after some time (exponential backoff?)
}