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)

This commit is contained in:
Florian Nücke 2013-10-01 10:03:01 +02:00
parent 814f49b0ec
commit 1da52f8403
13 changed files with 80 additions and 214 deletions

View File

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

View File

@ -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.
* <p/>
* 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.
* <p/>
* 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.
* <p/>
* 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.
* <p/>
* 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.
* <p/>
* 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
}

View File

@ -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.
* <p/>
* 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.
* <p/>
* 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")
}
}

View File

@ -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 = {

View File

@ -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)

View File

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

View File

@ -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)

View File

@ -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

View File

@ -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()

View File

@ -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

View File

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

View File

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

View File

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