mirror of
https://gitlab.com/cc-ru/ocelot/ocelot-desktop.git
synced 2026-01-06 19:22:38 +01:00
Merge branch 'develop'
This commit is contained in:
commit
f157465e50
@ -59,6 +59,8 @@ release:
|
||||
image: registry.gitlab.com/gitlab-org/release-cli:latest
|
||||
rules:
|
||||
- if: $CI_COMMIT_TAG
|
||||
before_script:
|
||||
- apk add git
|
||||
script:
|
||||
- echo "Creating a new release for tag $CI_COMMIT_TAG."
|
||||
- |
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
name := "ocelot-desktop"
|
||||
version := "1.9.0"
|
||||
version := "1.9.1"
|
||||
scalaVersion := "2.13.10"
|
||||
|
||||
lazy val root = project.in(file("."))
|
||||
|
||||
@ -6,12 +6,11 @@ import ocelot.desktop.inventory.SyncedInventory.SlotStatus.SlotStatus
|
||||
import ocelot.desktop.inventory.SyncedInventory.SyncDirection.SyncDirection
|
||||
import ocelot.desktop.inventory.SyncedInventory._
|
||||
import ocelot.desktop.inventory.traits.ComponentItem
|
||||
import ocelot.desktop.ui.event.BrainEvent
|
||||
import ocelot.desktop.ui.widget.EventHandlers
|
||||
import ocelot.desktop.ui.event.{BrainEvent, Event, EventAware}
|
||||
import ocelot.desktop.util.Logging
|
||||
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.event.{InventoryEntityAddedEvent, InventoryEntityRemovedEvent, NodeEvent}
|
||||
import totoro.ocelot.brain.nbt.NBTTagCompound
|
||||
|
||||
import scala.annotation.tailrec
|
||||
@ -28,7 +27,7 @@ import scala.annotation.tailrec
|
||||
* While synchronizing, relies on the convergence of changes, but guards against stack overflows
|
||||
* by limiting the recursion depth.
|
||||
*/
|
||||
trait SyncedInventory extends PersistedInventory with Logging {
|
||||
trait SyncedInventory extends EventAware with PersistedInventory with Logging {
|
||||
override type I <: Item with ComponentItem
|
||||
|
||||
// to avoid synchronization while we're loading stuff
|
||||
@ -38,8 +37,6 @@ trait SyncedInventory extends PersistedInventory with Logging {
|
||||
private var syncFuel: Int = _
|
||||
refuel()
|
||||
|
||||
protected def eventHandlers: EventHandlers
|
||||
|
||||
/**
|
||||
* The backing [[BrainInventory brain Inventory]].
|
||||
*/
|
||||
@ -78,7 +75,7 @@ trait SyncedInventory extends PersistedInventory with Logging {
|
||||
case None =>
|
||||
throw ItemLoadException(
|
||||
s"an item class ${itemClass.getName} cannot be instantiated " +
|
||||
s"with $entity (class ${entity.getClass.getName})",
|
||||
s"with $entity (class ${entity.getClass.getName})"
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -91,6 +88,10 @@ trait SyncedInventory extends PersistedInventory with Logging {
|
||||
slotNbt.setString(SlotEntityAddressTag, item.component.node.address)
|
||||
}
|
||||
|
||||
override def shouldReceiveEventsFor(address: String): Boolean =
|
||||
super.shouldReceiveEventsFor(address) ||
|
||||
inventoryIterator.flatMap(_.get).exists(_.shouldReceiveEventsFor(address))
|
||||
|
||||
eventHandlers += {
|
||||
case BrainEvent(InventoryEntityAddedEvent(slot, _)) if slot.inventory.owner eq brainInventory =>
|
||||
sync(slot.index, SyncDirection.BrainToDesktop)
|
||||
@ -99,6 +100,17 @@ trait SyncedInventory extends PersistedInventory with Logging {
|
||||
sync(slot.index, SyncDirection.BrainToDesktop)
|
||||
}
|
||||
|
||||
override def handleEvent(event: Event): Unit = {
|
||||
super.handleEvent(event)
|
||||
|
||||
for (slot <- inventoryIterator; item <- slot.get) {
|
||||
event match {
|
||||
case n: NodeEvent if !item.shouldReceiveEventsFor(n.address) => // ignore
|
||||
case _ => item.handleEvent(event)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override def onItemAdded(slot: Slot): Unit = {
|
||||
if (!isLoading) {
|
||||
sync(slot.index, SyncDirection.DesktopToBrain)
|
||||
@ -112,7 +124,9 @@ trait SyncedInventory extends PersistedInventory with Logging {
|
||||
}
|
||||
|
||||
def syncSlots(direction: SyncDirection = SyncDirection.Reconcile): Unit = {
|
||||
val occupiedSlots = inventoryIterator.map(_.index).toSet
|
||||
val occupiedSlots = inventoryIterator
|
||||
.map(_.index)
|
||||
.toSet
|
||||
.union(brainInventory.inventory.iterator.map(_.index).toSet)
|
||||
|
||||
for (slotIndex <- occupiedSlots)
|
||||
@ -124,8 +138,7 @@ trait SyncedInventory extends PersistedInventory with Logging {
|
||||
|
||||
try {
|
||||
doSync(slotIndex, direction)
|
||||
}
|
||||
finally {
|
||||
} finally {
|
||||
if (initialSync)
|
||||
refuel()
|
||||
}
|
||||
@ -141,29 +154,28 @@ trait SyncedInventory extends PersistedInventory with Logging {
|
||||
|
||||
if (syncFuel < 0) {
|
||||
// ignore: the limit has already been reached
|
||||
}
|
||||
else if (syncFuel == 0) {
|
||||
} else if (syncFuel == 0) {
|
||||
logger.error(
|
||||
s"Got trapped in an infinite loop while trying to synchronize the slot $slotIndex " +
|
||||
s"in $this (class ${this.getClass.getName})!",
|
||||
s"in $this (class ${this.getClass.getName})!"
|
||||
)
|
||||
logger.error(
|
||||
"The item in the slot: " +
|
||||
Slot(slotIndex).get.map(item => s"$item (class ${item.getClass.getName})").getOrElse("<empty>"),
|
||||
Slot(slotIndex).get.map(item => s"$item (class ${item.getClass.getName})").getOrElse("<empty>")
|
||||
)
|
||||
logger.error(
|
||||
"The entity if the slot: " +
|
||||
brainInventory.inventory(slotIndex)
|
||||
brainInventory
|
||||
.inventory(slotIndex)
|
||||
.get
|
||||
.map(entity => s"$entity (class ${entity.getClass.getName})")
|
||||
.getOrElse("<empty>"),
|
||||
.getOrElse("<empty>")
|
||||
)
|
||||
|
||||
logger.error("Breaking the loop forcefully by removing the items.")
|
||||
Slot(slotIndex).remove()
|
||||
brainInventory.inventory(slotIndex).remove()
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
direction match {
|
||||
case _ if checkSlotStatus(slotIndex) == SlotStatus.Synchronized =>
|
||||
|
||||
@ -175,57 +187,59 @@ trait SyncedInventory extends PersistedInventory with Logging {
|
||||
|
||||
case SyncDirection.BrainToDesktop =>
|
||||
val item = brainInventory.inventory(slotIndex).get match {
|
||||
case Some(entity) => Items.recover(entity) match {
|
||||
case Some(item) => Some(item.asInstanceOf[I])
|
||||
case Some(entity) =>
|
||||
Items.recover(entity) match {
|
||||
case Some(item) => Some(item.asInstanceOf[I])
|
||||
|
||||
case None =>
|
||||
logger.error(
|
||||
s"An entity ($entity class ${entity.getClass.getName}) was inserted into a slot " +
|
||||
s"(index: $slotIndex) of a brain inventory $brainInventory, " +
|
||||
s"but we were unable to recover an Item from it.",
|
||||
)
|
||||
logger.error(
|
||||
s"A Desktop inventory $this (class ${getClass.getName}) could not recover the item. Removing.",
|
||||
)
|
||||
logEntityLoss(slotIndex, entity)
|
||||
case None =>
|
||||
logger.error(
|
||||
s"An entity ($entity class ${entity.getClass.getName}) was inserted into a slot " +
|
||||
s"(index: $slotIndex) of a brain inventory $brainInventory, " +
|
||||
s"but we were unable to recover an Item from it."
|
||||
)
|
||||
logger.error(
|
||||
s"A Desktop inventory $this (class ${getClass.getName}) could not recover the item. Removing."
|
||||
)
|
||||
logEntityLoss(slotIndex, entity)
|
||||
|
||||
None
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
case None => None
|
||||
}
|
||||
|
||||
Slot(slotIndex).set(item)
|
||||
|
||||
case SyncDirection.Reconcile => checkSlotStatus(slotIndex) match {
|
||||
case SlotStatus.Synchronized => // no-op
|
||||
case SyncDirection.Reconcile =>
|
||||
checkSlotStatus(slotIndex) match {
|
||||
case SlotStatus.Synchronized => // no-op
|
||||
|
||||
// let's just grab whatever we have
|
||||
case SlotStatus.DesktopNonEmpty => doSync(slotIndex, SyncDirection.DesktopToBrain)
|
||||
case SlotStatus.BrainNonEmpty => doSync(slotIndex, SyncDirection.BrainToDesktop)
|
||||
// let's just grab whatever we have
|
||||
case SlotStatus.DesktopNonEmpty => doSync(slotIndex, SyncDirection.DesktopToBrain)
|
||||
case SlotStatus.BrainNonEmpty => doSync(slotIndex, SyncDirection.BrainToDesktop)
|
||||
|
||||
case SlotStatus.Conflict =>
|
||||
// so, the brain inventory and the Desktop inventory have conflicting views on what the slot contains
|
||||
// we'll let Desktop win because it has more info
|
||||
case SlotStatus.Conflict =>
|
||||
// so, the brain inventory and the Desktop inventory have conflicting views on what the slot contains
|
||||
// we'll let Desktop win because it has more info
|
||||
|
||||
(brainInventory.inventory(slotIndex).get, Slot(slotIndex).get) match {
|
||||
case (Some(entity), Some(item)) =>
|
||||
logger.error(
|
||||
s"Encountered an inventory conflict for slot $slotIndex! " +
|
||||
s"The Desktop inventory believes the slot contains $item (class ${item.getClass.getName}), " +
|
||||
s"but the brain inventory believes the slot contains $entity (class ${entity.getClass.getName}).",
|
||||
)
|
||||
logger.error("Resolving the conflict in favor of Ocelot Desktop.")
|
||||
(brainInventory.inventory(slotIndex).get, Slot(slotIndex).get) match {
|
||||
case (Some(entity), Some(item)) =>
|
||||
logger.error(
|
||||
s"Encountered an inventory conflict for slot $slotIndex! " +
|
||||
s"The Desktop inventory believes the slot contains $item (class ${item.getClass.getName}), " +
|
||||
s"but the brain inventory believes the slot contains $entity (class ${entity.getClass.getName})."
|
||||
)
|
||||
logger.error("Resolving the conflict in favor of Ocelot Desktop.")
|
||||
|
||||
case _ =>
|
||||
// this really should not happen... but alright, let's throw an exception at least
|
||||
throw new IllegalStateException(
|
||||
"an inventory conflict was detected even though one of the slots is empty",
|
||||
)
|
||||
}
|
||||
case _ =>
|
||||
// this really should not happen... but alright, let's throw an exception at least
|
||||
throw new IllegalStateException(
|
||||
"an inventory conflict was detected even though one of the slots is empty"
|
||||
)
|
||||
}
|
||||
|
||||
doSync(slotIndex, SyncDirection.DesktopToBrain)
|
||||
}
|
||||
doSync(slotIndex, SyncDirection.DesktopToBrain)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -234,7 +248,7 @@ trait SyncedInventory extends PersistedInventory with Logging {
|
||||
logger.error(
|
||||
s"Encountered a data loss! " +
|
||||
s"In the brain inventory $brainInventory (class ${brainInventory.getClass.getName}), " +
|
||||
s"the entity $entity (class ${entity.getClass.getName}) is deleted from the slot $slotIndex.",
|
||||
s"the entity $entity (class ${entity.getClass.getName}) is deleted from the slot $slotIndex."
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -28,7 +28,12 @@ object DiskDriveMountableItem {
|
||||
|
||||
override def icon: IconSource = IconSource.DiskDriveMountable
|
||||
|
||||
override def build(): DiskDriveMountableItem = new DiskDriveMountableItem(new DiskDriveMountable())
|
||||
override def build(): DiskDriveMountableItem = {
|
||||
val item = new DiskDriveMountableItem(new DiskDriveMountable())
|
||||
item.fillSlotsWithDefaultItems()
|
||||
|
||||
item
|
||||
}
|
||||
|
||||
override def recoverers: Iterable[ItemRecoverer[_, _]] = Some(ItemRecoverer(new DiskDriveMountableItem(_)))
|
||||
}
|
||||
|
||||
@ -52,6 +52,9 @@ trait ComponentItem extends Item with PersistableItem {
|
||||
setWorkspace()
|
||||
}
|
||||
|
||||
override def shouldReceiveEventsFor(address: String): Boolean =
|
||||
super.shouldReceiveEventsFor(address) || Option(component.node).exists(_.address == address)
|
||||
|
||||
override def fillTooltip(tooltip: ItemTooltip): Unit = {
|
||||
super.fillTooltip(tooltip)
|
||||
|
||||
|
||||
@ -4,6 +4,7 @@ import ocelot.desktop.OcelotDesktop
|
||||
import ocelot.desktop.audio._
|
||||
import ocelot.desktop.geometry.Vector2D
|
||||
import ocelot.desktop.graphics.Graphics
|
||||
import ocelot.desktop.inventory.SyncedInventory
|
||||
import ocelot.desktop.node.ComputerAwareNode.{ErrorMessageMoveSpeed, MaxErrorMessageDistance}
|
||||
import ocelot.desktop.ui.UiHandler
|
||||
import ocelot.desktop.ui.event.BrainEvent
|
||||
@ -17,10 +18,11 @@ import totoro.ocelot.brain.event._
|
||||
import scala.collection.mutable
|
||||
|
||||
abstract class ComputerAwareNode(entity: Entity with Environment with WorkspaceAware)
|
||||
extends SyncedInventoryEntityNode(entity)
|
||||
with DiskActivityHandler
|
||||
with ShiftClickNode
|
||||
{
|
||||
extends EntityNode(entity)
|
||||
with SyncedInventory
|
||||
with DiskActivityHandler
|
||||
with ShiftClickNode {
|
||||
|
||||
private val messages = mutable.ArrayBuffer[(Float, ComputerErrorMessageLabel)]()
|
||||
|
||||
protected def addErrorMessage(message: ComputerErrorMessageLabel): Unit = synchronized {
|
||||
|
||||
@ -49,7 +49,7 @@ abstract class EntityNode(val entity: Entity with Environment) extends Node {
|
||||
override def getNodeByPort(port: NodePort): network.Node = entity.node
|
||||
|
||||
override def shouldReceiveEventsFor(address: String): Boolean =
|
||||
entity.node != null && address == entity.node.address
|
||||
super.shouldReceiveEventsFor(address) || entity.node != null && address == entity.node.address
|
||||
|
||||
override def dispose(): Unit = {
|
||||
super.dispose()
|
||||
|
||||
@ -124,8 +124,6 @@ abstract class Node extends Widget with DragHandler with ClickHandler with Hover
|
||||
|
||||
def getNodeByPort(port: NodePort): network.Node = throw new IllegalArgumentException("this node has no ports")
|
||||
|
||||
def shouldReceiveEventsFor(address: String): Boolean = false
|
||||
|
||||
def connections: Iterator[(NodePort, Node, NodePort)] = _connections.iterator
|
||||
|
||||
def connect(portA: NodePort, node: Node, portB: NodePort): Unit = {
|
||||
@ -222,9 +220,13 @@ abstract class Node extends Widget with DragHandler with ClickHandler with Hover
|
||||
|
||||
val desiredPos =
|
||||
if (Keyboard.isKeyDown(Keyboard.KEY_LCONTROL))
|
||||
(pos - workspaceView.cameraOffset).snap(Size) +
|
||||
workspaceView.cameraOffset -
|
||||
grabPoint.snap(Size)
|
||||
(pos - workspaceView.cameraOffset).snap(Size) + // snap the position to the grid relative to the origin
|
||||
workspaceView.cameraOffset - // restore the camera offset
|
||||
grabPoint.snap(Size) + // accounts for multi-block screens
|
||||
Vector2D(
|
||||
((Size - width) % Size) / 2,
|
||||
((Size - height) % Size) / 2,
|
||||
) // if a node is not full-size, moves it to the center of the grid cell
|
||||
else
|
||||
pos - grabPoint
|
||||
|
||||
|
||||
@ -1,26 +0,0 @@
|
||||
package ocelot.desktop.node
|
||||
|
||||
import ocelot.desktop.inventory.SyncedInventory
|
||||
import ocelot.desktop.inventory.traits.ComponentItem
|
||||
import ocelot.desktop.ui.event.Event
|
||||
import totoro.ocelot.brain.entity.traits.{Entity, Environment}
|
||||
|
||||
abstract class SyncedInventoryEntityNode(entity: Entity with Environment)
|
||||
extends EntityNode(entity)
|
||||
with SyncedInventory {
|
||||
|
||||
override def shouldReceiveEventsFor(address: String): Boolean =
|
||||
super.shouldReceiveEventsFor(address) ||
|
||||
inventoryIterator
|
||||
.flatMap(_.get)
|
||||
.collect { case item: ComponentItem => item }
|
||||
.exists(_.component.node.address == address)
|
||||
|
||||
override def handleEvent(event: Event): Unit = {
|
||||
super.handleEvent(event)
|
||||
|
||||
for (slot <- inventoryIterator; item <- slot.get) {
|
||||
item.handleEvent(event)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,9 +1,11 @@
|
||||
package ocelot.desktop.node
|
||||
|
||||
import ocelot.desktop.ui.event.sources.KeyEvents
|
||||
import ocelot.desktop.ui.event.{ClickEvent, MouseEvent}
|
||||
import ocelot.desktop.ui.widget.window.{Window, Windowed}
|
||||
import org.lwjgl.input.Keyboard
|
||||
|
||||
trait WindowedNode[T <: Window] extends Node with Windowed[T]{
|
||||
trait WindowedNode[T <: Window] extends Node with Windowed[T] {
|
||||
override def dispose(): Unit = {
|
||||
closeAndDisposeWindowIfCreated()
|
||||
|
||||
@ -21,7 +23,8 @@ trait WindowedNode[T <: Window] extends Node with Windowed[T]{
|
||||
super.onClick(event)
|
||||
|
||||
event match {
|
||||
case ClickEvent(MouseEvent.Button.Left, _) =>
|
||||
// FIXME: this. is. ugly. do proper modifier tracking.
|
||||
case ClickEvent(MouseEvent.Button.Left, _) if !KeyEvents.isDown(Keyboard.KEY_LSHIFT) =>
|
||||
if (hasWindowCreated && window.isOpen)
|
||||
window.close()
|
||||
else
|
||||
|
||||
@ -2,8 +2,9 @@ package ocelot.desktop.node.nodes
|
||||
|
||||
import ocelot.desktop.geometry.Rect2D
|
||||
import ocelot.desktop.graphics.Graphics
|
||||
import ocelot.desktop.inventory.SyncedInventory
|
||||
import ocelot.desktop.node.Node.{HighlightThickness, NoHighlightSize}
|
||||
import ocelot.desktop.node.{LabeledEntityNode, ShiftClickNode, SyncedInventoryEntityNode, WindowedNode}
|
||||
import ocelot.desktop.node.{EntityNode, LabeledEntityNode, ShiftClickNode, WindowedNode}
|
||||
import ocelot.desktop.ui.event.ClickEvent
|
||||
import ocelot.desktop.ui.event.handlers.DiskActivityHandler
|
||||
import ocelot.desktop.ui.widget.contextmenu.ContextMenu
|
||||
@ -12,13 +13,14 @@ import ocelot.desktop.windows.DiskDriveWindow
|
||||
import totoro.ocelot.brain.entity.FloppyDiskDrive
|
||||
|
||||
class DiskDriveNode(entity: FloppyDiskDrive)
|
||||
extends SyncedInventoryEntityNode(entity)
|
||||
with LabeledEntityNode
|
||||
with DiskDriveAware
|
||||
with DiskActivityHandler
|
||||
with ShiftClickNode
|
||||
with WindowedNode[DiskDriveWindow]
|
||||
{
|
||||
extends EntityNode(entity)
|
||||
with SyncedInventory
|
||||
with LabeledEntityNode
|
||||
with DiskDriveAware
|
||||
with DiskActivityHandler
|
||||
with ShiftClickNode
|
||||
with WindowedNode[DiskDriveWindow] {
|
||||
|
||||
override def icon: String = "nodes/disk-drive/Default"
|
||||
|
||||
override def setupContextMenu(menu: ContextMenu, event: ClickEvent): Unit = {
|
||||
@ -40,9 +42,9 @@ class DiskDriveNode(entity: FloppyDiskDrive)
|
||||
position.x + HighlightThickness,
|
||||
position.y + HighlightThickness,
|
||||
NoHighlightSize,
|
||||
NoHighlightSize
|
||||
NoHighlightSize,
|
||||
),
|
||||
"nodes/disk-drive/"
|
||||
"nodes/disk-drive/",
|
||||
)
|
||||
}
|
||||
|
||||
@ -54,7 +56,7 @@ class DiskDriveNode(entity: FloppyDiskDrive)
|
||||
|
||||
override protected def onShiftClick(event: ClickEvent): Unit = {
|
||||
if (isFloppyItemPresent)
|
||||
eject()
|
||||
eject()
|
||||
}
|
||||
|
||||
override protected def hoveredShiftStatusBarText: String = "Eject floppy"
|
||||
|
||||
@ -5,6 +5,8 @@ import ocelot.desktop.ui.widget.EventHandlers
|
||||
trait EventAware {
|
||||
protected val eventHandlers = new EventHandlers
|
||||
|
||||
def shouldReceiveEventsFor(address: String): Boolean = false
|
||||
|
||||
def handleEvent(event: Event): Unit = {
|
||||
eventHandlers(event)
|
||||
}
|
||||
|
||||
@ -22,6 +22,7 @@ trait DiskDriveAware
|
||||
with Windowed[DiskDriveWindow]
|
||||
{
|
||||
override type I = FloppyItem
|
||||
|
||||
def floppyDiskDrive: FloppyDiskDrive
|
||||
|
||||
override def brainInventory: Inventory = floppyDiskDrive.inventory.owner
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user