Dispose of items when they become inaccessible

Closes #63.
This commit is contained in:
Fingercomp 2025-08-16 20:18:00 +03:00
parent 5bcc005143
commit f6c41f0184
No known key found for this signature in database
GPG Key ID: BBC71CEE45D86E37
5 changed files with 59 additions and 26 deletions

View File

@ -7,12 +7,16 @@ import totoro.ocelot.brain.event.NodeEvent
import scala.collection.mutable import scala.collection.mutable
/** Provides an inventory — a collection of [[Item]]s indexed by slots. */ /**
* Provides an inventory a collection of [[Item]]s indexed by slots.
*/
trait Inventory extends EventAware with Disposable { trait Inventory extends EventAware with Disposable {
// parallels totoro.ocelot.brain.entity.traits.Inventory // parallels totoro.ocelot.brain.entity.traits.Inventory
// this is intentional // this is intentional
/** The type of items stored in this inventory. */ /**
* The type of items stored in this inventory.
*/
type I <: Item type I <: Item
private type WeakHashSet[A] = mutable.WeakHashMap[A, Unit] private type WeakHashSet[A] = mutable.WeakHashMap[A, Unit]
@ -23,21 +27,18 @@ trait Inventory extends EventAware with Disposable {
override def dispose(): Unit = { override def dispose(): Unit = {
super.dispose() super.dispose()
inventoryIterator.foreach(_.removeAndDispose())
for (slot <- inventoryIterator) {
val item = slot.get
slot.remove()
item.foreach(_.dispose())
}
} }
/** Called after a new item is added to the inventory. /**
* Called after a new item is added to the inventory.
* *
* @param slot the slot the item was added to * @param slot the slot the item was added to
*/ */
def onItemAdded(slot: Slot): Unit def onItemAdded(slot: Slot): Unit
/** Called after an item is removed from the inventory. /**
* Called after an item is removed from the inventory.
* *
* When the item is replaced by another one, the event are sequenced in the following order: * When the item is replaced by another one, the event are sequenced in the following order:
* *
@ -52,7 +53,9 @@ trait Inventory extends EventAware with Disposable {
*/ */
def onItemRemoved(slot: Slot, removedItem: I, replacedBy: Option[I]): Unit def onItemRemoved(slot: Slot, removedItem: I, replacedBy: Option[I]): Unit
/** An iterator over all slots occupied in this inventory. */ /**
* An iterator over all slots occupied in this inventory.
*/
def inventoryIterator: Iterator[Slot] = slotItems.keysIterator.map(Slot(_)) def inventoryIterator: Iterator[Slot] = slotItems.keysIterator.map(Slot(_))
def clearInventory(): Unit = { def clearInventory(): Unit = {
@ -89,7 +92,9 @@ trait Inventory extends EventAware with Disposable {
} }
} }
/** A proxy to access a slot of the inventory. */ /**
* A proxy to access a slot of the inventory.
*/
final class Slot private[Inventory] (val index: Int) { final class Slot private[Inventory] (val index: Int) {
require(index >= 0) require(index >= 0)
@ -97,27 +102,46 @@ trait Inventory extends EventAware with Disposable {
def nonEmpty: Boolean = !isEmpty def nonEmpty: Boolean = !isEmpty
/** Inserts the `item` into this slot (replacing the previous item if any). */ /**
* Inserts the `item` into this slot (replacing the previous item if any).
*/
def put(item: inventory.I): Unit = { def put(item: inventory.I): Unit = {
setSlot(index, Some(item)) setSlot(index, Some(item))
} }
/** Allows inserting/removing the item in this slot. */ /**
* Allows inserting/removing the item in this slot.
*/
def set(item: Option[inventory.I]): Unit = { def set(item: Option[inventory.I]): Unit = {
setSlot(index, item) setSlot(index, item)
} }
/** Removes the item contained in this slot if there is one. */ /**
* Removes the item contained in this slot if there is one.
*/
def remove(): Unit = { def remove(): Unit = {
setSlot(index, None) setSlot(index, None)
} }
/** The [[Item]] contained in this slot. */ /**
* Removes the item contained in this slot if there is one, calling the item's [[dispose]] method.
*/
def removeAndDispose(): Unit = {
for (item <- get) {
remove()
item.dispose()
}
}
/**
* The [[Item]] contained in this slot.
*/
def get: Option[inventory.I] = slotItems.get(index) def get: Option[inventory.I] = slotItems.get(index)
val inventory: Inventory.this.type = Inventory.this val inventory: Inventory.this.type = Inventory.this
/** Registers an observer to receive item added/removed events. /**
* Registers an observer to receive item added/removed events.
* *
* @note The inventory keeps a '''weak''' reference to the `observer`. * @note The inventory keeps a '''weak''' reference to the `observer`.
*/ */
@ -159,7 +183,9 @@ trait Inventory extends EventAware with Disposable {
} }
final object Slot { final object Slot {
/** Creates a proxy to an inventory slot. */ /**
* Creates a proxy to an inventory slot.
*/
def apply(index: Int) = new Slot(index) def apply(index: Int) = new Slot(index)
} }
@ -216,13 +242,15 @@ trait Inventory extends EventAware with Disposable {
object Inventory { object Inventory {
trait SlotObserver { trait SlotObserver {
/** Called after an item was inserted into this slot. /**
* Called after an item was inserted into this slot.
* *
* @note [[Inventory.onItemAdded]] is called before this method. * @note [[Inventory.onItemAdded]] is called before this method.
*/ */
def onItemAdded(): Unit def onItemAdded(): Unit
/** Called after an item was removed from this slot. /**
* Called after an item was removed from this slot.
* *
* In particular, the slot no longer contains the removed item. * In particular, the slot no longer contains the removed item.
* *
@ -230,7 +258,9 @@ object Inventory {
*/ */
def onItemRemoved(removedItem: Item, replacedBy: Option[Item]): Unit def onItemRemoved(removedItem: Item, replacedBy: Option[Item]): Unit
/** Called when an item contained in this slot sends a notification via [[Item.notifySlot]]. */ /**
* Called when an item contained in this slot sends a notification via [[Item.notifySlot]].
*/
def onItemNotification(notification: Item.Notification): Unit def onItemNotification(notification: Item.Notification): Unit
} }
} }

View File

@ -104,7 +104,7 @@ trait PersistedInventory extends Inventory with Logging with Persistable {
} }
protected def onSlotLoadFailed(slotIndex: Int): Unit = { protected def onSlotLoadFailed(slotIndex: Int): Unit = {
Slot(slotIndex).remove() Slot(slotIndex).removeAndDispose()
} }
protected def saveEntityItem(slotNbt: NBTTagCompound, item: EntityItem): Unit = { protected def saveEntityItem(slotNbt: NBTTagCompound, item: EntityItem): Unit = {

View File

@ -178,7 +178,7 @@ trait SyncedInventory extends PersistedInventory with EventAware with Logging {
) )
logger.error("Breaking the loop forcefully by removing the items.") logger.error("Breaking the loop forcefully by removing the items.")
Slot(slotIndex).remove() Slot(slotIndex).removeAndDispose()
brainInventory.inventory(slotIndex).remove() brainInventory.inventory(slotIndex).remove()
} else { } else {
direction match { direction match {

View File

@ -110,7 +110,7 @@ class SlotWidget[I <: Item](private val slot: Inventory#Slot)(implicit slotItemT
item.fillRmbMenu(menu) item.fillRmbMenu(menu)
menu.addEntry( menu.addEntry(
ContextMenuEntry("Remove", IconSource.Delete, SoundSource.InterfaceClickLow) { ContextMenuEntry("Remove", IconSource.Delete, SoundSource.InterfaceClickLow) {
slot.remove() slot.removeAndDispose()
} }
) )
} }

View File

@ -117,8 +117,11 @@ trait ComputerAware
.minByOption(_.slotTier) .minByOption(_.slotTier)
} }
for (item <- items; newSlot <- findBestSlot(item, slots)) { for (item <- items) {
newSlot.item = item findBestSlot(item, slots) match{
case Some(newSlot) => newSlot.item = item
case None => item.dispose()
}
} }
} }