Add network stack provider priorities

This commit is contained in:
Shadowfacts 2021-03-03 22:00:21 -05:00
parent 2d3ac4538d
commit 170e50755a
Signed by: shadowfacts
GPG Key ID: 94A5AB95422746E5
9 changed files with 139 additions and 18 deletions

View File

@ -10,7 +10,7 @@ class NumberField(
var number: Int? var number: Int?
get() { get() {
return if (text.isEmpty()) { return if (isTextTemporarilyAllowed(text)) {
null null
} else { } else {
try { try {
@ -31,7 +31,7 @@ class NumberField(
} }
override fun validate(proposedText: String): Boolean { override fun validate(proposedText: String): Boolean {
return proposedText.isEmpty() || try { return isTextTemporarilyAllowed(proposedText) || try {
val value = Integer.parseInt(proposedText) val value = Integer.parseInt(proposedText)
validator?.invoke(value) ?: true validator?.invoke(value) ?: true
} catch (e: NumberFormatException) { } catch (e: NumberFormatException) {
@ -39,4 +39,8 @@ class NumberField(
} }
} }
private fun isTextTemporarilyAllowed(s: String): Boolean {
return s.isEmpty() || s == "-"
}
} }

View File

@ -5,7 +5,6 @@ import alexiil.mc.lib.attributes.Simulation
import alexiil.mc.lib.attributes.item.ItemAttributes import alexiil.mc.lib.attributes.item.ItemAttributes
import alexiil.mc.lib.attributes.item.ItemInsertable import alexiil.mc.lib.attributes.item.ItemInsertable
import alexiil.mc.lib.attributes.item.ItemStackUtil import alexiil.mc.lib.attributes.item.ItemStackUtil
import net.minecraft.block.BlockState
import net.minecraft.item.ItemStack 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

View File

@ -44,6 +44,7 @@ class MinerBlockEntity: DeviceBlockEntity(PhyBlockEntities.MINER),
override val pendingInsertions = mutableListOf<PendingInsertion>() override val pendingInsertions = mutableListOf<PendingInsertion>()
override val dispatchStackTimeout = TerminalBlockEntity.INSERTION_TIMEOUT override val dispatchStackTimeout = TerminalBlockEntity.INSERTION_TIMEOUT
override val controller = ActivationController(40L, this) override val controller = ActivationController(40L, this)
override var providerPriority = 0
var minerMode = MinerMode.ON_DEMAND var minerMode = MinerMode.ON_DEMAND
@ -151,6 +152,10 @@ class MinerBlockEntity: DeviceBlockEntity(PhyBlockEntities.MINER),
return minerMode == MinerMode.AUTOMATIC return minerMode == MinerMode.AUTOMATIC
} }
override fun canConfigureProviderPriority(): Boolean {
return minerMode == MinerMode.ON_DEMAND
}
override fun toCommonTag(tag: CompoundTag) { override fun toCommonTag(tag: CompoundTag) {
super.toCommonTag(tag) super.toCommonTag(tag)
writeDeviceConfiguration(tag) writeDeviceConfiguration(tag)
@ -164,11 +169,13 @@ class MinerBlockEntity: DeviceBlockEntity(PhyBlockEntities.MINER),
override fun writeDeviceConfiguration(tag: CompoundTag) { override fun writeDeviceConfiguration(tag: CompoundTag) {
tag.putString("MinerMode", minerMode.name) tag.putString("MinerMode", minerMode.name)
tag.putString("ActivationMode", controller.activationMode.name) tag.putString("ActivationMode", controller.activationMode.name)
tag.putInt("ProviderPriority", providerPriority)
} }
override fun loadDeviceConfiguration(tag: CompoundTag) { override fun loadDeviceConfiguration(tag: CompoundTag) {
minerMode = MinerMode.valueOf(tag.getString("MinerMode")) minerMode = MinerMode.valueOf(tag.getString("MinerMode"))
controller.activationMode = ActivationMode.valueOf(tag.getString("ActivationMode")) controller.activationMode = ActivationMode.valueOf(tag.getString("ActivationMode"))
providerPriority = tag.getInt("ProviderPriority")
} }
enum class MinerMode { enum class MinerMode {

View File

@ -4,7 +4,9 @@ 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.block.BlockState
import net.minecraft.item.ItemStack import net.minecraft.item.ItemStack
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.init.PhyBlockEntities import net.shadowfacts.phycon.init.PhyBlockEntities
@ -15,6 +17,7 @@ import net.shadowfacts.phycon.component.NetworkStackProvider
import net.shadowfacts.phycon.component.NetworkStackReceiver import net.shadowfacts.phycon.component.NetworkStackReceiver
import net.shadowfacts.phycon.component.handleItemStack import net.shadowfacts.phycon.component.handleItemStack
import net.shadowfacts.phycon.packet.* import net.shadowfacts.phycon.packet.*
import net.shadowfacts.phycon.util.ClientConfigurableDevice
import kotlin.math.min import kotlin.math.min
/** /**
@ -23,11 +26,14 @@ import kotlin.math.min
class InterfaceBlockEntity: DeviceBlockEntity(PhyBlockEntities.INTERFACE), class InterfaceBlockEntity: DeviceBlockEntity(PhyBlockEntities.INTERFACE),
ItemStackPacketHandler, ItemStackPacketHandler,
NetworkStackProvider, NetworkStackProvider,
NetworkStackReceiver { NetworkStackReceiver,
ClientConfigurableDevice {
private val facing: Direction private val facing: Direction
get() = cachedState[FaceDeviceBlock.FACING] get() = cachedState[FaceDeviceBlock.FACING]
override var providerPriority = 0
// todo: should this be a weak ref? // todo: should this be a weak ref?
private var inventory: GroupedItemInv? = null private var inventory: GroupedItemInv? = null
@ -101,4 +107,22 @@ class InterfaceBlockEntity: DeviceBlockEntity(PhyBlockEntities.INTERFACE),
} }
} }
override fun toCommonTag(tag: CompoundTag) {
super.toCommonTag(tag)
writeDeviceConfiguration(tag)
}
override fun fromCommonTag(tag: CompoundTag) {
super.fromCommonTag(tag)
loadDeviceConfiguration(tag)
}
override fun writeDeviceConfiguration(tag: CompoundTag) {
tag.putInt("ProviderPriority", providerPriority)
}
override fun loadDeviceConfiguration(tag: CompoundTag) {
providerPriority = tag.getInt("ProviderPriority")
}
} }

View File

@ -5,7 +5,6 @@ import alexiil.mc.lib.attributes.item.ItemStackCollections
import alexiil.mc.lib.attributes.item.ItemStackUtil import alexiil.mc.lib.attributes.item.ItemStackUtil
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.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
@ -42,7 +41,9 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL),
NetworkStackDispatcher<TerminalBlockEntity.PendingInsertion> { NetworkStackDispatcher<TerminalBlockEntity.PendingInsertion> {
companion object { companion object {
val LOCATE_REQUEST_TIMEOUT: Long = 40 // ticks // the locate request timeout is only 1 tick because that's long enough to hear from every device on the network
// in a degraded state (when there's latency in the network), not handling interface priorities correctly is acceptable
val LOCATE_REQUEST_TIMEOUT: Long = 1 // ticks
val INSERTION_TIMEOUT: Long = 40 val INSERTION_TIMEOUT: Long = 40
} }
@ -163,14 +164,15 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL),
override fun tick() { override fun tick() {
super.tick() super.tick()
if (counter % 20 == 0L) {
if (!world!!.isClient) { if (!world!!.isClient) {
finishPendingRequests() finishPendingRequests()
beginInsertions()
finishTimedOutPendingInsertions() finishTimedOutPendingInsertions()
} }
if (observers > 0 && !world!!.isClient) { if (counter % 20 == 0L && !world!!.isClient) {
beginInsertions()
if (observers > 0) {
updateAndSync() updateAndSync()
} }
} }
@ -209,8 +211,15 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL),
private fun stackLocateRequestCompleted(request: StackLocateRequest) { private fun stackLocateRequestCompleted(request: StackLocateRequest) {
pendingRequests.remove(request) pendingRequests.remove(request)
// todo: also sort results by interface priority val sortedResults = request.results.toMutableList()
val sortedResults = request.results.sortedByDescending { it.first }.toMutableList() sortedResults.sortWith { a, b ->
// sort results first by provider priority, and then by the count that it can provide
if (a.second.providerPriority == b.second.providerPriority) {
b.first - a.first
} else {
b.second.providerPriority - a.second.providerPriority
}
}
var amountRequested = 0 var amountRequested = 0
while (amountRequested < request.amount && sortedResults.isNotEmpty()) { while (amountRequested < request.amount && sortedResults.isNotEmpty()) {
val (sourceAmount, sourceInterface) = sortedResults.removeAt(0) val (sourceAmount, sourceInterface) = sortedResults.removeAt(0)
@ -268,6 +277,8 @@ data class StackLocateRequest(
get() = results.fold(0) { acc, (amount, _) -> acc + amount } get() = results.fold(0) { acc, (amount, _) -> acc + amount }
fun isFinishable(currentTimestamp: Long): Boolean { fun isFinishable(currentTimestamp: Long): Boolean {
return totalResultAmount >= amount || currentTimestamp - timestamp >= TerminalBlockEntity.LOCATE_REQUEST_TIMEOUT // we can't check totalResultAmount >= amount because we need to hear back from all network stack providers to
// correctly sort by priority
return currentTimestamp - timestamp >= TerminalBlockEntity.LOCATE_REQUEST_TIMEOUT
} }
} }

View File

@ -1,9 +1,17 @@
package net.shadowfacts.phycon.component package net.shadowfacts.phycon.component
import net.shadowfacts.phycon.api.NetworkDevice import net.shadowfacts.phycon.api.NetworkDevice
import net.shadowfacts.phycon.util.ClientConfigurableDevice
/** /**
* @author shadowfacts * @author shadowfacts
*/ */
interface NetworkStackProvider: NetworkDevice { interface NetworkStackProvider: NetworkDevice, ClientConfigurableDevice {
var providerPriority: Int
fun canConfigureProviderPriority(): Boolean {
return true
}
} }

View File

@ -16,6 +16,7 @@ import net.shadowfacts.phycon.block.DeviceBlockEntity
import net.shadowfacts.phycon.block.miner.MinerBlockEntity import net.shadowfacts.phycon.block.miner.MinerBlockEntity
import net.shadowfacts.phycon.block.redstone_controller.RedstoneControllerBlockEntity import net.shadowfacts.phycon.block.redstone_controller.RedstoneControllerBlockEntity
import net.shadowfacts.phycon.component.ActivationController import net.shadowfacts.phycon.component.ActivationController
import net.shadowfacts.phycon.component.NetworkStackProvider
import org.lwjgl.glfw.GLFW import org.lwjgl.glfw.GLFW
/** /**
@ -63,7 +64,14 @@ class DeviceConsoleScreen(
MinerViewController(device) MinerViewController(device)
)) ))
} }
if (device is NetworkStackProvider) {
tabs.add(TabViewController.SimpleTab(
Label("P").apply { textColor = Color.TEXT },
TranslatableText("gui.phycon.console.provider"),
ProviderViewController(device),
device::canConfigureProviderPriority
))
}
tabController = TabViewController(tabs) tabController = TabViewController(tabs)

View File

@ -0,0 +1,57 @@
package net.shadowfacts.phycon.screen.console
import net.minecraft.block.entity.BlockEntity
import net.minecraft.client.MinecraftClient
import net.minecraft.text.TranslatableText
import net.shadowfacts.cacao.util.Color
import net.shadowfacts.cacao.view.Label
import net.shadowfacts.cacao.view.textfield.NumberField
import net.shadowfacts.cacao.viewcontroller.ViewController
import net.shadowfacts.kiwidsl.dsl
import net.shadowfacts.phycon.component.NetworkStackProvider
import net.shadowfacts.phycon.networking.C2SConfigureDevice
/**
* @author shadowfacts
*/
class ProviderViewController<T>(
private val device: T
): ViewController() where T: BlockEntity, T: NetworkStackProvider {
override fun viewDidLoad() {
super.viewDidLoad()
val label = Label(TranslatableText("gui.phycon.console.provider.priority")).apply {
textColor = Color.TEXT
}
view.addSubview(label)
val field = NumberField(device.providerPriority) {
if (it.number != null) {
device.providerPriority = it.number!!
MinecraftClient.getInstance().player!!.networkHandler.sendPacket(C2SConfigureDevice(device))
}
}
view.addSubview(field)
val desc = Label(TranslatableText("gui.phycon.console.provider.priority_desc")).apply {
textColor = Color.TEXT
}
view.addSubview(desc)
view.solver.dsl {
field.widthAnchor equalTo 100
field.heightAnchor equalTo 20
field.rightAnchor equalTo view.rightAnchor
field.topAnchor equalTo view.topAnchor
label.centerYAnchor equalTo field.centerYAnchor
label.rightAnchor equalTo (field.leftAnchor - 4)
desc.topAnchor equalTo (field.bottomAnchor + 4)
desc.leftAnchor equalTo view.leftAnchor
desc.rightAnchor equalTo view.rightAnchor
}
}
}

View File

@ -21,6 +21,9 @@
"gui.phycon.console.remote": "Remote Management", "gui.phycon.console.remote": "Remote Management",
"gui.phycon.console.remote.mode": "Activation Mode", "gui.phycon.console.remote.mode": "Activation Mode",
"gui.phycon.console.miner.mode": "Miner Mode", "gui.phycon.console.miner.mode": "Miner Mode",
"gui.phycon.console.provider": "Item Provider",
"gui.phycon.console.provider.priority": "Provider Priority",
"gui.phycon.console.provider.priority_desc": "When a device requests items from the network, it send requests to providers (e.g., interfaces) with higher priorities first. Priorities can be negative.",
"gui.phycon.redstone_mode.high": "High", "gui.phycon.redstone_mode.high": "High",
"gui.phycon.redstone_mode.low": "Low", "gui.phycon.redstone_mode.low": "Low",
"gui.phycon.redstone_mode.toggle": "Toggle", "gui.phycon.redstone_mode.toggle": "Toggle",