From cd86902dd5d6c0b75c47f1e936e50a104e4e5a34 Mon Sep 17 00:00:00 2001 From: Fingercomp Date: Sun, 26 Jan 2025 22:32:54 +0300 Subject: [PATCH 1/2] Add a relay GUI Closes #74. --- lib/ocelot-brain | 2 +- .../resources/ocelot/desktop/colorscheme.txt | 4 + .../desktop/inventory/SyncedInventory.scala | 12 ++ .../desktop/inventory/item/ServerItem.scala | 6 +- .../desktop/node/nodes/ComputerNode.scala | 6 +- .../node/nodes/MicrocontrollerNode.scala | 6 +- .../ocelot/desktop/node/nodes/RelayNode.scala | 15 +- .../widget/slot/ComputerCpuSlotWidget.scala | 22 +++ .../ui/widget/slot/CpuSlotWidget.scala | 17 +-- .../ocelot/desktop/util/ComputerAware.scala | 2 +- .../ocelot/desktop/windows/RelayWindow.scala | 143 ++++++++++++++++++ 11 files changed, 207 insertions(+), 28 deletions(-) create mode 100644 src/main/scala/ocelot/desktop/ui/widget/slot/ComputerCpuSlotWidget.scala create mode 100644 src/main/scala/ocelot/desktop/windows/RelayWindow.scala diff --git a/lib/ocelot-brain b/lib/ocelot-brain index 4691682..e1968df 160000 --- a/lib/ocelot-brain +++ b/lib/ocelot-brain @@ -1 +1 @@ -Subproject commit 4691682de8b765ac0f862c57a08e29f8d776fb46 +Subproject commit e1968df8a58fab83572e657d981b7263c7edd72a diff --git a/src/main/resources/ocelot/desktop/colorscheme.txt b/src/main/resources/ocelot/desktop/colorscheme.txt index 37f7b96..1a8086f 100644 --- a/src/main/resources/ocelot/desktop/colorscheme.txt +++ b/src/main/resources/ocelot/desktop/colorscheme.txt @@ -186,3 +186,7 @@ OcelotCardTooltip = #c1905f RackRelayButtonText = #e0e0e0 Flash = #ffffff + +RelayTextGreen = #009900 +RelayTextYellow = #999900 +RelayTextRed = #990000 diff --git a/src/main/scala/ocelot/desktop/inventory/SyncedInventory.scala b/src/main/scala/ocelot/desktop/inventory/SyncedInventory.scala index f76b0a1..a891855 100644 --- a/src/main/scala/ocelot/desktop/inventory/SyncedInventory.scala +++ b/src/main/scala/ocelot/desktop/inventory/SyncedInventory.scala @@ -12,6 +12,7 @@ import ocelot.desktop.util.ReflectionUtils.findUnaryConstructor import totoro.ocelot.brain.entity.traits.{Entity, Environment, Inventory => BrainInventory} import totoro.ocelot.brain.event.{InventoryEntityAddedEvent, InventoryEntityRemovedEvent} import totoro.ocelot.brain.nbt.NBTTagCompound +import totoro.ocelot.brain.network.Network import scala.annotation.tailrec @@ -98,6 +99,17 @@ trait SyncedInventory extends PersistedInventory with EventAware with Logging { override def onItemAdded(slot: Slot): Unit = { if (!isLoading) { + // FIXME: the sheer scope of this atrocity is mind-blowing: + // we strip nodes of their free will and forcefully imprint onto them an address — + // and only because there is no other way to identify brain entities! + // in an ideal world, persistable objects should have a stable identifier + // which is in no way tied to environments and their addresses. + // alas, for the time being we have to bear the burden of grave sins and war crimes. + val component = slot.get.get.asInstanceOf[ComponentItem].component + if (component.node.address == null) { + Network.joinNewNetwork(component.node) + } + sync(slot.index, SyncDirection.DesktopToBrain) } } diff --git a/src/main/scala/ocelot/desktop/inventory/item/ServerItem.scala b/src/main/scala/ocelot/desktop/inventory/item/ServerItem.scala index 6511a8a..f52e5a4 100644 --- a/src/main/scala/ocelot/desktop/inventory/item/ServerItem.scala +++ b/src/main/scala/ocelot/desktop/inventory/item/ServerItem.scala @@ -37,7 +37,7 @@ class ServerItem(val server: Server) server.tier match { case Tier.One => cardSlots = addSlotWidgets(new CardSlotWidget(_, Tier.Two), new CardSlotWidget(_, Tier.Two)) - cpuSlot = addSlotWidget(new CpuSlotWidget(_, this, Tier.Two)) + cpuSlot = addSlotWidget(new ComputerCpuSlotWidget(_, this, Tier.Two)) componentBusSlots = addSlotWidgets(new ComponentBusSlotWidget(_, Tier.Two)) memorySlots = addSlotWidgets(new MemorySlotWidget(_, Tier.Two), new MemorySlotWidget(_, Tier.Two)) diskSlots = addSlotWidgets(new HddSlotWidget(_, Tier.Two)) @@ -45,7 +45,7 @@ class ServerItem(val server: Server) case Tier.Two => cardSlots = addSlotWidgets(new CardSlotWidget(_, Tier.Three), new CardSlotWidget(_, Tier.Two), new CardSlotWidget(_, Tier.Two)) - cpuSlot = addSlotWidget(new CpuSlotWidget(_, this, Tier.Three)) + cpuSlot = addSlotWidget(new ComputerCpuSlotWidget(_, this, Tier.Three)) componentBusSlots = addSlotWidgets(new ComponentBusSlotWidget(_, Tier.Three), new ComponentBusSlotWidget(_, Tier.Three)) memorySlots = addSlotWidgets(new MemorySlotWidget(_, Tier.Three), new MemorySlotWidget(_, Tier.Three), new MemorySlotWidget(_, Tier.Three)) diskSlots = addSlotWidgets(new HddSlotWidget(_, Tier.Three), new HddSlotWidget(_, Tier.Three)) @@ -70,7 +70,7 @@ class ServerItem(val server: Server) ) } - cpuSlot = addSlotWidget(new CpuSlotWidget(_, this, Tier.Three)) + cpuSlot = addSlotWidget(new ComputerCpuSlotWidget(_, this, Tier.Three)) componentBusSlots = addSlotWidgets( new ComponentBusSlotWidget(_, Tier.Three), diff --git a/src/main/scala/ocelot/desktop/node/nodes/ComputerNode.scala b/src/main/scala/ocelot/desktop/node/nodes/ComputerNode.scala index 1839d5a..597a303 100644 --- a/src/main/scala/ocelot/desktop/node/nodes/ComputerNode.scala +++ b/src/main/scala/ocelot/desktop/node/nodes/ComputerNode.scala @@ -66,7 +66,7 @@ class ComputerNode(val computerCase: Case) memorySlots = addSlotWidgets(new MemorySlotWidget(_, Tier.One)) diskSlots = addSlotWidgets(new HddSlotWidget(_, Tier.One)) floppySlot = None - cpuSlot = addSlotWidget(new CpuSlotWidget(_, this, Tier.One)) + cpuSlot = addSlotWidget(new ComputerCpuSlotWidget(_, this, Tier.One)) // no idea why on earth the memory slots are split in two here memorySlots :+= addSlotWidget(new MemorySlotWidget(_, Tier.One)) eepromSlot = addSlotWidget(new EepromSlotWidget(_)) @@ -76,7 +76,7 @@ class ComputerNode(val computerCase: Case) memorySlots = addSlotWidgets(new MemorySlotWidget(_, Tier.Two), new MemorySlotWidget(_, Tier.Two)) diskSlots = addSlotWidgets(new HddSlotWidget(_, Tier.Two), new HddSlotWidget(_, Tier.One)) floppySlot = None - cpuSlot = addSlotWidget(new CpuSlotWidget(_, this, Tier.Two)) + cpuSlot = addSlotWidget(new ComputerCpuSlotWidget(_, this, Tier.Two)) eepromSlot = addSlotWidget(new EepromSlotWidget(_)) case _ => @@ -107,7 +107,7 @@ class ComputerNode(val computerCase: Case) } floppySlot = Some(addSlotWidget(new FloppySlotWidget(_))) - cpuSlot = addSlotWidget(new CpuSlotWidget(_, this, Tier.Three)) + cpuSlot = addSlotWidget(new ComputerCpuSlotWidget(_, this, Tier.Three)) eepromSlot = addSlotWidget(new EepromSlotWidget(_)) } } diff --git a/src/main/scala/ocelot/desktop/node/nodes/MicrocontrollerNode.scala b/src/main/scala/ocelot/desktop/node/nodes/MicrocontrollerNode.scala index 0608ca1..420741f 100644 --- a/src/main/scala/ocelot/desktop/node/nodes/MicrocontrollerNode.scala +++ b/src/main/scala/ocelot/desktop/node/nodes/MicrocontrollerNode.scala @@ -74,7 +74,7 @@ class MicrocontrollerNode(val microcontroller: Microcontroller) new CardSlotWidget(_, Tier.One) ) - cpuSlot = addSlotWidget(new CpuSlotWidget(_, this, Tier.One)) + cpuSlot = addSlotWidget(new ComputerCpuSlotWidget(_, this, Tier.One)) memorySlots = addSlotWidgets( new MemorySlotWidget(_, Tier.One), @@ -89,7 +89,7 @@ class MicrocontrollerNode(val microcontroller: Microcontroller) new CardSlotWidget(_, Tier.One) ) - cpuSlot = addSlotWidget(new CpuSlotWidget(_, this, Tier.One)) + cpuSlot = addSlotWidget(new ComputerCpuSlotWidget(_, this, Tier.One)) memorySlots = addSlotWidgets( new MemorySlotWidget(_, Tier.One), @@ -105,7 +105,7 @@ class MicrocontrollerNode(val microcontroller: Microcontroller) new CardSlotWidget(_, Tier.Three) ) - cpuSlot = addSlotWidget(new CpuSlotWidget(_, this, Tier.Three)) + cpuSlot = addSlotWidget(new ComputerCpuSlotWidget(_, this, Tier.Three)) memorySlots = addSlotWidgets( new MemorySlotWidget(_, Tier.Three), diff --git a/src/main/scala/ocelot/desktop/node/nodes/RelayNode.scala b/src/main/scala/ocelot/desktop/node/nodes/RelayNode.scala index 5166883..4cf2eae 100644 --- a/src/main/scala/ocelot/desktop/node/nodes/RelayNode.scala +++ b/src/main/scala/ocelot/desktop/node/nodes/RelayNode.scala @@ -1,12 +1,19 @@ package ocelot.desktop.node.nodes import ocelot.desktop.graphics.IconSource -import ocelot.desktop.node.{EntityNode, NodePort} +import ocelot.desktop.inventory.SyncedInventory +import ocelot.desktop.node.{EntityNode, LabeledNode, NodePort, WindowedNode} +import ocelot.desktop.windows.RelayWindow import totoro.ocelot.brain.entity.Relay import totoro.ocelot.brain.network import totoro.ocelot.brain.util.Direction -class RelayNode(val relay: Relay) extends EntityNode(relay) { +class RelayNode(val relay: Relay) + extends EntityNode(relay) + with SyncedInventory + with LabeledNode + with WindowedNode[RelayWindow] { + override val iconSource: IconSource = IconSource.Nodes.Relay override def ports: Array[NodePort] = Array( @@ -24,4 +31,8 @@ class RelayNode(val relay: Relay) extends EntityNode(relay) { override def shouldReceiveEventsFor(address: String): Boolean = { ports.exists(port => getNodeByPort(port).address == address) } + + override def brainInventory: Relay = relay + + override protected def createWindow(): RelayWindow = new RelayWindow(this) } diff --git a/src/main/scala/ocelot/desktop/ui/widget/slot/ComputerCpuSlotWidget.scala b/src/main/scala/ocelot/desktop/ui/widget/slot/ComputerCpuSlotWidget.scala new file mode 100644 index 0000000..1a316ee --- /dev/null +++ b/src/main/scala/ocelot/desktop/ui/widget/slot/ComputerCpuSlotWidget.scala @@ -0,0 +1,22 @@ +package ocelot.desktop.ui.widget.slot + +import ocelot.desktop.inventory.traits.CpuLikeItem +import ocelot.desktop.inventory.{Inventory, Item} +import ocelot.desktop.util.ComputerAware +import totoro.ocelot.brain.util.Tier.Tier + +class ComputerCpuSlotWidget(slot: Inventory#Slot, computer: ComputerAware, _tier: Tier) + extends CpuSlotWidget(slot, _tier) { + + protected override def onItemNotification(notification: Item.Notification): Unit = { + super.onItemNotification(notification) + + notification match { + case CpuLikeItem.CpuArchitectureChangedNotification => + computer.turnOn() + computer.turnOff() + + case _ => + } + } +} diff --git a/src/main/scala/ocelot/desktop/ui/widget/slot/CpuSlotWidget.scala b/src/main/scala/ocelot/desktop/ui/widget/slot/CpuSlotWidget.scala index d426eff..9947f63 100644 --- a/src/main/scala/ocelot/desktop/ui/widget/slot/CpuSlotWidget.scala +++ b/src/main/scala/ocelot/desktop/ui/widget/slot/CpuSlotWidget.scala @@ -1,24 +1,11 @@ package ocelot.desktop.ui.widget.slot import ocelot.desktop.graphics.IconSource +import ocelot.desktop.inventory.Inventory import ocelot.desktop.inventory.traits.CpuLikeItem -import ocelot.desktop.inventory.{Inventory, Item} -import ocelot.desktop.util.ComputerAware import totoro.ocelot.brain.util.Tier.Tier -class CpuSlotWidget(slot: Inventory#Slot, computer: ComputerAware, _tier: Tier) extends SlotWidget[CpuLikeItem](slot) { +class CpuSlotWidget(slot: Inventory#Slot, _tier: Tier) extends SlotWidget[CpuLikeItem](slot) { override def ghostIcon: Option[IconSource] = Some(IconSource.CpuIcon) override def slotTier: Option[Tier] = Some(_tier) - - protected override def onItemNotification(notification: Item.Notification): Unit = { - super.onItemNotification(notification) - - notification match { - case CpuLikeItem.CpuArchitectureChangedNotification => - computer.turnOn() - computer.turnOff() - - case _ => - } - } } diff --git a/src/main/scala/ocelot/desktop/util/ComputerAware.scala b/src/main/scala/ocelot/desktop/util/ComputerAware.scala index 6304fa1..7a44612 100644 --- a/src/main/scala/ocelot/desktop/util/ComputerAware.scala +++ b/src/main/scala/ocelot/desktop/util/ComputerAware.scala @@ -82,7 +82,7 @@ trait ComputerAware } var eepromSlot: EepromSlotWidget = _ - var cpuSlot: CpuSlotWidget = _ + var cpuSlot: ComputerCpuSlotWidget = _ var componentBusSlots: Array[ComponentBusSlotWidget] = Array.empty var memorySlots: Array[MemorySlotWidget] = Array.empty var cardSlots: Array[CardSlotWidget] = Array.empty diff --git a/src/main/scala/ocelot/desktop/windows/RelayWindow.scala b/src/main/scala/ocelot/desktop/windows/RelayWindow.scala new file mode 100644 index 0000000..62b1656 --- /dev/null +++ b/src/main/scala/ocelot/desktop/windows/RelayWindow.scala @@ -0,0 +1,143 @@ +package ocelot.desktop.windows + +import ocelot.desktop.ColorScheme +import ocelot.desktop.color.Color +import ocelot.desktop.geometry.Size2D +import ocelot.desktop.inventory.ItemFactory +import ocelot.desktop.inventory.item.{LinkedCardItem, WirelessNetworkCardItem} +import ocelot.desktop.node.nodes.RelayNode +import ocelot.desktop.ui.layout.{AlignItems, JustifyContent, Layout, LinearLayout} +import ocelot.desktop.ui.widget.slot.{CardSlotWidget, CpuSlotWidget, HddSlotWidget, MemorySlotWidget} +import ocelot.desktop.ui.widget.window.PanelWindow +import ocelot.desktop.ui.widget.{Label, Widget} +import ocelot.desktop.util.Orientation +import ocelot.desktop.windows.RelayWindow.{LabelWidth, TransferRateFormat} +import totoro.ocelot.brain.util.Tier +import totoro.ocelot.brain.util.Tier.Tier + +import java.text.DecimalFormat + +class RelayWindow(val node: RelayNode) extends PanelWindow { + override protected def title: String = "Relay" + + private def makeLabel(_text: => String): Label = new Label { + override def text: String = _text + + override def minimumSize: Size2D = super.minimumSize.copy(width = LabelWidth) + + override def maximumSize: Size2D = minimumSize + } + + setInner(new Widget { + override protected val layout: Layout = new LinearLayout(this, orientation = Orientation.Vertical, gap = 4f) + + // CPU + children :+= new Widget { + override protected val layout: Layout = new LinearLayout( + this, + orientation = Orientation.Horizontal, + alignItems = AlignItems.Center, + gap = 8f, + ) + + children :+= makeLabel("Cycle rate") + + children :+= new Label { + override def text: String = s"${TransferRateFormat.format(20f / node.relay.relayDelay)}" + } + + children :+= new CpuSlotWidget(node.Slot(node.relay.cpuSlot.index), Tier.Three) { + override def slotTier: Option[Tier] = None + } + } + + // Memory + children :+= new Widget { + override protected val layout: Layout = new LinearLayout( + this, + orientation = Orientation.Horizontal, + alignItems = AlignItems.Center, + gap = 8f, + ) + + children :+= makeLabel("Packets / cycle") + + children :+= new Label { + override def text: String = s"${node.relay.packetsPerCycleAvg()} / ${node.relay.relayAmount}" + + override def color: Color = thresholdBasedColor( + node.relay.packetsPerCycleAvg(), + (node.relay.relayAmount / 2f).ceil.toInt, + node.relay.relayAmount, + ) + } + + children :+= new MemorySlotWidget(node.Slot(node.relay.memorySlot.index), Tier.Three) { + override def slotTier: Option[Tier] = None + } + } + + // HDD + children :+= new Widget { + override protected val layout: Layout = new LinearLayout( + this, + orientation = Orientation.Horizontal, + alignItems = AlignItems.Center, + gap = 8f, + ) + + children :+= makeLabel("Queue size") + + children :+= new Label { + override def text: String = s"${node.relay.queue.size} / ${node.relay.maxQueueSize}" + + override def color: Color = thresholdBasedColor( + node.relay.queue.size, + node.relay.maxQueueSize / 2, + node.relay.maxQueueSize, + ) + } + + children :+= new HddSlotWidget(node.Slot(node.relay.hddSlot.index), Tier.Three) { + override def slotTier: Option[Tier] = None + } + } + + // Network card + children :+= new Widget { + override protected val layout: Layout = new LinearLayout( + this, + orientation = Orientation.Horizontal, + justifyContent = JustifyContent.End, + gap = 8f, + ) + + override def maximumSize: Size2D = super.maximumSize.copy(width = Float.PositiveInfinity) + + children :+= new CardSlotWidget(node.Slot(node.relay.networkCardSlot.index), Tier.Three) { + override def slotTier: Option[Tier] = None + + override def isItemAccepted(factory: ItemFactory): Boolean = { + super.isItemAccepted(factory) && + Array(classOf[WirelessNetworkCardItem], classOf[LinkedCardItem]) + .exists(_.isAssignableFrom(factory.itemClass)) + } + } + } + }) + + private def thresholdBasedColor(value: Int, yellow: Int, red: Int): Color = { + if (value < yellow) { + ColorScheme("RelayTextGreen") + } else if (value < red) { + ColorScheme("RelayTextYellow") + } else { + ColorScheme("RelayTextRed") + } + } +} + +object RelayWindow { + private val TransferRateFormat = new DecimalFormat("#.##hz") + private val LabelWidth = 130 +} From 7a6d62efbed16b4226eef2037130599eb0125dde Mon Sep 17 00:00:00 2001 From: Fingercomp Date: Mon, 27 Jan 2025 00:16:13 +0300 Subject: [PATCH 2/2] Use semantic naming for relay window colors --- src/main/resources/ocelot/desktop/colorscheme.txt | 6 +++--- .../scala/ocelot/desktop/windows/RelayWindow.scala | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/main/resources/ocelot/desktop/colorscheme.txt b/src/main/resources/ocelot/desktop/colorscheme.txt index 1a8086f..1878c16 100644 --- a/src/main/resources/ocelot/desktop/colorscheme.txt +++ b/src/main/resources/ocelot/desktop/colorscheme.txt @@ -187,6 +187,6 @@ RackRelayButtonText = #e0e0e0 Flash = #ffffff -RelayTextGreen = #009900 -RelayTextYellow = #999900 -RelayTextRed = #990000 +RelayTextLow = #009900 +RelayTextMid = #999900 +RelayTextHigh = #990000 diff --git a/src/main/scala/ocelot/desktop/windows/RelayWindow.scala b/src/main/scala/ocelot/desktop/windows/RelayWindow.scala index 62b1656..f295ec9 100644 --- a/src/main/scala/ocelot/desktop/windows/RelayWindow.scala +++ b/src/main/scala/ocelot/desktop/windows/RelayWindow.scala @@ -126,13 +126,13 @@ class RelayWindow(val node: RelayNode) extends PanelWindow { } }) - private def thresholdBasedColor(value: Int, yellow: Int, red: Int): Color = { - if (value < yellow) { - ColorScheme("RelayTextGreen") - } else if (value < red) { - ColorScheme("RelayTextYellow") + private def thresholdBasedColor(value: Int, mid: Int, high: Int): Color = { + if (value < mid) { + ColorScheme("RelayTextLow") + } else if (value < high) { + ColorScheme("RelayTextMid") } else { - ColorScheme("RelayTextRed") + ColorScheme("RelayTextHigh") } } }