diff --git a/src/main/java/li/cil/oc/api/machine/Owner.java b/src/main/java/li/cil/oc/api/machine/Owner.java index 506e396bc..8a7d6dfc7 100644 --- a/src/main/java/li/cil/oc/api/machine/Owner.java +++ b/src/main/java/li/cil/oc/api/machine/Owner.java @@ -12,6 +12,21 @@ import net.minecraft.world.World; * running in, to allow querying the time of day, for example. */ public interface Owner extends Context { + /** + * The X coordinate of this machine owner in the world, in block coordinates. + */ + int x(); + + /** + * The Y coordinate of this machine owner in the world, in block coordinates. + */ + int y(); + + /** + * The Z coordinate of this machine owner in the world, in block coordinates. + */ + int z(); + /** * The world the machine is running in, e.g. if the owner is a tile entity * this is the world the tile entity lives in. diff --git a/src/main/java/li/cil/oc/api/package-info.java b/src/main/java/li/cil/oc/api/package-info.java index c3e1cf8c9..73dfe65ea 100644 --- a/src/main/java/li/cil/oc/api/package-info.java +++ b/src/main/java/li/cil/oc/api/package-info.java @@ -37,5 +37,5 @@ @cpw.mods.fml.common.API( owner = "OpenComputers|Core", provides = "OpenComputersAPI", - apiVersion = "1.4.9") + apiVersion = "1.4.10") package li.cil.oc.api; \ No newline at end of file diff --git a/src/main/scala/li/cil/oc/common/Proxy.scala b/src/main/scala/li/cil/oc/common/Proxy.scala index 5fd25a2d9..697e79217 100644 --- a/src/main/scala/li/cil/oc/common/Proxy.scala +++ b/src/main/scala/li/cil/oc/common/Proxy.scala @@ -78,5 +78,6 @@ class Proxy { FMLCommonHandler.instance().bus().register(EventHandler) FMLCommonHandler.instance().bus().register(SimpleComponentTickHandler.Instance) MinecraftForge.EVENT_BUS.register(WirelessNetwork) + MinecraftForge.EVENT_BUS.register(SaveHandler) } } \ No newline at end of file diff --git a/src/main/scala/li/cil/oc/common/SaveHandler.scala b/src/main/scala/li/cil/oc/common/SaveHandler.scala new file mode 100644 index 000000000..ddc4873c5 --- /dev/null +++ b/src/main/scala/li/cil/oc/common/SaveHandler.scala @@ -0,0 +1,83 @@ +package li.cil.oc.common + +import java.io +import java.util.logging.Level +import li.cil.oc.{OpenComputers, Settings} +import net.minecraft.nbt.NBTTagCompound +import net.minecraft.world.ChunkCoordIntPair +import net.minecraftforge.common.DimensionManager +import net.minecraftforge.event.ForgeSubscribe +import net.minecraftforge.event.world.WorldEvent +import scala.collection.mutable + +// TODO Save all data to an NBT compound and save it as a single file, to improve file I/O performance. +object SaveHandler { + val saveData = mutable.Map.empty[ChunkCoordIntPair, mutable.Map[String, Array[Byte]]] + + var cachedNbt = new NBTTagCompound() + + def savePath = new io.File(DimensionManager.getCurrentSaveRootDirectory, Settings.savePath + "state") + + def scheduleSave(chunk: ChunkCoordIntPair, name: String, data: Array[Byte]) = saveData.synchronized { + if (chunk == null) OpenComputers.log.warning("Cannot save machines with non tile entity owners.") + else saveData.getOrElseUpdate(chunk, mutable.Map.empty[String, Array[Byte]]) += name -> data + } + + def load(chunk: ChunkCoordIntPair, name: String): Array[Byte] = { + if (chunk == null) null + else { + val path = savePath + val chunkPath = new io.File(path, s"${chunk.chunkXPos}.${chunk.chunkZPos}") + val file = new io.File(chunkPath, name) + try { + // val bis = new io.BufferedInputStream(new GZIPInputStream(new io.FileInputStream(file))) + val bis = new io.BufferedInputStream(new io.FileInputStream(file)) + val bos = new io.ByteArrayOutputStream + val buffer = new Array[Byte](8 * 1024) + var read = 0 + do { + read = bis.read(buffer) + if (read > 0) { + bos.write(buffer, 0, read) + } + } while (read >= 0) + bis.close() + bos.toByteArray + } + catch { + case e: io.IOException => + OpenComputers.log.log(Level.WARNING, "Error loading auxiliary tile entity data.", e) + Array.empty[Byte] + } + } + } + + // Used by the native lua state to store kernel and stack data in auxiliary + // files instead of directly in the tile entity data, avoiding potential + // problems with the tile entity data becoming too large. + @ForgeSubscribe + def onWorldSave(e: WorldEvent.Save) = saveData.synchronized { + val path = savePath + path.mkdirs() + for ((chunk, entries) <- saveData) { + val chunkPath = new io.File(path, s"${chunk.chunkXPos}.${chunk.chunkZPos}") + chunkPath.mkdirs() + if (chunkPath.exists && chunkPath.isDirectory) { + for (file <- chunkPath.listFiles()) file.delete() + } + for ((name, data) <- entries) { + val file = new io.File(chunkPath, name) + try { + // val fos = new GZIPOutputStream(new io.FileOutputStream(file)) + val fos = new io.BufferedOutputStream(new io.FileOutputStream(file)) + fos.write(data) + fos.close() + } + catch { + case e: io.IOException => OpenComputers.log.log(Level.WARNING, s"Error saving auxiliary tile entity data to '${file.getAbsolutePath}.", e) + } + } + } + saveData.clear() + } +} diff --git a/src/main/scala/li/cil/oc/common/tileentity/Computer.scala b/src/main/scala/li/cil/oc/common/tileentity/Computer.scala index fe51b34c7..a28aa8896 100644 --- a/src/main/scala/li/cil/oc/common/tileentity/Computer.scala +++ b/src/main/scala/li/cil/oc/common/tileentity/Computer.scala @@ -143,12 +143,22 @@ trait Computer extends Environment with ComponentInventory with Rotatable with B override def readFromNBT(nbt: NBTTagCompound) { super.readFromNBT(nbt) + // God, this is so ugly... will need to rework the robot architecture. + // This is required for loading auxiliary data (kernel state), because the + // coordinates in the actual robot won't be set properly, otherwise. + this match { + case proxy: RobotProxy => + proxy.robot.xCoord = xCoord + proxy.robot.yCoord = yCoord + proxy.robot.zCoord = zCoord + case _ => + } computer.load(nbt.getCompoundTag(Settings.namespace + "computer")) } override def writeToNBT(nbt: NBTTagCompound) { super.writeToNBT(nbt) - if (!new Exception().getStackTrace.exists(_.getClassName.startsWith("mcp.mobius.waila"))) { + if (computer != null) { nbt.setNewCompoundTag(Settings.namespace + "computer", computer.save) } } diff --git a/src/main/scala/li/cil/oc/common/tileentity/Rack.scala b/src/main/scala/li/cil/oc/common/tileentity/Rack.scala index 39851d093..ba45aa5b6 100644 --- a/src/main/scala/li/cil/oc/common/tileentity/Rack.scala +++ b/src/main/scala/li/cil/oc/common/tileentity/Rack.scala @@ -266,15 +266,13 @@ class Rack extends PowerAcceptor with Hub with PowerBalancer with Inventory with // Side check for Waila (and other mods that may call this client side). override def writeToNBT(nbt: NBTTagCompound) = if (isServer) { - if (!new Exception().getStackTrace.exists(_.getClassName.startsWith("mcp.mobius.waila"))) { - nbt.setNewTagList(Settings.namespace + "servers", servers map { - case Some(server) => - val serverNbt = new NBTTagCompound() - server.save(serverNbt) - serverNbt - case _ => new NBTTagCompound() - }) - } + nbt.setNewTagList(Settings.namespace + "servers", servers map { + case Some(server) => + val serverNbt = new NBTTagCompound() + server.save(serverNbt) + serverNbt + case _ => new NBTTagCompound() + }) super.writeToNBT(nbt) nbt.setByteArray(Settings.namespace + "sides", sides.map(_.ordinal.toByte)) nbt.setNewTagList(Settings.namespace + "terminals", terminals.map(t => { diff --git a/src/main/scala/li/cil/oc/server/component/Server.scala b/src/main/scala/li/cil/oc/server/component/Server.scala index a3eb1ffbe..e552489e3 100644 --- a/src/main/scala/li/cil/oc/server/component/Server.scala +++ b/src/main/scala/li/cil/oc/server/component/Server.scala @@ -70,6 +70,12 @@ class Server(val rack: tileentity.Rack, val number: Int) extends Owner { case _ => false } + override def x = rack.x + + override def y = rack.y + + override def z = rack.z + override def world = rack.world override def markAsChanged() = rack.markAsChanged() diff --git a/src/main/scala/li/cil/oc/server/component/machine/LuaJLuaArchitecture.scala b/src/main/scala/li/cil/oc/server/component/machine/LuaJLuaArchitecture.scala index 60d6f54f7..71b95222f 100644 --- a/src/main/scala/li/cil/oc/server/component/machine/LuaJLuaArchitecture.scala +++ b/src/main/scala/li/cil/oc/server/component/machine/LuaJLuaArchitecture.scala @@ -247,7 +247,7 @@ class LuaJLuaArchitecture(val machine: api.machine.Machine) extends Architecture // Are we a robot? (No this is not a CAPTCHA.) // TODO deprecate this - computer.set("isRobot", (_: Varargs) => LuaValue.valueOf(machine.components.contains("robot"))) + computer.set("isRobot", (_: Varargs) => LuaValue.valueOf(machine.components.containsValue("robot"))) computer.set("freeMemory", (_: Varargs) => LuaValue.valueOf(memory / 2)) diff --git a/src/main/scala/li/cil/oc/server/component/machine/NativeLuaArchitecture.scala b/src/main/scala/li/cil/oc/server/component/machine/NativeLuaArchitecture.scala index 69014fe64..6fc7996fe 100644 --- a/src/main/scala/li/cil/oc/server/component/machine/NativeLuaArchitecture.scala +++ b/src/main/scala/li/cil/oc/server/component/machine/NativeLuaArchitecture.scala @@ -6,11 +6,14 @@ import java.io.{IOException, FileNotFoundException} import java.util.logging.Level import li.cil.oc.api.machine.{Architecture, LimitReachedException, ExecutionResult} import li.cil.oc.api.network.ComponentConnector +import li.cil.oc.common.SaveHandler +import li.cil.oc.server.component import li.cil.oc.util.ExtendedLuaState.extendLuaState import li.cil.oc.util.{GameTimeFormatter, LuaStateFactory} import li.cil.oc.{api, OpenComputers, server, Settings} import net.minecraft.nbt.NBTTagCompound -import scala.Some +import net.minecraft.tileentity.TileEntity +import net.minecraft.world.ChunkCoordIntPair import scala.collection.convert.WrapAsScala._ import scala.collection.mutable @@ -295,7 +298,7 @@ class NativeLuaArchitecture(val machine: api.machine.Machine) extends Architectu // Are we a robot? (No this is not a CAPTCHA.) // TODO deprecate this lua.pushScalaFunction(lua => { - lua.pushBoolean(machine.components.contains("robot")) + lua.pushBoolean(machine.components.containsValue("robot")) 1 }) lua.setField(-2, "isRobot") @@ -595,14 +598,20 @@ class NativeLuaArchitecture(val machine: api.machine.Machine) extends Architectu // on. First, clear the stack, meaning the current kernel. lua.setTop(0) - unpersist(nbt.getByteArray("kernel")) + val kernel = + if (nbt.hasKey("kernel")) nbt.getByteArray("kernel") + else SaveHandler.load(new ChunkCoordIntPair(machine.owner.x >> 4, machine.owner.z >> 4), node.address + "_kernel") + unpersist(kernel) if (!lua.isThread(1)) { // This shouldn't really happen, but there's a chance it does if // the save was corrupt (maybe someone modified the Lua files). throw new IllegalArgumentException("Invalid kernel.") } if (state.contains(Machine.State.SynchronizedCall) || state.contains(Machine.State.SynchronizedReturn)) { - unpersist(nbt.getByteArray("stack")) + val stack = + if (nbt.hasKey("stack")) nbt.getByteArray("stack") + else SaveHandler.load(new ChunkCoordIntPair(machine.owner.x >> 4, machine.owner.z >> 4), node.address + "_stack") + unpersist(stack) if (!(if (state.contains(Machine.State.SynchronizedCall)) lua.isFunction(2) else lua.isTable(2))) { // Same as with the above, should not really happen normally, but // could for the same reasons. @@ -629,12 +638,12 @@ class NativeLuaArchitecture(val machine: api.machine.Machine) extends Architectu // Try persisting Lua, because that's what all of the rest depends on. // Save the kernel state (which is always at stack index one). assert(lua.isThread(1)) - nbt.setByteArray("kernel", persist(1)) + SaveHandler.scheduleSave(new ChunkCoordIntPair(machine.owner.x >> 4, machine.owner.z >> 4), node.address + "_kernel", persist(1)) // While in a driver call we have one object on the global stack: either // the function to call the driver with, or the result of the call. if (state.contains(Machine.State.SynchronizedCall) || state.contains(Machine.State.SynchronizedReturn)) { assert(if (state.contains(Machine.State.SynchronizedCall)) lua.isFunction(2) else lua.isTable(2)) - nbt.setByteArray("stack", persist(2)) + SaveHandler.scheduleSave(new ChunkCoordIntPair(machine.owner.x >> 4, machine.owner.z >> 4), node.address + "_stack", persist(2)) } nbt.setInteger("kernelMemory", math.ceil(kernelMemory / ramScale).toInt)