WIP network switch statistics

This commit is contained in:
Shadowfacts 2024-07-14 10:42:23 -07:00
parent b64a05e0ad
commit 4effe2f1b4
9 changed files with 335 additions and 4 deletions

View File

@ -55,6 +55,10 @@ object RenderHelper: DrawableHelper() {
if (disabled) return if (disabled) return
RenderSystem.lineWidth(width) RenderSystem.lineWidth(width)
RenderSystem.enableBlend()
RenderSystem.disableTexture()
RenderSystem.defaultBlendFunc()
RenderSystem.setShader(GameRenderer::getPositionColorShader)
val tessellator = Tessellator.getInstance() val tessellator = Tessellator.getInstance()
val buffer = tessellator.buffer val buffer = tessellator.buffer
buffer.begin(VertexFormat.DrawMode.LINES, VertexFormats.POSITION_COLOR) buffer.begin(VertexFormat.DrawMode.LINES, VertexFormats.POSITION_COLOR)

View File

@ -7,6 +7,7 @@ import net.fabricmc.fabric.api.client.screenhandler.v1.ScreenRegistry
import net.fabricmc.fabric.api.renderer.v1.RendererAccess import net.fabricmc.fabric.api.renderer.v1.RendererAccess
import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial
import net.shadowfacts.phycon.block.inserter.InserterScreen import net.shadowfacts.phycon.block.inserter.InserterScreen
import net.shadowfacts.phycon.block.netswitch.SwitchConsoleScreen
import net.shadowfacts.phycon.block.redstone_emitter.RedstoneEmitterScreen import net.shadowfacts.phycon.block.redstone_emitter.RedstoneEmitterScreen
import net.shadowfacts.phycon.block.terminal.CraftingTerminalScreen import net.shadowfacts.phycon.block.terminal.CraftingTerminalScreen
import net.shadowfacts.phycon.init.PhyScreens import net.shadowfacts.phycon.init.PhyScreens
@ -44,6 +45,7 @@ object PhysicalConnectivityClient: ClientModInitializer {
ScreenRegistry.register(PhyScreens.CRAFTING_TERMINAL, ::CraftingTerminalScreen) ScreenRegistry.register(PhyScreens.CRAFTING_TERMINAL, ::CraftingTerminalScreen)
ScreenRegistry.register(PhyScreens.INSERTER, ::InserterScreen) ScreenRegistry.register(PhyScreens.INSERTER, ::InserterScreen)
ScreenRegistry.register(PhyScreens.REDSTONE_EMITTER, ::RedstoneEmitterScreen) ScreenRegistry.register(PhyScreens.REDSTONE_EMITTER, ::RedstoneEmitterScreen)
ScreenRegistry.register(PhyScreens.SWITCH_CONSOLE, ::SwitchConsoleScreen)
registerGlobalReceiver(S2CTerminalUpdateDisplayedItems) registerGlobalReceiver(S2CTerminalUpdateDisplayedItems)
} }

View File

@ -20,6 +20,7 @@ import net.shadowfacts.phycon.frame.BasePacketFrame
import net.shadowfacts.phycon.frame.NetworkSplitFrame import net.shadowfacts.phycon.frame.NetworkSplitFrame
import net.shadowfacts.phycon.init.PhyBlockEntities import net.shadowfacts.phycon.init.PhyBlockEntities
import net.shadowfacts.phycon.packet.ItemStackPacket import net.shadowfacts.phycon.packet.ItemStackPacket
import net.shadowfacts.phycon.util.IntRingBuffer
import net.shadowfacts.phycon.util.NetworkUtil import net.shadowfacts.phycon.util.NetworkUtil
import java.lang.ref.WeakReference import java.lang.ref.WeakReference
import java.util.Deque import java.util.Deque
@ -38,9 +39,13 @@ class SwitchBlockEntity(pos: BlockPos, state: BlockState): BlockEntity(PhyBlockE
private val macTable = mutableMapOf<MACAddress, Direction>() private val macTable = mutableMapOf<MACAddress, Direction>()
private val destinationCache = Array<WeakReference<Interface>?>(6) { null } private val destinationCache = Array<WeakReference<Interface>?>(6) { null }
val packetStatistics = IntRingBuffer(60) // 1 minute's worth
private val currentSecondPacketStatistics = IntRingBuffer(20)
private var packetsHandledThisTick = 0 private var packetsHandledThisTick = 0
private var delayedPackets: Deque<Pair<PacketFrame, SwitchInterface>> = LinkedList() private var delayedPackets: Deque<Pair<PacketFrame, SwitchInterface>> = LinkedList()
var statisticsObserver: (() -> Unit)? = null
fun interfaceForSide(side: Direction): SwitchInterface { fun interfaceForSide(side: Direction): SwitchInterface {
return interfaces.find { it.side == side }!! return interfaces.find { it.side == side }!!
} }
@ -98,6 +103,16 @@ class SwitchBlockEntity(pos: BlockPos, state: BlockState): BlockEntity(PhyBlockE
} }
fun tick() { fun tick() {
if (statisticsObserver != null) {
if (currentSecondPacketStatistics.size == 20) {
packetStatistics.add(currentSecondPacketStatistics.sum())
currentSecondPacketStatistics.clear()
statisticsObserver?.invoke()
} else {
currentSecondPacketStatistics.add(packetsHandledThisTick)
}
}
packetsHandledThisTick = 0 packetsHandledThisTick = 0
while (delayedPackets.isNotEmpty() && packetsHandledThisTick <= SWITCHING_CAPACITY) { while (delayedPackets.isNotEmpty() && packetsHandledThisTick <= SWITCHING_CAPACITY) {
@ -147,6 +162,11 @@ class SwitchBlockEntity(pos: BlockPos, state: BlockState): BlockEntity(PhyBlockE
delayedPackets.addLast(frame to fromItf) delayedPackets.addLast(frame to fromItf)
} }
} }
tag.getIntArray("PacketStatistics")?.also { statistics ->
if (statistics.isNotEmpty()) {
packetStatistics.replace(statistics)
}
}
} }
override fun toUpdatePacket(): Packet<ClientPlayPacketListener>? { override fun toUpdatePacket(): Packet<ClientPlayPacketListener>? {
@ -156,6 +176,7 @@ class SwitchBlockEntity(pos: BlockPos, state: BlockState): BlockEntity(PhyBlockE
override fun toInitialChunkDataNbt(): NbtCompound { override fun toInitialChunkDataNbt(): NbtCompound {
val tag = NbtCompound() val tag = NbtCompound()
tag.putLongArray("InterfaceAddresses", interfaces.map { it.macAddress.address }) tag.putLongArray("InterfaceAddresses", interfaces.map { it.macAddress.address })
tag.putIntArray("PacketStatistics", packetStatistics.asContiguousArray())
return tag return tag
} }

View File

@ -0,0 +1,94 @@
package net.shadowfacts.phycon.block.netswitch
import net.minecraft.client.util.math.MatrixStack
import net.minecraft.entity.player.PlayerInventory
import net.minecraft.text.LiteralText
import net.minecraft.text.Text
import net.shadowfacts.cacao.CacaoHandledScreen
import net.shadowfacts.cacao.geometry.Point
import net.shadowfacts.cacao.geometry.Rect
import net.shadowfacts.cacao.geometry.Size
import net.shadowfacts.cacao.util.Color
import net.shadowfacts.cacao.util.RenderHelper
import net.shadowfacts.cacao.view.Label
import net.shadowfacts.cacao.view.View
import net.shadowfacts.cacao.viewcontroller.ViewController
import net.shadowfacts.cacao.window.ScreenHandlerWindow
import net.shadowfacts.kiwidsl.dsl
import org.lwjgl.glfw.GLFW
/**
* @author shadowfacts
*/
class SwitchConsoleScreen(
handler: SwitchConsoleScreenHandler,
playerInventory: PlayerInventory,
title: Text,
): CacaoHandledScreen<SwitchConsoleScreenHandler>(
handler,
playerInventory,
title,
) {
val root = SwitchConsoleViewController(handler.switch)
init {
addWindow(ScreenHandlerWindow(handler, root))
}
override fun shouldPause() = false
override fun keyPressed(keyCode: Int, scanCode: Int, modifiers: Int): Boolean {
if (keyCode == GLFW.GLFW_KEY_E) {
close()
return true
}
return super.keyPressed(keyCode, scanCode, modifiers)
}
}
class SwitchConsoleViewController(val switch: SwitchBlockEntity): ViewController() {
override fun viewDidLoad() {
super.viewDidLoad()
val stats = SwitchPacketStatisticsView(switch)
view.addSubview(stats)
view.solver.dsl {
stats.centerXAnchor equalTo (view.centerXAnchor + 50)
stats.centerYAnchor equalTo (view.centerYAnchor + 50)
}
}
}
class SwitchPacketStatisticsView(val switch: SwitchBlockEntity): View() {
init {
intrinsicContentSize = Size(180.0, 90.0)
}
override fun drawContent(matrixStack: MatrixStack, mouse: Point, delta: Float) {
RenderHelper.fill(matrixStack, bounds, Color.BLACK)
if (switch.packetStatistics.size == 0) {
return
}
// TODO: drawLine isn't working for some reason
RenderHelper.drawLine(Point(bounds.left, bounds.top), Point(bounds.right, bounds.bottom), 1.0, 2f, Color.MAGENTA)
return
val maxPackets = switch.packetStatistics.maxOf { it }
val maxDataPointsCount = 60
var lastPoint: Point? = null
val size = Size(3.0, 3.0)
for ((index, packets) in switch.packetStatistics.withIndex()) {
val x = (1 - (switch.packetStatistics.size - index).toDouble() / maxDataPointsCount) * bounds.width
val y = (1 - (packets.toDouble() / maxPackets)) * (bounds.height)
val point = Point(x, y)
if (lastPoint != null) {
// RenderHelper.fill(matrixStack, Rect(lastPoint, 3.0, 3.0), Color.RED)
RenderHelper.drawLine(lastPoint, point, 1.0, 2f, Color.RED)
}
lastPoint = point
}
}
}

View File

@ -0,0 +1,46 @@
package net.shadowfacts.phycon.block.netswitch
import net.minecraft.entity.player.PlayerEntity
import net.minecraft.entity.player.PlayerInventory
import net.minecraft.network.PacketByteBuf
import net.minecraft.screen.ScreenHandler
import net.minecraft.util.Identifier
import net.shadowfacts.phycon.PhysicalConnectivity
import net.shadowfacts.phycon.init.PhyBlocks
import net.shadowfacts.phycon.init.PhyScreens
/**
* @author shadowfacts
*/
class SwitchConsoleScreenHandler(
syncId: Int,
val switch: SwitchBlockEntity,
): ScreenHandler(PhyScreens.SWITCH_CONSOLE, syncId) {
companion object {
val ID = Identifier(PhysicalConnectivity.MODID, "switch_console")
}
constructor(syncId: Int, playerInv: PlayerInventory, buf: PacketByteBuf):
this(
syncId,
PhyBlocks.SWITCH.getBlockEntity(playerInv.player.world, buf.readBlockPos())!!
)
init {
switch.statisticsObserver = {
switch.world!!.updateListeners(switch.pos, switch.cachedState, switch.cachedState, 3)
}
}
override fun canUse(player: PlayerEntity): Boolean {
return true
}
override fun close(player: PlayerEntity) {
super.close(player)
switch.statisticsObserver = null
}
}

View File

@ -1,8 +1,14 @@
package net.shadowfacts.phycon.init package net.shadowfacts.phycon.init
import net.fabricmc.fabric.api.screenhandler.v1.ExtendedScreenHandlerType
import net.fabricmc.fabric.api.screenhandler.v1.ExtendedScreenHandlerType.ExtendedFactory
import net.fabricmc.fabric.api.screenhandler.v1.ScreenHandlerRegistry import net.fabricmc.fabric.api.screenhandler.v1.ScreenHandlerRegistry
import net.minecraft.screen.ScreenHandler
import net.minecraft.screen.ScreenHandlerType import net.minecraft.screen.ScreenHandlerType
import net.minecraft.util.Identifier
import net.minecraft.util.registry.Registry
import net.shadowfacts.phycon.block.inserter.InserterScreenHandler import net.shadowfacts.phycon.block.inserter.InserterScreenHandler
import net.shadowfacts.phycon.block.netswitch.SwitchConsoleScreenHandler
import net.shadowfacts.phycon.block.redstone_emitter.RedstoneEmitterScreenHandler import net.shadowfacts.phycon.block.redstone_emitter.RedstoneEmitterScreenHandler
import net.shadowfacts.phycon.block.terminal.CraftingTerminalBlock import net.shadowfacts.phycon.block.terminal.CraftingTerminalBlock
import net.shadowfacts.phycon.block.terminal.CraftingTerminalScreenHandler import net.shadowfacts.phycon.block.terminal.CraftingTerminalScreenHandler
@ -19,12 +25,19 @@ object PhyScreens {
private set private set
lateinit var REDSTONE_EMITTER: ScreenHandlerType<RedstoneEmitterScreenHandler> lateinit var REDSTONE_EMITTER: ScreenHandlerType<RedstoneEmitterScreenHandler>
private set private set
lateinit var SWITCH_CONSOLE: ScreenHandlerType<SwitchConsoleScreenHandler>
private set
fun init() { fun init() {
TERMINAL = ScreenHandlerRegistry.registerExtended(TerminalBlock.ID, ::TerminalScreenHandler) TERMINAL = register(TerminalBlock.ID, ::TerminalScreenHandler)
CRAFTING_TERMINAL = ScreenHandlerRegistry.registerExtended(CraftingTerminalBlock.ID, ::CraftingTerminalScreenHandler) CRAFTING_TERMINAL = register(CraftingTerminalBlock.ID, ::CraftingTerminalScreenHandler)
INSERTER = ScreenHandlerRegistry.registerExtended(InserterScreenHandler.ID, ::InserterScreenHandler) INSERTER = register(InserterScreenHandler.ID, ::InserterScreenHandler)
REDSTONE_EMITTER = ScreenHandlerRegistry.registerExtended(RedstoneEmitterScreenHandler.ID, ::RedstoneEmitterScreenHandler) REDSTONE_EMITTER = register(RedstoneEmitterScreenHandler.ID, ::RedstoneEmitterScreenHandler)
SWITCH_CONSOLE = register(SwitchConsoleScreenHandler.ID, ::SwitchConsoleScreenHandler)
}
fun <T: ScreenHandler> register(id: Identifier, factory: ExtendedFactory<T>): ScreenHandlerType<T> {
return Registry.register(Registry.SCREEN_HANDLER, id, ExtendedScreenHandlerType(factory))
} }
} }

View File

@ -1,14 +1,25 @@
package net.shadowfacts.phycon.item package net.shadowfacts.phycon.item
import net.fabricmc.fabric.api.screenhandler.v1.ExtendedScreenHandlerFactory
import net.minecraft.client.MinecraftClient import net.minecraft.client.MinecraftClient
import net.minecraft.entity.player.PlayerEntity
import net.minecraft.entity.player.PlayerInventory
import net.minecraft.item.Item import net.minecraft.item.Item
import net.minecraft.item.ItemUsageContext import net.minecraft.item.ItemUsageContext
import net.minecraft.network.PacketByteBuf
import net.minecraft.screen.ScreenHandler
import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.text.Text
import net.minecraft.util.ActionResult import net.minecraft.util.ActionResult
import net.minecraft.util.Identifier import net.minecraft.util.Identifier
import net.shadowfacts.phycon.PhysicalConnectivity import net.shadowfacts.phycon.PhysicalConnectivity
import net.shadowfacts.phycon.block.DeviceBlock import net.shadowfacts.phycon.block.DeviceBlock
import net.shadowfacts.phycon.block.DeviceBlockEntity import net.shadowfacts.phycon.block.DeviceBlockEntity
import net.shadowfacts.phycon.block.netswitch.SwitchBlockEntity
import net.shadowfacts.phycon.client.screen.console.DeviceConsoleScreen import net.shadowfacts.phycon.client.screen.console.DeviceConsoleScreen
import net.shadowfacts.phycon.block.netswitch.SwitchConsoleScreen
import net.shadowfacts.phycon.block.netswitch.SwitchConsoleScreenHandler
import net.shadowfacts.phycon.init.PhyBlocks
/** /**
* @author shadowfacts * @author shadowfacts
@ -31,6 +42,14 @@ class ConsoleItem: Item(Settings().maxCount(1)) {
} }
return ActionResult.SUCCESS return ActionResult.SUCCESS
} }
} else if (block === PhyBlocks.SWITCH) {
val be = block.getBlockEntity(context.world, context.blockPos)
if (be != null) {
if (!context.world.isClient && context.player != null) {
openScreen(be, context.player!!)
}
return ActionResult.SUCCESS
}
} }
return ActionResult.PASS return ActionResult.PASS
} }
@ -41,4 +60,19 @@ class ConsoleItem: Item(Settings().maxCount(1)) {
MinecraftClient.getInstance().setScreen(screen) MinecraftClient.getInstance().setScreen(screen)
} }
private fun openScreen(be: SwitchBlockEntity, player: PlayerEntity) {
val factory = object: ExtendedScreenHandlerFactory {
override fun createMenu(syncId: Int, playerInv: PlayerInventory, player: PlayerEntity): ScreenHandler {
return SwitchConsoleScreenHandler(syncId, be)
}
override fun getDisplayName() = PhyBlocks.SWITCH.name
override fun writeScreenOpeningData(player: ServerPlayerEntity, buf: PacketByteBuf) {
buf.writeBlockPos(be.pos)
}
}
player.openHandledScreen(factory)
}
} }

View File

@ -0,0 +1,69 @@
package net.shadowfacts.phycon.util
import kotlin.math.min
/**
* @author shadowfacts
*/
class IntRingBuffer(size: Int): Iterable<Int> {
private val array = IntArray(size)
private var next = 0
private var wrapped = false
val size: Int
get() = if (wrapped) array.size else next
fun add(value: Int) {
array[next] = value
next = (next + 1) % array.size
wrapped = wrapped || next == 0
}
fun asContiguousArray(): IntArray {
if (wrapped) {
val array = IntArray(array.size)
this.array.copyInto(array, destinationOffset = array.size - next, startIndex = 0, endIndex = next)
this.array.copyInto(array, destinationOffset = 0, startIndex = next, endIndex = array.size)
return array
} else {
return this.array.copyOfRange(0, next)
}
}
fun replace(array: IntArray) {
val count = min(array.size, this.array.size)
array.copyInto(this.array, destinationOffset = 0, startIndex = array.size - count, endIndex = count)
next = (count + 1) % this.array.size
wrapped = array.size >= this.array.size
}
fun clear() {
next = 0
wrapped = false
}
override fun iterator() = Iterator(this)
class Iterator(private val ringBuffer: IntRingBuffer): IntIterator() {
private var nextIndex = if (ringBuffer.wrapped) ringBuffer.next else 0
private var wrapped = false
override fun nextInt(): Int {
val res = ringBuffer.array[nextIndex]
nextIndex += 1
if (nextIndex >= ringBuffer.array.size) {
wrapped = true
nextIndex = 0
}
return res
}
override fun hasNext(): Boolean {
return if (ringBuffer.wrapped) {
!wrapped || nextIndex < ringBuffer.next
} else {
nextIndex < ringBuffer.next
}
}
}
}

View File

@ -0,0 +1,48 @@
package net.shadowfacts.phycon.util
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.Assertions.assertArrayEquals
import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.Assertions.assertEquals
/**
* @author shadowfacts
*/
class IntRingBufferTests {
@Test
fun testEmpty() {
assertArrayEquals(intArrayOf(), IntRingBuffer(4).asContiguousArray())
}
@Test
fun testAsContiguousArray() {
val buffer = IntRingBuffer(4)
buffer.add(0)
buffer.add(1)
assertArrayEquals(intArrayOf(0, 1), buffer.asContiguousArray())
buffer.add(2)
buffer.add(3)
buffer.add(4)
assertArrayEquals(intArrayOf(1, 2, 3, 4), buffer.asContiguousArray())
}
@Test
fun testIterator() {
val buffer = IntRingBuffer(4)
val iterator = buffer.iterator()
assertFalse(iterator.hasNext())
buffer.add(0)
buffer.add(1)
assertEquals(listOf(0, 1), buffer.toList())
buffer.add(2)
buffer.add(3)
buffer.add(4)
assertEquals(listOf(1, 2, 3, 4), buffer.toList())
}
}