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) + } }