Compare commits

..

3 Commits

Author SHA1 Message Date
Shadowfacts 4c5b7daf9e
Use an actual logger 2021-02-26 17:04:18 -05:00
Shadowfacts 1f078c671f
Add EnumButton 2021-02-24 22:47:07 -05:00
Shadowfacts 9252a35dae
Convert extractor to use NetworkStackDispatcher 2021-02-24 22:09:24 -05:00
13 changed files with 140 additions and 43 deletions

8
PhyConDebugLogging.xml Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
<Loggers>
<Logger name="PhyNet">
<AppenderRef ref="SysOut" level="debug"/>
</Logger>
</Loggers>
</Configuration>

View File

@ -1,5 +1,5 @@
plugins { plugins {
id "fabric-loom" version "0.5-SNAPSHOT" id "fabric-loom" version "0.6.49"
id "maven-publish" id "maven-publish"
id "org.jetbrains.kotlin.jvm" version "1.4.30" id "org.jetbrains.kotlin.jvm" version "1.4.30"
} }
@ -12,6 +12,7 @@ version = project.mod_version
group = project.maven_group group = project.maven_group
minecraft { minecraft {
log4jConfigs.from "PhyConDebugLogging.xml"
} }
repositories { repositories {

View File

@ -7,6 +7,7 @@ import net.shadowfacts.phycon.init.PhyBlocks
import net.shadowfacts.phycon.init.PhyItems import net.shadowfacts.phycon.init.PhyItems
import net.shadowfacts.phycon.init.PhyScreens import net.shadowfacts.phycon.init.PhyScreens
import net.shadowfacts.phycon.networking.* import net.shadowfacts.phycon.networking.*
import org.apache.logging.log4j.LogManager
/** /**
* @author shadowfacts * @author shadowfacts
@ -15,6 +16,8 @@ object PhysicalConnectivity: ModInitializer {
val MODID = "phycon" val MODID = "phycon"
val NETWORK_LOGGER = LogManager.getLogger("PhyNet")
override fun onInitialize() { override fun onInitialize() {
PhyBlocks.init() PhyBlocks.init()
PhyBlockEntities.init() PhyBlockEntities.init()

View File

@ -6,6 +6,7 @@ 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.minecraft.util.Tickable
import net.shadowfacts.phycon.PhysicalConnectivity
import net.shadowfacts.phycon.api.PacketSink import net.shadowfacts.phycon.api.PacketSink
import net.shadowfacts.phycon.api.PacketSource import net.shadowfacts.phycon.api.PacketSource
import net.shadowfacts.phycon.api.Interface import net.shadowfacts.phycon.api.Interface
@ -52,6 +53,7 @@ abstract class DeviceBlockEntity(type: BlockEntityType<*>): BlockEntity(type),
abstract override fun handle(packet: Packet) abstract override fun handle(packet: Packet)
private fun doHandlePacket(packet: Packet) { private fun doHandlePacket(packet: Packet) {
PhysicalConnectivity.NETWORK_LOGGER.debug("{} ({}) received packet: {}", this, ipAddress, packet)
when (packet) { when (packet) {
is DeviceRemovedPacket -> { is DeviceRemovedPacket -> {
arpTable.remove(packet.source) arpTable.remove(packet.source)
@ -65,13 +67,12 @@ abstract class DeviceBlockEntity(type: BlockEntityType<*>): BlockEntity(type),
} }
override fun receive(frame: EthernetFrame) { override fun receive(frame: EthernetFrame) {
println("$this ($ipAddress, ${macAddress}) received frame from ${frame.source}: $frame") PhysicalConnectivity.NETWORK_LOGGER.debug("{} ({}, {}) received frame from {}: {}", this, ipAddress, macAddress, frame.source, frame)
when (frame) { when (frame) {
is ARPQueryFrame -> handleARPQuery(frame) is ARPQueryFrame -> handleARPQuery(frame)
is ARPResponseFrame -> handleARPResponse(frame) is ARPResponseFrame -> handleARPResponse(frame)
is PacketFrame -> { is PacketFrame -> {
if (frame.packet.destination.isBroadcast || frame.packet.destination == ipAddress) { if (frame.packet.destination.isBroadcast || frame.packet.destination == ipAddress) {
println("$this ($ipAddress) received packet: ${frame.packet}")
doHandlePacket(frame.packet) doHandlePacket(frame.packet)
} }
} }
@ -79,17 +80,17 @@ abstract class DeviceBlockEntity(type: BlockEntityType<*>): BlockEntity(type),
} }
private fun handleARPQuery(frame: ARPQueryFrame) { private fun handleARPQuery(frame: ARPQueryFrame) {
println("$this ($ipAddress) received ARP query for ${frame.queryIP}") PhysicalConnectivity.NETWORK_LOGGER.debug("{}, ({}), received ARP query for {}", this, ipAddress, frame.queryIP)
arpTable[frame.sourceIP] = frame.source arpTable[frame.sourceIP] = frame.source
if (frame.queryIP == ipAddress) { if (frame.queryIP == ipAddress) {
println("$this ($ipAddress) sending ARP response to ${frame.source} with $macAddress") PhysicalConnectivity.NETWORK_LOGGER.debug("{} ({}) sending ARP response to {} with {}", this, ipAddress, frame.sourceIP, macAddress)
send(ARPResponseFrame(ipAddress, macAddress, frame.source)) send(ARPResponseFrame(ipAddress, macAddress, frame.source))
} }
} }
private fun handleARPResponse(frame: ARPResponseFrame) { private fun handleARPResponse(frame: ARPResponseFrame) {
arpTable[frame.query] = frame.source arpTable[frame.query] = frame.source
println("$this ($ipAddress) received ARP response for ${frame.query} with ${frame.source}") PhysicalConnectivity.NETWORK_LOGGER.debug("{}, ({}) received ARP response for {} with {}", this, ipAddress, frame.query, frame.source)
val toRemove = packetQueue.filter { (packet, _) -> val toRemove = packetQueue.filter { (packet, _) ->
if (packet.destination == frame.query) { if (packet.destination == frame.query) {
@ -110,7 +111,7 @@ abstract class DeviceBlockEntity(type: BlockEntityType<*>): BlockEntity(type),
} else { } else {
packetQueue.add(PendingPacket(packet, counter)) packetQueue.add(PendingPacket(packet, counter))
println("$this ($ipAddress) sending ARP query for ${packet.destination}") PhysicalConnectivity.NETWORK_LOGGER.debug("{} ({}) sending ARP query for {}", this, ipAddress, packet.destination)
send(ARPQueryFrame(packet.destination, ipAddress, macAddress)) send(ARPQueryFrame(packet.destination, ipAddress, macAddress))
} }
} }

View File

@ -1,26 +1,31 @@
package net.shadowfacts.phycon.network.block.extractor package net.shadowfacts.phycon.network.block.extractor
import alexiil.mc.lib.attributes.SearchOptions import alexiil.mc.lib.attributes.SearchOptions
import alexiil.mc.lib.attributes.item.GroupedItemInv import alexiil.mc.lib.attributes.Simulation
import alexiil.mc.lib.attributes.item.FixedItemInv
import alexiil.mc.lib.attributes.item.ItemAttributes import alexiil.mc.lib.attributes.item.ItemAttributes
import alexiil.mc.lib.attributes.item.filter.ExactItemStackFilter
import net.minecraft.block.BlockState import net.minecraft.block.BlockState
import net.minecraft.item.ItemStack
import net.minecraft.nbt.CompoundTag import net.minecraft.nbt.CompoundTag
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.api.util.IPAddress
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.ActivationController import net.shadowfacts.phycon.network.component.ActivationController
import net.shadowfacts.phycon.network.component.NetworkStackDispatcher
import net.shadowfacts.phycon.network.component.handleItemStack
import net.shadowfacts.phycon.network.packet.CapacityPacket import net.shadowfacts.phycon.network.packet.CapacityPacket
import net.shadowfacts.phycon.network.packet.CheckCapacityPacket
import net.shadowfacts.phycon.network.packet.ItemStackPacket import net.shadowfacts.phycon.network.packet.ItemStackPacket
import net.shadowfacts.phycon.network.packet.RemoteActivationPacket import net.shadowfacts.phycon.network.packet.RemoteActivationPacket
import net.shadowfacts.phycon.util.ActivationMode import net.shadowfacts.phycon.util.ActivationMode
import kotlin.properties.Delegates
/** /**
* @author shadowfacts * @author shadowfacts
*/ */
class ExtractorBlockEntity: DeviceBlockEntity(PhyBlockEntities.EXTRACTOR), class ExtractorBlockEntity: DeviceBlockEntity(PhyBlockEntities.EXTRACTOR),
NetworkStackDispatcher<ExtractorBlockEntity.PendingInsertion>,
ActivationController.ActivatableDevice { ActivationController.ActivatableDevice {
companion object { companion object {
@ -30,17 +35,18 @@ class ExtractorBlockEntity: DeviceBlockEntity(PhyBlockEntities.EXTRACTOR),
private val facing: Direction private val facing: Direction
get() = cachedState[ExtractorBlock.FACING] get() = cachedState[ExtractorBlock.FACING]
private var inventory: GroupedItemInv? = null private var inventory: FixedItemInv? = null
private var shouldExtract = false override val pendingInsertions = mutableListOf<PendingInsertion>()
override val dispatchStackTimeout = 40L
override val controller = ActivationController(SLEEP_TIME, this) override val controller = ActivationController(SLEEP_TIME, this)
fun updateInventory() { fun updateInventory() {
val offsetPos = pos.offset(facing) val offsetPos = pos.offset(facing)
val option = SearchOptions.inDirection(facing) val option = SearchOptions.inDirection(facing)
inventory = ItemAttributes.GROUPED_INV.getFirstOrNull(world, offsetPos, option) inventory = ItemAttributes.FIXED_INV.getFirstOrNull(world, offsetPos, option)
} }
private fun getInventory(): GroupedItemInv? { private fun getInventory(): FixedItemInv? {
if (inventory == null) updateInventory() if (inventory == null) updateInventory()
return inventory return inventory
} }
@ -48,18 +54,27 @@ class ExtractorBlockEntity: DeviceBlockEntity(PhyBlockEntities.EXTRACTOR),
override fun handle(packet: Packet) { override fun handle(packet: Packet) {
when (packet) { when (packet) {
is CapacityPacket -> handleCapacity(packet) is CapacityPacket -> handleCapacity(packet)
is ItemStackPacket -> handleItemStack(packet)
is RemoteActivationPacket -> controller.handleRemoteActivation(packet) is RemoteActivationPacket -> controller.handleRemoteActivation(packet)
} }
} }
private fun handleCapacity(packet: CapacityPacket) { override fun doHandleItemStack(packet: ItemStackPacket): ItemStack {
if (shouldExtract && packet.capacity > 0) { // we can't insert things back into the inventory, so just let them spawn
getInventory()?.also { inv -> return packet.stack
shouldExtract = false
val extracted = inv.extract(packet.stack, packet.capacity)
sendPacket(ItemStackPacket(extracted, ipAddress, packet.stackReceiver.ipAddress))
}
} }
override fun createPendingInsertion(stack: ItemStack) = PendingInsertion(stack, counter)
override fun finishInsertion(insertion: PendingInsertion): ItemStack {
val inventory = getInventory() ?: return insertion.stack
// if the inventory has changed, the old slot index is meaningless
if (inventory !== insertion.inventory) return insertion.stack
val extracted = inventory.extractStack(insertion.inventorySlot, ExactItemStackFilter(insertion.stack), ItemStack.EMPTY, insertion.totalCapacity, Simulation.ACTION)
if (extracted.isEmpty) return insertion.stack
// if we extracted less than expected, make sure super.finishInsertion doesn't send more than we actually have
insertion.stack = extracted
return super.finishInsertion(insertion)
} }
override fun tick() { override fun tick() {
@ -72,11 +87,17 @@ class ExtractorBlockEntity: DeviceBlockEntity(PhyBlockEntities.EXTRACTOR),
override fun activate(): Boolean { override fun activate(): Boolean {
val inventory = getInventory() ?: return false val inventory = getInventory() ?: return false
val stack = inventory.storedStacks.firstOrNull() ?: return false for (slot in 0 until inventory.slotCount) {
shouldExtract = true val slotStack = inventory.getInvStack(slot)
sendPacket(CheckCapacityPacket(stack, ipAddress, IPAddress.BROADCAST)) if (slotStack.isEmpty) continue
dispatchItemStack(slotStack) { insertion ->
insertion.inventory = inventory
insertion.inventorySlot = slot
}
return true return true
} }
return false
}
override fun toTag(tag: CompoundTag): CompoundTag { override fun toTag(tag: CompoundTag): CompoundTag {
tag.putString("ActivationMode", controller.activationMode.name) tag.putString("ActivationMode", controller.activationMode.name)
@ -97,4 +118,9 @@ class ExtractorBlockEntity: DeviceBlockEntity(PhyBlockEntities.EXTRACTOR),
super.fromClientTag(tag) super.fromClientTag(tag)
controller.activationMode = ActivationMode.valueOf(tag.getString("ActivationMode")) controller.activationMode = ActivationMode.valueOf(tag.getString("ActivationMode"))
} }
class PendingInsertion(stack: ItemStack, timestamp: Long): NetworkStackDispatcher.PendingInsertion<PendingInsertion>(stack, timestamp) {
lateinit var inventory: FixedItemInv
var inventorySlot by Delegates.notNull<Int>()
}
} }

View File

@ -7,6 +7,7 @@ import net.minecraft.entity.ItemEntity
import net.minecraft.nbt.CompoundTag import net.minecraft.nbt.CompoundTag
import net.minecraft.util.Tickable import net.minecraft.util.Tickable
import net.minecraft.util.math.Direction import net.minecraft.util.math.Direction
import net.shadowfacts.phycon.PhysicalConnectivity
import net.shadowfacts.phycon.api.Interface import net.shadowfacts.phycon.api.Interface
import net.shadowfacts.phycon.api.frame.EthernetFrame import net.shadowfacts.phycon.api.frame.EthernetFrame
import net.shadowfacts.phycon.api.frame.PacketFrame import net.shadowfacts.phycon.api.frame.PacketFrame
@ -53,10 +54,10 @@ class SwitchBlockEntity: BlockEntity(PhyBlockEntities.SWITCH),
if (frame.destination.type != MACAddress.Type.BROADCAST && macTable.containsKey(frame.destination)) { if (frame.destination.type != MACAddress.Type.BROADCAST && macTable.containsKey(frame.destination)) {
val dir = macTable[frame.destination]!! val dir = macTable[frame.destination]!!
println("$this (${fromItf.side}, ${fromItf.macAddress}) forwarding $frame to side $dir") PhysicalConnectivity.NETWORK_LOGGER.debug("{} ({}, {}) forwarding {} to side {}", this, fromItf.side, fromItf.macAddress, frame, dir)
interfaceForSide(dir).send(frame) interfaceForSide(dir).send(frame)
} else { } else {
println("$this (${fromItf.side}, ${fromItf.macAddress}) flooding $frame") PhysicalConnectivity.NETWORK_LOGGER.debug("{} ({}, {}) flooding {}", this, fromItf.side, fromItf.macAddress, frame)
flood(frame, fromItf) flood(frame, fromItf)
} }
} }

View File

@ -33,7 +33,8 @@ interface NetworkStackDispatcher<Insertion: NetworkStackDispatcher.PendingInsert
if (insertion != null) { if (insertion != null) {
insertion.results.add(packet.capacity to packet.stackReceiver) insertion.results.add(packet.capacity to packet.stackReceiver)
if (insertion.isFinishable(this)) { if (insertion.isFinishable(this)) {
finishInsertion(insertion) val remaining = finishInsertion(insertion)
// todo: do something with remaining
} }
} }
} }
@ -43,7 +44,8 @@ interface NetworkStackDispatcher<Insertion: NetworkStackDispatcher.PendingInsert
// 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 // 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()) { while (!remaining.isEmpty && sortedResults.isNotEmpty()) {
val (capacity, receivingInterface) = sortedResults.removeFirst() val (capacity, receivingInterface) = sortedResults.removeFirst()
if (capacity <= 0) continue if (capacity <= 0) continue
@ -56,7 +58,7 @@ interface NetworkStackDispatcher<Insertion: NetworkStackDispatcher.PendingInsert
} }
open class PendingInsertion<Self: PendingInsertion<Self>>( open class PendingInsertion<Self: PendingInsertion<Self>>(
val stack: ItemStack, var stack: ItemStack,
val timestamp: Long val timestamp: Long
) { ) {
val results = mutableSetOf<Pair<Int, NetworkStackReceiver>>() val results = mutableSetOf<Pair<Int, NetworkStackReceiver>>()
@ -77,5 +79,6 @@ fun <Self, Insertion: NetworkStackDispatcher.PendingInsertion<Insertion>> Self.f
val finishable = pendingInsertions.filter { it.isFinishable(this) } val finishable = pendingInsertions.filter { it.isFinishable(this) }
// finishInsertion removes the object from pendingInsertions // finishInsertion removes the object from pendingInsertions
finishable.forEach(::finishInsertion) 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?) // todo: if a timed-out insertion can't be finished, we should probably retry after some time (exponential backoff?)
} }

View File

@ -7,6 +7,7 @@ import net.minecraft.client.util.math.MatrixStack
import net.shadowfacts.phycon.network.DeviceBlockEntity import net.shadowfacts.phycon.network.DeviceBlockEntity
import net.shadowfacts.phycon.network.component.ActivationController import net.shadowfacts.phycon.network.component.ActivationController
import net.shadowfacts.phycon.networking.C2SConfigureActivationMode import net.shadowfacts.phycon.networking.C2SConfigureActivationMode
import net.shadowfacts.phycon.util.ActivationMode
import net.shadowfacts.phycon.util.next import net.shadowfacts.phycon.util.next
import org.lwjgl.glfw.GLFW import org.lwjgl.glfw.GLFW
@ -28,10 +29,7 @@ class ActivatableDeviceConsoleScreen<T>(
val minX = (width - backgroundWidth) / 2 val minX = (width - backgroundWidth) / 2
val minY = (height - backgroundHeight) / 2 val minY = (height - backgroundHeight) / 2
lateinit var mode: ButtonWidget val mode = EnumButton(device.controller::activationMode, minX + 5, minY + 25, 55, 20) {
mode = ButtonWidget(minX + 5, minY + 25, 55, 20, device.controller.activationMode.friendlyName) {
device.controller.activationMode = device.controller.activationMode.next
mode.message = device.controller.activationMode.friendlyName
client!!.player!!.networkHandler.sendPacket(C2SConfigureActivationMode(device)) client!!.player!!.networkHandler.sendPacket(C2SConfigureActivationMode(device))
} }
addButton(mode) addButton(mode)

View File

@ -0,0 +1,49 @@
package net.shadowfacts.phycon.screen
import net.minecraft.client.gui.widget.AbstractPressableButtonWidget
import net.minecraft.text.Text
import net.shadowfacts.phycon.util.FriendlyNameable
import net.shadowfacts.phycon.util.RotatableEnum
import net.shadowfacts.phycon.util.next
import net.shadowfacts.phycon.util.prev
import kotlin.reflect.KMutableProperty
/**
* @author shadowfacts
*/
class EnumButton<E>(
val prop: KMutableProperty<E>,
x: Int,
y: Int,
width: Int,
height: Int,
val onChange: () -> Unit
): AbstractPressableButtonWidget(
x,
y,
width,
height,
prop.getter.call().friendlyName
) where E: Enum<E>, E: RotatableEnum, E: FriendlyNameable {
private var currentButton: Int? = null
override fun mouseClicked(d: Double, e: Double, button: Int): Boolean {
currentButton = button
val res = super.mouseClicked(d, e, button)
currentButton = null
return res
}
override fun isValidClickButton(i: Int): Boolean {
return i == 0 || i == 1
}
override fun onPress() {
val newVal = if ((currentButton ?: 0) == 0) prop.getter.call().next else prop.getter.call().prev
prop.setter.call(newVal)
message = newVal.friendlyName
onChange()
}
}

View File

@ -33,10 +33,7 @@ class RedstoneControllerConsoleScreen(
val minX = (width - backgroundWidth) / 2 val minX = (width - backgroundWidth) / 2
val minY = (height - backgroundHeight) / 2 val minY = (height - backgroundHeight) / 2
lateinit var mode: ButtonWidget val mode = EnumButton(device::redstoneMode, minX + 5, minY + 25, 75, 20) {
mode = ButtonWidget(minX + 5, minY + 25, 75, 20, device.redstoneMode.friendlyName) {
device.redstoneMode = device.redstoneMode.next
mode.message = device.redstoneMode.friendlyName
client!!.player!!.networkHandler.sendPacket(C2SConfigureRedstoneController(device)) client!!.player!!.networkHandler.sendPacket(C2SConfigureRedstoneController(device))
} }
addButton(mode) addButton(mode)

View File

@ -6,10 +6,10 @@ import net.minecraft.text.TranslatableText
/** /**
* @author shadowfacts * @author shadowfacts
*/ */
enum class ActivationMode: RotatableEnum { enum class ActivationMode: RotatableEnum, FriendlyNameable {
AUTOMATIC, AUTOMATIC,
MANAGED; MANAGED;
val friendlyName: Text override val friendlyName: Text
get() = TranslatableText("gui.phycon.activation_mode.${name.toLowerCase()}") get() = TranslatableText("gui.phycon.activation_mode.${name.toLowerCase()}")
} }

View File

@ -0,0 +1,10 @@
package net.shadowfacts.phycon.util
import net.minecraft.text.Text
/**
* @author shadowfacts
*/
interface FriendlyNameable {
val friendlyName: Text
}

View File

@ -6,7 +6,7 @@ import net.minecraft.text.TranslatableText
/** /**
* @author shadowfacts * @author shadowfacts
*/ */
enum class RedstoneMode: RotatableEnum { enum class RedstoneMode: RotatableEnum, FriendlyNameable {
HIGH, HIGH,
LOW, LOW,
TOGGLE, TOGGLE,
@ -25,6 +25,6 @@ enum class RedstoneMode: RotatableEnum {
else -> false else -> false
} }
val friendlyName: Text override val friendlyName: Text
get() = TranslatableText("gui.phycon.redstone_mode.${name.toLowerCase()}") get() = TranslatableText("gui.phycon.redstone_mode.${name.toLowerCase()}")
} }