Merge branch 'feature/relay-gui' into develop

This commit is contained in:
Fingercomp 2025-01-27 00:20:25 +03:00
commit 9d323138d3
No known key found for this signature in database
GPG Key ID: BBC71CEE45D86E37
11 changed files with 207 additions and 28 deletions

@ -1 +1 @@
Subproject commit 4691682de8b765ac0f862c57a08e29f8d776fb46 Subproject commit e1968df8a58fab83572e657d981b7263c7edd72a

View File

@ -186,3 +186,7 @@ OcelotCardTooltip = #c1905f
RackRelayButtonText = #e0e0e0 RackRelayButtonText = #e0e0e0
Flash = #ffffff Flash = #ffffff
RelayTextLow = #009900
RelayTextMid = #999900
RelayTextHigh = #990000

View File

@ -12,6 +12,7 @@ import ocelot.desktop.util.ReflectionUtils.findUnaryConstructor
import totoro.ocelot.brain.entity.traits.{Entity, Environment, Inventory => BrainInventory} import totoro.ocelot.brain.entity.traits.{Entity, Environment, Inventory => BrainInventory}
import totoro.ocelot.brain.event.{InventoryEntityAddedEvent, InventoryEntityRemovedEvent} import totoro.ocelot.brain.event.{InventoryEntityAddedEvent, InventoryEntityRemovedEvent}
import totoro.ocelot.brain.nbt.NBTTagCompound import totoro.ocelot.brain.nbt.NBTTagCompound
import totoro.ocelot.brain.network.Network
import scala.annotation.tailrec import scala.annotation.tailrec
@ -98,6 +99,17 @@ trait SyncedInventory extends PersistedInventory with EventAware with Logging {
override def onItemAdded(slot: Slot): Unit = { override def onItemAdded(slot: Slot): Unit = {
if (!isLoading) { 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) sync(slot.index, SyncDirection.DesktopToBrain)
} }
} }

View File

@ -37,7 +37,7 @@ class ServerItem(val server: Server)
server.tier match { server.tier match {
case Tier.One => case Tier.One =>
cardSlots = addSlotWidgets(new CardSlotWidget(_, Tier.Two), new CardSlotWidget(_, Tier.Two)) 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)) componentBusSlots = addSlotWidgets(new ComponentBusSlotWidget(_, Tier.Two))
memorySlots = addSlotWidgets(new MemorySlotWidget(_, Tier.Two), new MemorySlotWidget(_, Tier.Two)) memorySlots = addSlotWidgets(new MemorySlotWidget(_, Tier.Two), new MemorySlotWidget(_, Tier.Two))
diskSlots = addSlotWidgets(new HddSlotWidget(_, Tier.Two)) diskSlots = addSlotWidgets(new HddSlotWidget(_, Tier.Two))
@ -45,7 +45,7 @@ class ServerItem(val server: Server)
case Tier.Two => case Tier.Two =>
cardSlots = addSlotWidgets(new CardSlotWidget(_, Tier.Three), new CardSlotWidget(_, Tier.Two), new CardSlotWidget(_, 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)) componentBusSlots = addSlotWidgets(new ComponentBusSlotWidget(_, Tier.Three), new ComponentBusSlotWidget(_, Tier.Three))
memorySlots = addSlotWidgets(new MemorySlotWidget(_, Tier.Three), new MemorySlotWidget(_, Tier.Three), new MemorySlotWidget(_, 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)) 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( componentBusSlots = addSlotWidgets(
new ComponentBusSlotWidget(_, Tier.Three), new ComponentBusSlotWidget(_, Tier.Three),

View File

@ -66,7 +66,7 @@ class ComputerNode(val computerCase: Case)
memorySlots = addSlotWidgets(new MemorySlotWidget(_, Tier.One)) memorySlots = addSlotWidgets(new MemorySlotWidget(_, Tier.One))
diskSlots = addSlotWidgets(new HddSlotWidget(_, Tier.One)) diskSlots = addSlotWidgets(new HddSlotWidget(_, Tier.One))
floppySlot = None 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 // no idea why on earth the memory slots are split in two here
memorySlots :+= addSlotWidget(new MemorySlotWidget(_, Tier.One)) memorySlots :+= addSlotWidget(new MemorySlotWidget(_, Tier.One))
eepromSlot = addSlotWidget(new EepromSlotWidget(_)) eepromSlot = addSlotWidget(new EepromSlotWidget(_))
@ -76,7 +76,7 @@ class ComputerNode(val computerCase: Case)
memorySlots = addSlotWidgets(new MemorySlotWidget(_, Tier.Two), new MemorySlotWidget(_, Tier.Two)) memorySlots = addSlotWidgets(new MemorySlotWidget(_, Tier.Two), new MemorySlotWidget(_, Tier.Two))
diskSlots = addSlotWidgets(new HddSlotWidget(_, Tier.Two), new HddSlotWidget(_, Tier.One)) diskSlots = addSlotWidgets(new HddSlotWidget(_, Tier.Two), new HddSlotWidget(_, Tier.One))
floppySlot = None floppySlot = None
cpuSlot = addSlotWidget(new CpuSlotWidget(_, this, Tier.Two)) cpuSlot = addSlotWidget(new ComputerCpuSlotWidget(_, this, Tier.Two))
eepromSlot = addSlotWidget(new EepromSlotWidget(_)) eepromSlot = addSlotWidget(new EepromSlotWidget(_))
case _ => case _ =>
@ -107,7 +107,7 @@ class ComputerNode(val computerCase: Case)
} }
floppySlot = Some(addSlotWidget(new FloppySlotWidget(_))) floppySlot = Some(addSlotWidget(new FloppySlotWidget(_)))
cpuSlot = addSlotWidget(new CpuSlotWidget(_, this, Tier.Three)) cpuSlot = addSlotWidget(new ComputerCpuSlotWidget(_, this, Tier.Three))
eepromSlot = addSlotWidget(new EepromSlotWidget(_)) eepromSlot = addSlotWidget(new EepromSlotWidget(_))
} }
} }

View File

@ -74,7 +74,7 @@ class MicrocontrollerNode(val microcontroller: Microcontroller)
new CardSlotWidget(_, Tier.One) new CardSlotWidget(_, Tier.One)
) )
cpuSlot = addSlotWidget(new CpuSlotWidget(_, this, Tier.One)) cpuSlot = addSlotWidget(new ComputerCpuSlotWidget(_, this, Tier.One))
memorySlots = addSlotWidgets( memorySlots = addSlotWidgets(
new MemorySlotWidget(_, Tier.One), new MemorySlotWidget(_, Tier.One),
@ -89,7 +89,7 @@ class MicrocontrollerNode(val microcontroller: Microcontroller)
new CardSlotWidget(_, Tier.One) new CardSlotWidget(_, Tier.One)
) )
cpuSlot = addSlotWidget(new CpuSlotWidget(_, this, Tier.One)) cpuSlot = addSlotWidget(new ComputerCpuSlotWidget(_, this, Tier.One))
memorySlots = addSlotWidgets( memorySlots = addSlotWidgets(
new MemorySlotWidget(_, Tier.One), new MemorySlotWidget(_, Tier.One),
@ -105,7 +105,7 @@ class MicrocontrollerNode(val microcontroller: Microcontroller)
new CardSlotWidget(_, Tier.Three) new CardSlotWidget(_, Tier.Three)
) )
cpuSlot = addSlotWidget(new CpuSlotWidget(_, this, Tier.Three)) cpuSlot = addSlotWidget(new ComputerCpuSlotWidget(_, this, Tier.Three))
memorySlots = addSlotWidgets( memorySlots = addSlotWidgets(
new MemorySlotWidget(_, Tier.Three), new MemorySlotWidget(_, Tier.Three),

View File

@ -1,12 +1,19 @@
package ocelot.desktop.node.nodes package ocelot.desktop.node.nodes
import ocelot.desktop.graphics.IconSource 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.entity.Relay
import totoro.ocelot.brain.network import totoro.ocelot.brain.network
import totoro.ocelot.brain.util.Direction 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 val iconSource: IconSource = IconSource.Nodes.Relay
override def ports: Array[NodePort] = Array( override def ports: Array[NodePort] = Array(
@ -24,4 +31,8 @@ class RelayNode(val relay: Relay) extends EntityNode(relay) {
override def shouldReceiveEventsFor(address: String): Boolean = { override def shouldReceiveEventsFor(address: String): Boolean = {
ports.exists(port => getNodeByPort(port).address == address) ports.exists(port => getNodeByPort(port).address == address)
} }
override def brainInventory: Relay = relay
override protected def createWindow(): RelayWindow = new RelayWindow(this)
} }

View File

@ -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 _ =>
}
}
}

View File

@ -1,24 +1,11 @@
package ocelot.desktop.ui.widget.slot package ocelot.desktop.ui.widget.slot
import ocelot.desktop.graphics.IconSource import ocelot.desktop.graphics.IconSource
import ocelot.desktop.inventory.Inventory
import ocelot.desktop.inventory.traits.CpuLikeItem import ocelot.desktop.inventory.traits.CpuLikeItem
import ocelot.desktop.inventory.{Inventory, Item}
import ocelot.desktop.util.ComputerAware
import totoro.ocelot.brain.util.Tier.Tier 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 ghostIcon: Option[IconSource] = Some(IconSource.CpuIcon)
override def slotTier: Option[Tier] = Some(_tier) 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 _ =>
}
}
} }

View File

@ -82,7 +82,7 @@ trait ComputerAware
} }
var eepromSlot: EepromSlotWidget = _ var eepromSlot: EepromSlotWidget = _
var cpuSlot: CpuSlotWidget = _ var cpuSlot: ComputerCpuSlotWidget = _
var componentBusSlots: Array[ComponentBusSlotWidget] = Array.empty var componentBusSlots: Array[ComponentBusSlotWidget] = Array.empty
var memorySlots: Array[MemorySlotWidget] = Array.empty var memorySlots: Array[MemorySlotWidget] = Array.empty
var cardSlots: Array[CardSlotWidget] = Array.empty var cardSlots: Array[CardSlotWidget] = Array.empty

View File

@ -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, mid: Int, high: Int): Color = {
if (value < mid) {
ColorScheme("RelayTextLow")
} else if (value < high) {
ColorScheme("RelayTextMid")
} else {
ColorScheme("RelayTextHigh")
}
}
}
object RelayWindow {
private val TransferRateFormat = new DecimalFormat("#.##hz")
private val LabelWidth = 130
}