Compare commits

..

2 Commits

6 changed files with 117 additions and 40 deletions

View File

@ -0,0 +1,15 @@
package net.shadowfacts.phycon.api;
import net.shadowfacts.phycon.api.packet.Packet;
/**
* @author shadowfacts
*/
public interface PacketSource {
// todo: better name for this
void sendToSingle(Packet packet);
void sendToAll(Packet packet);
void sendToAll(Packet packet, Iterable<PacketSink> destinations);
}

View File

@ -4,18 +4,16 @@ import net.minecraft.block.BlockState
import net.minecraft.block.entity.BlockEntity import net.minecraft.block.entity.BlockEntity
import net.minecraft.block.entity.BlockEntityType import net.minecraft.block.entity.BlockEntityType
import net.minecraft.nbt.CompoundTag import net.minecraft.nbt.CompoundTag
import net.minecraft.util.Tickable
import net.shadowfacts.phycon.api.PacketSink import net.shadowfacts.phycon.api.PacketSink
import net.shadowfacts.phycon.api.PacketSource
import net.shadowfacts.phycon.api.packet.Packet import net.shadowfacts.phycon.api.packet.Packet
import net.shadowfacts.phycon.api.util.MACAddress import net.shadowfacts.phycon.api.util.MACAddress
import net.shadowfacts.phycon.network.packet.DeviceRemovedPacket import net.shadowfacts.phycon.network.packet.DeviceRemovedPacket
import java.lang.ref.WeakReference
import java.util.*
/** /**
* @author shadowfacts * @author shadowfacts
*/ */
abstract class DeviceBlockEntity(type: BlockEntityType<*>): BlockEntity(type), PacketSink { abstract class DeviceBlockEntity(type: BlockEntityType<*>): BlockEntity(type), PacketSink, PacketSource {
var macAddress = MACAddress.random() var macAddress = MACAddress.random()
protected set protected set
@ -48,8 +46,7 @@ abstract class DeviceBlockEntity(type: BlockEntityType<*>): BlockEntity(type), P
destination.handle(packet) destination.handle(packet)
} }
// todo: better name for this override fun sendToSingle(packet: Packet) {
fun sendToSingle(packet: Packet) {
val destinations = NetworkUtil.findDestinations(world!!, pos) val destinations = NetworkUtil.findDestinations(world!!, pos)
if (destinations.size != 1) { if (destinations.size != 1) {
// todo: handle this better // todo: handle this better
@ -59,11 +56,11 @@ abstract class DeviceBlockEntity(type: BlockEntityType<*>): BlockEntity(type), P
send(packet, destinations.first()) send(packet, destinations.first())
} }
fun sendToAll(packet: Packet) { override fun sendToAll(packet: Packet) {
sendToAll(packet, NetworkUtil.findDestinations(world!!, pos)) sendToAll(packet, NetworkUtil.findDestinations(world!!, pos))
} }
fun sendToAll(packet: Packet, destinations: Iterable<PacketSink>) { override fun sendToAll(packet: Packet, destinations: Iterable<PacketSink>) {
destinations.forEach { destinations.forEach {
it.handle(packet) it.handle(packet)
} }

View File

@ -4,17 +4,19 @@ import alexiil.mc.lib.attributes.SearchOptions
import alexiil.mc.lib.attributes.Simulation import alexiil.mc.lib.attributes.Simulation
import alexiil.mc.lib.attributes.item.GroupedItemInv import alexiil.mc.lib.attributes.item.GroupedItemInv
import alexiil.mc.lib.attributes.item.ItemAttributes import alexiil.mc.lib.attributes.item.ItemAttributes
import net.minecraft.entity.ItemEntity import net.minecraft.item.ItemStack
import net.minecraft.util.math.Direction import net.minecraft.util.math.Direction
import net.shadowfacts.phycon.api.packet.Packet import net.shadowfacts.phycon.api.packet.Packet
import net.shadowfacts.phycon.init.PhyBlockEntities import net.shadowfacts.phycon.init.PhyBlockEntities
import net.shadowfacts.phycon.network.DeviceBlockEntity import net.shadowfacts.phycon.network.DeviceBlockEntity
import net.shadowfacts.phycon.network.component.ItemStackPacketHandler
import net.shadowfacts.phycon.network.component.handleItemStack
import net.shadowfacts.phycon.network.packet.* import net.shadowfacts.phycon.network.packet.*
/** /**
* @author shadowfacts * @author shadowfacts
*/ */
class InterfaceBlockEntity: DeviceBlockEntity(PhyBlockEntities.INTERFACE) { class InterfaceBlockEntity: DeviceBlockEntity(PhyBlockEntities.INTERFACE), ItemStackPacketHandler {
private val facing: Direction private val facing: Direction
get() = world!!.getBlockState(pos)[InterfaceBlock.FACING] get() = world!!.getBlockState(pos)[InterfaceBlock.FACING]
@ -73,21 +75,14 @@ class InterfaceBlockEntity: DeviceBlockEntity(PhyBlockEntities.INTERFACE) {
} }
} }
private fun handleItemStack(packet: ItemStackPacket) { override fun doHandleItemStack(packet: ItemStackPacket): ItemStack {
val inventory = getInventory() val inventory = getInventory()
if (inventory != null) { if (inventory != null) {
val remaining = inventory.insert(packet.stack) val remaining = inventory.insert(packet.stack)
if (!remaining.isEmpty) { // whatever could not be inserted will be sent back to the packet's source
// todo: should this send whatever was left back to the terminal instead of dropping it? return remaining
// todo: calculate entity spawn point by finding non-obstructed location
val entity = ItemEntity(world!!, pos.x.toDouble(), pos.y + 1.0, pos.z.toDouble(), remaining)
world!!.spawnEntity(entity)
}
} else { } else {
// todo: should the stack back to the terminal instead of dropping it? return packet.stack
// 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)
} }
} }

View File

@ -7,7 +7,6 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap
import net.fabricmc.fabric.api.block.entity.BlockEntityClientSerializable import net.fabricmc.fabric.api.block.entity.BlockEntityClientSerializable
import net.fabricmc.fabric.api.screenhandler.v1.ExtendedScreenHandlerFactory import net.fabricmc.fabric.api.screenhandler.v1.ExtendedScreenHandlerFactory
import net.minecraft.block.BlockState import net.minecraft.block.BlockState
import net.minecraft.entity.ItemEntity
import net.minecraft.entity.player.PlayerEntity import net.minecraft.entity.player.PlayerEntity
import net.minecraft.entity.player.PlayerInventory import net.minecraft.entity.player.PlayerInventory
import net.minecraft.inventory.Inventory import net.minecraft.inventory.Inventory
@ -25,16 +24,16 @@ import net.shadowfacts.phycon.api.util.MACAddress
import net.shadowfacts.phycon.init.PhyBlockEntities import net.shadowfacts.phycon.init.PhyBlockEntities
import net.shadowfacts.phycon.network.DeviceBlockEntity import net.shadowfacts.phycon.network.DeviceBlockEntity
import net.shadowfacts.phycon.network.block.netinterface.InterfaceBlockEntity import net.shadowfacts.phycon.network.block.netinterface.InterfaceBlockEntity
import net.shadowfacts.phycon.network.component.ItemStackPacketHandler
import net.shadowfacts.phycon.network.component.handleItemStack
import net.shadowfacts.phycon.network.packet.* import net.shadowfacts.phycon.network.packet.*
import net.shadowfacts.phycon.util.fromTag
import net.shadowfacts.phycon.util.toTag
import java.util.* import java.util.*
import kotlin.math.min import kotlin.math.min
/** /**
* @author shadowfacts * @author shadowfacts
*/ */
class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL), InventoryChangedListener, BlockEntityClientSerializable, Tickable { class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL), InventoryChangedListener, BlockEntityClientSerializable, Tickable, ItemStackPacketHandler {
companion object { companion object {
val LOCATE_REQUEST_TIMEOUT = 40 // ticks val LOCATE_REQUEST_TIMEOUT = 40 // ticks
@ -44,6 +43,7 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL), Invento
private val inventoryCache = mutableMapOf<MACAddress, GroupedItemInv>() private val inventoryCache = mutableMapOf<MACAddress, GroupedItemInv>()
val internalBuffer = TerminalBufferInventory(18) val internalBuffer = TerminalBufferInventory(18)
private val locateRequestQueue = LinkedList<StackLocateRequest>()
private val pendingRequests = LinkedList<StackLocateRequest>() private val pendingRequests = LinkedList<StackLocateRequest>()
private val pendingInsertions = Int2ObjectArrayMap<PendingStackInsertion>() private val pendingInsertions = Int2ObjectArrayMap<PendingStackInsertion>()
@ -85,24 +85,20 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL), Invento
if (request != null) { if (request != null) {
request.results.add(packet.amount to packet.sourceInterface) request.results.add(packet.amount to packet.sourceInterface)
if (request.totalResultAmount >= request.amount || counter - request.timestamp >= LOCATE_REQUEST_TIMEOUT || request.results.size >= inventoryCache.size) { if (request.totalResultAmount >= request.amount || counter - request.timestamp >= LOCATE_REQUEST_TIMEOUT || request.results.size >= inventoryCache.size) {
pendingRequests.remove(request)
stackLocateRequestCompleted(request) stackLocateRequestCompleted(request)
} }
} }
} }
private fun handleItemStack(packet: ItemStackPacket) { override fun doHandleItemStack(packet: ItemStackPacket): ItemStack {
val remaining = internalBuffer.insertFromNetwork(packet.stack) val remaining = internalBuffer.insertFromNetwork(packet.stack)
if (!remaining.isEmpty) {
// todo: calculate entity spawn point by finding non-obstructed location
val entity = ItemEntity(world!!, pos.x.toDouble(), pos.y + 1.0, pos.z.toDouble(), remaining)
world!!.spawnEntity(entity)
}
// this happens outside the normal update loop because by receiving the item stack packet // this happens outside the normal update loop because by receiving the item stack packet
// we "know" how much the count in the source inventory has changed // we "know" how much the count in the source inventory has changed
updateNetItems() updateNetItems()
sync() sync()
return remaining
} }
private fun handleCapacity(packet: CapacityPacket) { private fun handleCapacity(packet: CapacityPacket) {
@ -111,9 +107,8 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL), Invento
} }
if (insertion != null) { if (insertion != null) {
insertion.results.add(packet.capacity to packet.receivingInterface) insertion.results.add(packet.capacity to packet.receivingInterface)
if (insertion.totalCapacity >= insertion.stack.count || counter - insertion.timestamp >= INSERTION_TIMEOUT || insertion.results.size >= inventoryCache.size) { if (insertion.isFinishable(counter)) {
pendingInsertions.remove(insertion.bufferSlot) finishInsertion(insertion)
completeInsertion(insertion)
} }
} }
} }
@ -146,6 +141,35 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL), Invento
} }
} }
private fun finishPendingInsertions() {
if (world!!.isClient) return
for (insertion in pendingInsertions.values) {
if (!insertion.isFinishable(counter)) continue
finishInsertion(insertion)
}
}
private fun sendEnqueuedLocateRequests() {
if (world!!.isClient) return
for (request in locateRequestQueue) {
pendingRequests.add(request)
sendToSingle(LocateStackPacket(request.stack, macAddress))
}
locateRequestQueue.clear()
}
private fun finishPendingRequests() {
if (world!!.isClient) return
for (request in pendingRequests) {
if (request.isFinishable(counter)) {
stackLocateRequestCompleted(request)
}
}
}
fun addObserver() { fun addObserver() {
observers++ observers++
} }
@ -155,12 +179,15 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL), Invento
} }
override fun tick() { override fun tick() {
if (observers > 0 && (++counter % 20) == 0) { if (observers > 0 && (++counter % 10) == 0) {
if (world!!.isClient) { if (world!!.isClient) {
println(cachedNetItems) println(cachedNetItems)
} else { } else {
updateNetItems() updateNetItems()
sendEnqueuedLocateRequests()
finishPendingRequests()
beginInsertions() beginInsertions()
finishPendingInsertions()
sync() sync()
} }
} }
@ -190,11 +217,12 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL), Invento
} }
fun requestItem(stack: ItemStack, amount: Int = stack.count) { fun requestItem(stack: ItemStack, amount: Int = stack.count) {
pendingRequests.add(StackLocateRequest(stack, amount, counter)) locateRequestQueue.add(StackLocateRequest(stack, amount, counter))
sendToSingle(LocateStackPacket(stack, macAddress))
} }
private fun stackLocateRequestCompleted(request: StackLocateRequest) { private fun stackLocateRequestCompleted(request: StackLocateRequest) {
pendingRequests.remove(request)
// todo: also sort results by interface priority // todo: also sort results by interface priority
val sortedResults = request.results.sortedByDescending { it.first }.toMutableList() val sortedResults = request.results.sortedByDescending { it.first }.toMutableList()
var amountRequested = 0 var amountRequested = 0
@ -206,7 +234,9 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL), Invento
} }
} }
private fun completeInsertion(insertion: PendingStackInsertion) { private fun finishInsertion(insertion: PendingStackInsertion) {
pendingInsertions.remove(insertion.bufferSlot)
// todo: also sort results by interface priority // todo: also sort results by interface priority
val sortedResults = insertion.results.sortedBy { it.first }.toMutableList() val sortedResults = insertion.results.sortedBy { it.first }.toMutableList()
val remaining = insertion.stack val remaining = insertion.stack
@ -279,6 +309,10 @@ data class StackLocateRequest(
) { ) {
val totalResultAmount: Int val totalResultAmount: Int
get() = results.fold(0) { acc, (amount, _) -> acc + amount } get() = results.fold(0) { acc, (amount, _) -> acc + amount }
fun isFinishable(currentTimestamp: Int): Boolean {
return totalResultAmount > amount || currentTimestamp - timestamp >= TerminalBlockEntity.LOCATE_REQUEST_TIMEOUT
}
} }
data class PendingStackInsertion( data class PendingStackInsertion(
@ -289,4 +323,8 @@ data class PendingStackInsertion(
) { ) {
val totalCapacity: Int val totalCapacity: Int
get() = results.fold(0) { acc, (amount, _) -> acc + amount } get() = results.fold(0) { acc, (amount, _) -> acc + amount }
fun isFinishable(currentTimestamp: Int): Boolean {
return totalCapacity > stack.count || currentTimestamp - timestamp >= TerminalBlockEntity.INSERTION_TIMEOUT
}
} }

View File

@ -0,0 +1,31 @@
package net.shadowfacts.phycon.network.component
import net.minecraft.block.entity.BlockEntity
import net.minecraft.entity.ItemEntity
import net.minecraft.item.ItemStack
import net.shadowfacts.phycon.api.PacketSink
import net.shadowfacts.phycon.api.PacketSource
import net.shadowfacts.phycon.network.packet.ItemStackPacket
/**
* @author shadowfacts
*/
interface ItemStackPacketHandler: PacketSink, PacketSource {
fun doHandleItemStack(packet: ItemStackPacket): ItemStack
}
fun <BE> BE.handleItemStack(packet: ItemStackPacket) where BE: BlockEntity, BE: 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)
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))
}
}

View File

@ -6,5 +6,6 @@ import net.shadowfacts.phycon.api.util.MACAddress
/** /**
* @author shadowfacts * @author shadowfacts
*/ */
class ItemStackPacket(val stack: ItemStack, source: MACAddress, destination: MACAddress): BasePacket(source, destination) { class ItemStackPacket(val stack: ItemStack, val bounceCount: Int, source: MACAddress, destination: MACAddress): BasePacket(source, destination) {
constructor(stack: ItemStack, source: MACAddress, destination: MACAddress): this(stack, 0, source, destination)
} }