From f9c4306692bbc6333dcacb3e9063647047b25656 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20N=C3=BCcke?= Date: Sun, 16 Mar 2014 06:56:38 +0100 Subject: [PATCH 1/3] fixed component.isRobot (tho that's deprecated anyway!); reworked machine state saving - kernel (and stack, if necessary) are now saved in extra files instead of in the tile entity's nbt tag. if the data gets too bit, minecraft apparently silently fails in saving the chunk... yay. the kernel data is dumped uncompressed to saves/[world]/opencomputers/[chunkX].[chunkY]/[node_address]_kernel - uncompressed because gzipping makes saving noticeably slower. this also allows removing the waila check in the save in case people still have a very old version of it. --- src/main/scala/li/cil/oc/common/Proxy.scala | 1 + .../scala/li/cil/oc/common/SaveHandler.scala | 83 +++++++++++++++++++ .../cil/oc/common/tileentity/Computer.scala | 12 ++- .../li/cil/oc/common/tileentity/Rack.scala | 16 ++-- .../machine/LuaJLuaArchitecture.scala | 2 +- .../machine/NativeLuaArchitecture.scala | 36 ++++++-- 6 files changed, 133 insertions(+), 17 deletions(-) create mode 100644 src/main/scala/li/cil/oc/common/SaveHandler.scala diff --git a/src/main/scala/li/cil/oc/common/Proxy.scala b/src/main/scala/li/cil/oc/common/Proxy.scala index aa3020558..adddd903a 100644 --- a/src/main/scala/li/cil/oc/common/Proxy.scala +++ b/src/main/scala/li/cil/oc/common/Proxy.scala @@ -79,5 +79,6 @@ class Proxy { GameRegistry.registerPlayerTracker(Keyboard) NetworkRegistry.instance.registerConnectionHandler(ConnectionHandler) 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 1baf448e9..d3ce5357e 100644 --- a/src/main/scala/li/cil/oc/common/tileentity/Computer.scala +++ b/src/main/scala/li/cil/oc/common/tileentity/Computer.scala @@ -142,12 +142,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 eb9f24dff..3e5ede36d 100644 --- a/src/main/scala/li/cil/oc/common/tileentity/Rack.scala +++ b/src/main/scala/li/cil/oc/common/tileentity/Rack.scala @@ -267,15 +267,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/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..bc1f5daa1 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,13 @@ 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.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 +297,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 +597,26 @@ 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 machine.owner match { + case t: TileEntity => SaveHandler.load(new ChunkCoordIntPair(t.xCoord >> 4, t.zCoord >> 4), node.address + "_kernel") + case _ => SaveHandler.load(null, 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 machine.owner match { + case t: TileEntity => SaveHandler.load(new ChunkCoordIntPair(t.xCoord >> 4, t.zCoord >> 4), node.address + "_stack") + case _ => SaveHandler.load(null, 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 +643,22 @@ 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)) + machine.owner match { + case t: TileEntity => + SaveHandler.scheduleSave(new ChunkCoordIntPair(t.xCoord >> 4, t.zCoord >> 4), node.address + "_kernel", persist(1)) + case _ => + SaveHandler.scheduleSave(null, 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)) + machine.owner match { + case t: TileEntity => + SaveHandler.scheduleSave(new ChunkCoordIntPair(t.xCoord >> 4, t.zCoord >> 4), node.address + "_stack", persist(2)) + case _ => + SaveHandler.scheduleSave(null, node.address + "_stack", persist(1)) + } } nbt.setInteger("kernelMemory", math.ceil(kernelMemory / ramScale).toInt) From b5da2832049f45c9a1a3f82dae8249b67b3fb404 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20N=C3=BCcke?= Date: Sun, 16 Mar 2014 07:25:51 +0100 Subject: [PATCH 2/3] fixed saving for server racks in new kernel save system --- .../server/component/machine/NativeLuaArchitecture.scala | 7 +++++++ 1 file changed, 7 insertions(+) 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 bc1f5daa1..265f8b095 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 @@ -7,6 +7,7 @@ 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} @@ -601,6 +602,7 @@ class NativeLuaArchitecture(val machine: api.machine.Machine) extends Architectu if (nbt.hasKey("kernel")) nbt.getByteArray("kernel") else machine.owner match { case t: TileEntity => SaveHandler.load(new ChunkCoordIntPair(t.xCoord >> 4, t.zCoord >> 4), node.address + "_kernel") + case t: component.Server => SaveHandler.load(new ChunkCoordIntPair(t.rack.xCoord >> 4, t.rack.zCoord >> 4), node.address + "_kernel") case _ => SaveHandler.load(null, node.address + "_kernel") } unpersist(kernel) @@ -614,6 +616,7 @@ class NativeLuaArchitecture(val machine: api.machine.Machine) extends Architectu if (nbt.hasKey("stack")) nbt.getByteArray("stack") else machine.owner match { case t: TileEntity => SaveHandler.load(new ChunkCoordIntPair(t.xCoord >> 4, t.zCoord >> 4), node.address + "_stack") + case t: component.Server => SaveHandler.load(new ChunkCoordIntPair(t.rack.xCoord >> 4, t.rack.zCoord >> 4), node.address + "_stack") case _ => SaveHandler.load(null, node.address + "_stack") } unpersist(stack) @@ -646,6 +649,8 @@ class NativeLuaArchitecture(val machine: api.machine.Machine) extends Architectu machine.owner match { case t: TileEntity => SaveHandler.scheduleSave(new ChunkCoordIntPair(t.xCoord >> 4, t.zCoord >> 4), node.address + "_kernel", persist(1)) + case t: component.Server => + SaveHandler.scheduleSave(new ChunkCoordIntPair(t.rack.xCoord >> 4, t.rack.zCoord >> 4), node.address + "_kernel", persist(1)) case _ => SaveHandler.scheduleSave(null, node.address + "_kernel", persist(1)) } @@ -656,6 +661,8 @@ class NativeLuaArchitecture(val machine: api.machine.Machine) extends Architectu machine.owner match { case t: TileEntity => SaveHandler.scheduleSave(new ChunkCoordIntPair(t.xCoord >> 4, t.zCoord >> 4), node.address + "_stack", persist(2)) + case t: component.Server => + SaveHandler.scheduleSave(new ChunkCoordIntPair(t.rack.xCoord >> 4, t.rack.zCoord >> 4), node.address + "_stack", persist(2)) case _ => SaveHandler.scheduleSave(null, node.address + "_stack", persist(1)) } From 067664dd3e3866c62a808335389666d5d33ad736 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20N=C3=BCcke?= Date: Sun, 16 Mar 2014 07:36:53 +0100 Subject: [PATCH 3/3] added position to owner interface in the API to make machine saving more generic --- .../java/li/cil/oc/api/machine/Owner.java | 15 ++++++++++ src/main/java/li/cil/oc/api/package-info.java | 2 +- .../li/cil/oc/server/component/Server.scala | 6 ++++ .../machine/NativeLuaArchitecture.scala | 30 +++---------------- 4 files changed, 26 insertions(+), 27 deletions(-) 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/server/component/Server.scala b/src/main/scala/li/cil/oc/server/component/Server.scala index ba00da008..1903e14c9 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/NativeLuaArchitecture.scala b/src/main/scala/li/cil/oc/server/component/machine/NativeLuaArchitecture.scala index 265f8b095..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 @@ -600,11 +600,7 @@ class NativeLuaArchitecture(val machine: api.machine.Machine) extends Architectu val kernel = if (nbt.hasKey("kernel")) nbt.getByteArray("kernel") - else machine.owner match { - case t: TileEntity => SaveHandler.load(new ChunkCoordIntPair(t.xCoord >> 4, t.zCoord >> 4), node.address + "_kernel") - case t: component.Server => SaveHandler.load(new ChunkCoordIntPair(t.rack.xCoord >> 4, t.rack.zCoord >> 4), node.address + "_kernel") - case _ => SaveHandler.load(null, node.address + "_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 @@ -614,11 +610,7 @@ class NativeLuaArchitecture(val machine: api.machine.Machine) extends Architectu if (state.contains(Machine.State.SynchronizedCall) || state.contains(Machine.State.SynchronizedReturn)) { val stack = if (nbt.hasKey("stack")) nbt.getByteArray("stack") - else machine.owner match { - case t: TileEntity => SaveHandler.load(new ChunkCoordIntPair(t.xCoord >> 4, t.zCoord >> 4), node.address + "_stack") - case t: component.Server => SaveHandler.load(new ChunkCoordIntPair(t.rack.xCoord >> 4, t.rack.zCoord >> 4), node.address + "_stack") - case _ => SaveHandler.load(null, node.address + "_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 @@ -646,26 +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)) - machine.owner match { - case t: TileEntity => - SaveHandler.scheduleSave(new ChunkCoordIntPair(t.xCoord >> 4, t.zCoord >> 4), node.address + "_kernel", persist(1)) - case t: component.Server => - SaveHandler.scheduleSave(new ChunkCoordIntPair(t.rack.xCoord >> 4, t.rack.zCoord >> 4), node.address + "_kernel", persist(1)) - case _ => - SaveHandler.scheduleSave(null, node.address + "_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)) - machine.owner match { - case t: TileEntity => - SaveHandler.scheduleSave(new ChunkCoordIntPair(t.xCoord >> 4, t.zCoord >> 4), node.address + "_stack", persist(2)) - case t: component.Server => - SaveHandler.scheduleSave(new ChunkCoordIntPair(t.rack.xCoord >> 4, t.rack.zCoord >> 4), node.address + "_stack", persist(2)) - case _ => - SaveHandler.scheduleSave(null, node.address + "_stack", persist(1)) - } + 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)