From 1da52f84036ccaccc364c374ba5a6e51d04ae653 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20N=C3=BCcke?= Date: Tue, 1 Oct 2013 10:03:01 +0200 Subject: [PATCH] removed the overly generic component saving again, items just have to be saved by their inventories. way too many problems with figuring out when to save otherwise (e.g. world unload triggers for each "sub-world", so when going from overworld to nether clearing the cache didn't really make sense; quite the opposite) --- li/cil/oc/OpenComputers.scala | 12 +-- li/cil/oc/api/Persistable.scala | 66 ------------ li/cil/oc/api/driver/Item.scala | 22 ++++ li/cil/oc/common/Proxy.scala | 3 +- li/cil/oc/common/item/GraphicsCard.scala | 4 - li/cil/oc/common/item/Persistable.scala | 100 ------------------ li/cil/oc/common/item/RedstoneCard.scala | 4 - .../tileentity/ComponentInventory.scala | 59 ++++++----- li/cil/oc/server/component/Computer.scala | 5 + li/cil/oc/server/component/RedstoneCard.scala | 1 - li/cil/oc/server/driver/Disk.scala | 2 +- li/cil/oc/server/driver/GraphicsCard.scala | 8 +- li/cil/oc/server/driver/Redstone.scala | 8 +- 13 files changed, 80 insertions(+), 214 deletions(-) delete mode 100644 li/cil/oc/common/item/Persistable.scala diff --git a/li/cil/oc/OpenComputers.scala b/li/cil/oc/OpenComputers.scala index 6b18da9bc..576d96fd9 100644 --- a/li/cil/oc/OpenComputers.scala +++ b/li/cil/oc/OpenComputers.scala @@ -12,7 +12,7 @@ import java.util.logging.Logger import li.cil.oc.client.{PacketHandler => ClientPacketHandler} import li.cil.oc.common.Proxy import li.cil.oc.server.{PacketHandler => ServerPacketHandler} -import scala.reflect.runtime.universe._ +import scala.reflect.runtime.{universe => ru} @Mod(modid = "OpenComputers", name = "OpenComputers", version = "0.0.0", dependencies = "required-after:Forge@[9.10.0.804,)", modLanguage = "scala") @NetworkMod(clientSideRequired = true, serverSideRequired = false, @@ -24,6 +24,11 @@ object OpenComputers { /** Logger used all throughout this mod. */ val log = Logger.getLogger("OpenComputers") + // Workaround for threading issues in Scala 2.10's runtime reflection: just + // initialize it once in the beginning. For more on this issue see + // http://docs.scala-lang.org/overviews/reflection/thread-safety.html + val mirror = ru.runtimeMirror(OpenComputers.getClass.getClassLoader) + @SidedProxy( clientSide = "li.cil.oc.client.Proxy", serverSide = "li.cil.oc.server.Proxy") @@ -37,9 +42,4 @@ object OpenComputers { @EventHandler def postInit(e: FMLPostInitializationEvent) = proxy.postInit(e) - - // Workaround for threading issues in Scala 2.10's runtime reflection: just - // initialize it once in the beginning. For more on this issue see - // http://docs.scala-lang.org/overviews/reflection/thread-safety.html - val dummyMirror = runtimeMirror(OpenComputers.getClass.getClassLoader) } \ No newline at end of file diff --git a/li/cil/oc/api/Persistable.scala b/li/cil/oc/api/Persistable.scala index ee36122ea..159672d6a 100644 --- a/li/cil/oc/api/Persistable.scala +++ b/li/cil/oc/api/Persistable.scala @@ -1,8 +1,6 @@ package li.cil.oc.api -import net.minecraft.item.ItemStack import net.minecraft.nbt.NBTTagCompound -import scala.reflect.runtime.{universe => ru} /** * An object that can be persisted to an NBT tag and restored back from it. @@ -24,68 +22,4 @@ trait Persistable { * @param nbt the tag to save the state to. */ def save(nbt: NBTTagCompound) -} - -/** - * Allows linking persistable instances to an item stack. - *

- * This is used to track item components. It creates an entry in the item - * stack's tag compound name "component", with a UUID that is used to link - * it to the component instance. - * Each type this is used with has to be registered first. For components, I - * recommend registering them in the constructor of the item the component - * belongs to. - *

- * It is important to note that since there is no way for the persistent - * object registry to know when a persistent object met its ultimate doom, - * entries will live forever, unless they are explicitly `delete`d. - */ -object Persistable { - /** - * Registers a new type valid for lookups. - *

- * The name has to be unique among all registered types. It is similar to - * how TileEntities names have to be unique, since this is used to look - * up the constructor when unpersisting instances. - * - * @param name the name of the type - * @param constructor a function that can be used to create a new instance. - * @tparam T the type of the object. - */ - def add[T <: Persistable : ru.TypeTag](name: String, constructor: () => T): Unit = - instance.foreach(_.add(name, constructor)) - - /** - * Retrieve the object associated to the specified item stack. - *

- * If there is no instance yet, one will be created. - * - * @param stack the item stack to get the persistent object for. - * @tparam T the type of the object. - * @return the persistent object associated with that item stack. - */ - def get[T <: Persistable : ru.TypeTag](stack: ItemStack): Option[T] = - instance.fold(None: Option[T])(_.get(stack)) - - /** - * Marks the object associated to the specified item stack for deletion. - *

- * Note that the persisted data is only deleted upon the next world save, to - * avoid discrepancies between the persistent storage and the savegame. - * - * @param stack the item stack to delete the persistent object for. - */ - def delete(stack: ItemStack): Unit = - instance.foreach(_.delete(stack)) - - // ----------------------------------------------------------------------- // - - /** Initialized in pre-init. */ - private[oc] var instance: Option[ { - def add[T <: Persistable : ru.TypeTag](name: String, constructor: () => T): Unit - - def get[T <: Persistable : ru.TypeTag](stack: ItemStack): Option[T] - - def delete(stack: ItemStack): Unit - }] = None } \ No newline at end of file diff --git a/li/cil/oc/api/driver/Item.scala b/li/cil/oc/api/driver/Item.scala index fefa8ffd3..bba29fd64 100644 --- a/li/cil/oc/api/driver/Item.scala +++ b/li/cil/oc/api/driver/Item.scala @@ -3,6 +3,7 @@ package li.cil.oc.api.driver import li.cil.oc.api.network.Node import net.minecraft.item.ItemStack import li.cil.oc.api.Driver +import net.minecraft.nbt.NBTTagCompound /** * Interface for item component drivers. @@ -54,9 +55,30 @@ trait Item extends Driver { * added to a computer, for example. Components that are not part of the * component network probably don't make much sense (can't think of any uses * at this time), but you may still opt to not implement this. + *

+ * This is expected to return a *new instance* each time it is called. * * @param item the item instance for which to get the node. * @return the network node for that item. */ def node(item: ItemStack): Option[Node] = None + + /** + * Get the tag compound based on the item stack to use for persisting the + * node associated with the specified item stack. + *

+ * This is only used if `node` is not `None`. This must always be a child + * tag of the items own tag compound, it will not be saved otherwise. Use + * this in the unlikely case that the default name collides with something. + * + * @param item the item to get the child tag to use for the `node`. + * @return the tag to use for saving and loading. + */ + def nbt(item: ItemStack) = { + val nbt = item.getTagCompound + if (!nbt.hasKey("oc.node")) { + nbt.setCompoundTag("oc.node", new NBTTagCompound()) + } + nbt.getCompoundTag("oc.node") + } } \ No newline at end of file diff --git a/li/cil/oc/common/Proxy.scala b/li/cil/oc/common/Proxy.scala index b9706d4de..d09a0aab5 100644 --- a/li/cil/oc/common/Proxy.scala +++ b/li/cil/oc/common/Proxy.scala @@ -16,13 +16,13 @@ class Proxy { Config.load(e.getSuggestedConfigurationFile) // Note: en_US is loaded automatically. + // TODO Are others as well? LanguageRegistry.instance.loadLocalization( "/assets/" + Config.resourceDomain + "/lang/de_DE.lang", "de_DE", false) api.Driver.instance = Some(driver.Registry) api.FileSystem.instance = Some(fs.FileSystem) api.Network.instance = Some(network.Network) - api.Persistable.instance = Some(item.Persistable) } def init(e: FMLInitializationEvent): Unit = { @@ -39,7 +39,6 @@ class Proxy { MinecraftForge.EVENT_BUS.register(Computer) MinecraftForge.EVENT_BUS.register(network.Network) - MinecraftForge.EVENT_BUS.register(item.Persistable) } def postInit(e: FMLPostInitializationEvent): Unit = { diff --git a/li/cil/oc/common/item/GraphicsCard.scala b/li/cil/oc/common/item/GraphicsCard.scala index 7c798aa00..1c7e4ed77 100644 --- a/li/cil/oc/common/item/GraphicsCard.scala +++ b/li/cil/oc/common/item/GraphicsCard.scala @@ -1,15 +1,11 @@ package li.cil.oc.common.item import li.cil.oc.Config -import li.cil.oc.api -import li.cil.oc.server.component import net.minecraft.client.renderer.texture.IconRegister class GraphicsCard(val parent: Delegator) extends Delegate { def unlocalizedName = "GraphicsCard" - api.Persistable.add("oc.item." + unlocalizedName, () => new component.GraphicsCard()) - override def registerIcons(iconRegister: IconRegister) { super.registerIcons(iconRegister) diff --git a/li/cil/oc/common/item/Persistable.scala b/li/cil/oc/common/item/Persistable.scala deleted file mode 100644 index 5bf365b59..000000000 --- a/li/cil/oc/common/item/Persistable.scala +++ /dev/null @@ -1,100 +0,0 @@ -package li.cil.oc.common.item - -import java.io._ -import java.util.logging.Level -import li.cil.oc.api -import li.cil.oc.{OpenComputers, Config} -import net.minecraft.item.ItemStack -import net.minecraft.nbt.{NBTBase, NBTTagCompound, NBTTagString} -import net.minecraftforge.common.DimensionManager -import net.minecraftforge.event.ForgeSubscribe -import net.minecraftforge.event.world.WorldEvent -import scala.collection.mutable -import scala.reflect.runtime.{universe => ru} - -object Persistable { - private val mirror = ru.runtimeMirror(getClass.getClassLoader) - - private val types = mutable.Map.empty[ru.Type, (String, () => api.Persistable)] - - private val cache = mutable.Map.empty[String, api.Persistable] - - private val deleted = mutable.Set.empty[String] - - def add[T <: api.Persistable : ru.TypeTag](name: String, constructor: () => T): Unit = - types += ru.typeOf[T] ->(name, constructor) - - def get[T <: api.Persistable : ru.TypeTag](stack: ItemStack): Option[T] = { - val uuid = if (stack.hasTagCompound && stack.getTagCompound.hasKey("component")) { - stack.getTagCompound.getString("component") - } else { - val uuid = java.util.UUID.randomUUID().toString - stack.setTagInfo("component", new NBTTagString(null, uuid)) - uuid - } - Some(cache.getOrElseUpdate(uuid, load(uuid).getOrElse({ - val (_, constructor) = types(ru.typeOf[T]) - constructor() - })).asInstanceOf[T]) - } - - def delete(stack: ItemStack): Unit = if (stack.hasTagCompound && stack.getTagCompound.hasKey("component")) { - val uuid = stack.getTagCompound.getString("component") - cache -= uuid - deleted += uuid - } - - private def saveDirectory = { - val directory = new File(DimensionManager.getCurrentSaveRootDirectory, Config.resourceDomain + "/components") - if (!directory.exists()) - directory.mkdirs() - directory - } - - @ForgeSubscribe - def onWorldUnload(e: WorldEvent.Unload) = { - cache.clear() - deleted.clear() - } - - @ForgeSubscribe - def onWorldSave(e: WorldEvent.Save) { - val directory = saveDirectory - for ((uuid, instance) <- cache) try { - val (name, _) = types(mirror.classSymbol(instance.getClass).toType) - val nbt = new NBTTagCompound(name) - instance.save(nbt) - val file = new File(directory, uuid) - val stream = new DataOutputStream(new FileOutputStream(file)) - NBTBase.writeNamedTag(nbt, stream) - stream.close() - } catch { - case e: Throwable => OpenComputers.log.log(Level.WARNING, "Error saving component.", e) - } - for (uuid <- deleted) { - try { - new File(saveDirectory, uuid).delete() - } catch { - case e: Throwable => OpenComputers.log.log(Level.WARNING, "Error deleting component.", e) - } - } - deleted.clear() - } - - def load(uuid: String) = { - val file = new File(saveDirectory, uuid) - if (file.exists()) try { - val stream = new DataInputStream(new FileInputStream(file)) - val nbt = NBTBase.readNamedTag(stream).asInstanceOf[NBTTagCompound] - stream.close() - val (_, constructor) = types.values.find { - case (name, _) => name == nbt.getName - }.get - val instance = constructor() - instance.load(nbt) - Some(instance) - } catch { - case e: Throwable => OpenComputers.log.log(Level.WARNING, "Error loading component.", e); None - } else None - } -} diff --git a/li/cil/oc/common/item/RedstoneCard.scala b/li/cil/oc/common/item/RedstoneCard.scala index 49f4dcc95..a85248255 100644 --- a/li/cil/oc/common/item/RedstoneCard.scala +++ b/li/cil/oc/common/item/RedstoneCard.scala @@ -1,15 +1,11 @@ package li.cil.oc.common.item import li.cil.oc.Config -import li.cil.oc.api -import li.cil.oc.server.component import net.minecraft.client.renderer.texture.IconRegister class RedstoneCard(val parent: Delegator) extends Delegate { def unlocalizedName = "RedstoneCard" - api.Persistable.add("oc.item." + unlocalizedName, () => new component.RedstoneCard()) - override def registerIcons(iconRegister: IconRegister) { super.registerIcons(iconRegister) diff --git a/li/cil/oc/common/tileentity/ComponentInventory.scala b/li/cil/oc/common/tileentity/ComponentInventory.scala index 26754dc8f..cf0f8e23f 100644 --- a/li/cil/oc/common/tileentity/ComponentInventory.scala +++ b/li/cil/oc/common/tileentity/ComponentInventory.scala @@ -13,12 +13,16 @@ import net.minecraft.world.World import li.cil.oc.Items trait ComponentInventory extends IInventory with Node { - protected val inventory = new Array[ItemStack](8) + protected val inventory = new Array[ItemStack](inventorySize) + + protected val itemComponents = Array.fill[Option[Node]](inventorySize)(None) protected val computer: component.Computer def world: World + def inventorySize = 8 + def installedMemory = inventory.foldLeft(0)((sum, stack) => sum + (Registry.driverFor(stack) match { case Some(driver) if driver.slot(stack) == Slot.RAM => Items.multi.subItem(stack) match { case Some(ram: item.Memory) => ram.kiloBytes * 1024 @@ -36,6 +40,10 @@ trait ComponentInventory extends IInventory with Node { if (slot >= 0 && slot < inventory.length) { inventory(slot) = ItemStack.loadItemStackFromNBT( slotNbt.getCompoundTag("item")) + itemComponents(slot) = Registry.driverFor(inventory(slot)) match { + case None => None + case Some(driver) => driver.node(inventory(slot)) + } } } } @@ -49,6 +57,14 @@ trait ComponentInventory extends IInventory with Node { case (stack, slot) => { val slotNbt = new NBTTagCompound slotNbt.setByte("slot", slot.toByte) + + itemComponents(slot) match { + case None => // Nothing special to save. + case Some(node) => + // We're guaranteed to have a driver for entries. + node.save(Registry.driverFor(stack).get.nbt(stack)) + } + val itemNbt = new NBTTagCompound stack.writeToNBT(itemNbt) slotNbt.setCompoundTag("item", itemNbt) @@ -64,29 +80,14 @@ trait ComponentInventory extends IInventory with Node { override protected def onConnect() { super.onConnect() - for (slot <- 0 until inventory.length) { - itemNode(slot) match { - case None => // Ignore. - case Some(node) => - network.foreach(_.connect(this, node)) - } - } + for (node <- itemComponents.filter(_.isDefined).map(_.get)) + network.foreach(_.connect(this, node)) } override protected def onDisconnect() { super.onDisconnect() - for (slot <- 0 until inventory.length) { - itemNode(slot) match { - case None => // Ignore. - case Some(node) => - node.network.foreach(_.remove(node)) - } - } - } - - private def itemNode(slot: Int) = Registry.driverFor(inventory(slot)) match { - case None => None - case Some(driver) => driver.node(inventory(slot)) + for (node <- itemComponents.filter(_.isDefined).map(_.get)) + node.network.foreach(_.remove(node)) } // ----------------------------------------------------------------------- // @@ -122,9 +123,10 @@ trait ComponentInventory extends IInventory with Node { def setInventorySlotContents(slot: Int, item: ItemStack) = { // Uninstall component previously in that slot. - if (!world.isRemote) itemNode(slot) match { + if (!world.isRemote) itemComponents(slot) match { case None => // Nothing to do. case Some(node) => + itemComponents(slot) = None node.network.foreach(_.remove(node)) } @@ -132,10 +134,15 @@ trait ComponentInventory extends IInventory with Node { if (item != null && item.stackSize > getInventoryStackLimit) item.stackSize = getInventoryStackLimit - if (!world.isRemote) itemNode(slot) match { - case None => // Nothing to do. - case Some(node) => - network.foreach(_.connect(this, node)) + if (!world.isRemote) Registry.driverFor(inventory(slot)) match { + case None => // No driver. + case Some(driver) => + driver.node(inventory(slot)) match { + case None => // No node. + case Some(node) => + itemComponents(slot) = Some(node) + network.foreach(_.connect(this, node)) + } } computer.recomputeMemory() @@ -147,7 +154,7 @@ trait ComponentInventory extends IInventory with Node { case (1 | 2 | 3, Some(driver)) => driver.slot(item) == Slot.PCI case (4 | 5, Some(driver)) => driver.slot(item) == Slot.RAM case (6 | 7, Some(driver)) => driver.slot(item) == Slot.HDD - case (_, Some(_)) => false // Invalid slot. + case _ => false // Invalid slot. } def isInvNameLocalized = false diff --git a/li/cil/oc/server/component/Computer.scala b/li/cil/oc/server/component/Computer.scala index 2e68c059f..24fce2aea 100644 --- a/li/cil/oc/server/component/Computer.scala +++ b/li/cil/oc/server/component/Computer.scala @@ -244,6 +244,11 @@ class Computer(val owner: Computer.Environment) extends component.Computer with OpenComputers.log.warning("Out of memory!") // TODO remove this when we have a component that can display crash messages owner.network.foreach(_.sendToAll(owner, "computer.crashed", "not enough memory")) close() + case e: java.lang.Error if e.getMessage == "not enough memory" => + OpenComputers.log.warning("Out of memory!") // TODO remove this when we have a component that can display crash messages + // TODO get this to the world as a computer.crashed message. problem: synchronizing it. + owner.network.foreach(_.sendToAll(owner, "computer.crashed", "not enough memory")) + close() case e: Throwable => { OpenComputers.log.log(Level.WARNING, "Faulty Lua implementation for synchronized calls.", e) close() diff --git a/li/cil/oc/server/component/RedstoneCard.scala b/li/cil/oc/server/component/RedstoneCard.scala index 94cc9f17a..9a9a778a6 100644 --- a/li/cil/oc/server/component/RedstoneCard.scala +++ b/li/cil/oc/server/component/RedstoneCard.scala @@ -3,7 +3,6 @@ package li.cil.oc.server.component import li.cil.oc.api.Network import li.cil.oc.api.network.Message import li.cil.oc.api.network.Node -import net.minecraft.nbt.NBTTagCompound import net.minecraft.tileentity.TileEntity import net.minecraftforge.common.ForgeDirection diff --git a/li/cil/oc/server/driver/Disk.scala b/li/cil/oc/server/driver/Disk.scala index 373689eae..298798285 100644 --- a/li/cil/oc/server/driver/Disk.scala +++ b/li/cil/oc/server/driver/Disk.scala @@ -12,5 +12,5 @@ object Disk extends driver.Item { override def slot(item: ItemStack) = Slot.HDD - override def node(item: ItemStack) = null + override def node(item: ItemStack) = None } \ No newline at end of file diff --git a/li/cil/oc/server/driver/GraphicsCard.scala b/li/cil/oc/server/driver/GraphicsCard.scala index e8817a07a..b5b54c1f1 100644 --- a/li/cil/oc/server/driver/GraphicsCard.scala +++ b/li/cil/oc/server/driver/GraphicsCard.scala @@ -1,7 +1,7 @@ package li.cil.oc.server.driver import li.cil.oc.api.driver.Slot -import li.cil.oc.api.{Persistable, driver} +import li.cil.oc.api.driver import li.cil.oc.server.component import li.cil.oc.{Config, Items} import net.minecraft.item.ItemStack @@ -13,5 +13,9 @@ object GraphicsCard extends driver.Item { override def slot(item: ItemStack) = Slot.PCI - override def node(item: ItemStack) = Persistable.get[component.GraphicsCard](item) + override def node(item: ItemStack) = { + val instance = new component.GraphicsCard() + instance.load(nbt(item)) + Some(instance) + } } \ No newline at end of file diff --git a/li/cil/oc/server/driver/Redstone.scala b/li/cil/oc/server/driver/Redstone.scala index 3ebe909fb..015b42339 100644 --- a/li/cil/oc/server/driver/Redstone.scala +++ b/li/cil/oc/server/driver/Redstone.scala @@ -1,7 +1,7 @@ package li.cil.oc.server.driver import li.cil.oc.api.driver.Slot -import li.cil.oc.api.{Persistable, driver} +import li.cil.oc.api.driver import li.cil.oc.server.component import li.cil.oc.{Config, Items} import net.minecraft.item.ItemStack @@ -13,5 +13,9 @@ object Redstone extends driver.Item { override def slot(item: ItemStack) = Slot.PCI - override def node(item: ItemStack) = Persistable.get[component.RedstoneCard](item) + override def node(item: ItemStack) = { + val instance = new component.RedstoneCard() + instance.load(nbt(item)) + Some(instance) + } }