Florian Nücke | bb491ec | 2013-09-29 14:15:40 +0200 | [diff] [blame] | 1 | package li.cil.oc.server.component |
Florian Nücke | 61f53b9 | 2013-08-20 21:03:38 +0200 | [diff] [blame] | 2 | |
Florian Nücke | 1454512 | 2013-10-24 21:39:21 +0200 | [diff] [blame] | 3 | import com.naef.jnlua._ |
Florian Nücke | af286d1 | 2013-10-27 01:37:34 +0200 | [diff] [blame] | 4 | import java.io.{FileNotFoundException, IOException} |
Florian Nücke | 4dfa9ab | 2013-09-25 17:24:40 +0200 | [diff] [blame] | 5 | import java.util.logging.Level |
Florian Nücke | 90bbdc2 | 2013-10-02 15:11:49 +0200 | [diff] [blame] | 6 | import li.cil.oc.api |
Florian Nücke | 730c8f0 | 2013-11-01 01:12:55 +0100 | [diff] [blame] | 7 | import li.cil.oc.api.network._ |
Florian Nücke | c1fd2c0 | 2013-09-29 14:57:05 +0200 | [diff] [blame] | 8 | import li.cil.oc.common.tileentity |
Florian Nücke | 730c8f0 | 2013-11-01 01:12:55 +0100 | [diff] [blame] | 9 | import li.cil.oc.server |
Florian Nücke | cbf1760 | 2013-12-01 17:16:58 +0100 | [diff] [blame] | 10 | import li.cil.oc.server.PacketSender |
Florian Nücke | c1fd2c0 | 2013-09-29 14:57:05 +0200 | [diff] [blame] | 11 | import li.cil.oc.util.ExtendedLuaState.extendLuaState |
Florian Nücke | f135d86 | 2013-11-17 12:54:58 +0100 | [diff] [blame] | 12 | import li.cil.oc.util.ExtendedNBT._ |
Florian Nücke | ac5fe3c | 2013-11-25 21:54:59 +0100 | [diff] [blame] | 13 | import li.cil.oc.util.{ThreadPoolFactory, GameTimeFormatter, LuaStateFactory} |
| 14 | import li.cil.oc.{OpenComputers, Settings} |
Florian Nücke | 6e1ba98 | 2013-11-16 19:51:56 +0100 | [diff] [blame] | 15 | import net.minecraft.entity.player.EntityPlayer |
Florian Nücke | 4e820da | 2013-08-29 22:34:41 +0200 | [diff] [blame] | 16 | import net.minecraft.nbt._ |
Florian Nücke | bccee0c | 2013-11-08 14:08:01 +0100 | [diff] [blame] | 17 | import net.minecraft.server.MinecraftServer |
Florian Nücke | 9ab4c5c | 2013-12-15 16:18:16 +0100 | [diff] [blame] | 18 | import net.minecraft.server.integrated.IntegratedServer |
Florian Nücke | 0930f89 | 2013-09-23 01:30:57 +0200 | [diff] [blame] | 19 | import scala.Array.canBuildFrom |
Florian Nücke | 4dfa9ab | 2013-09-25 17:24:40 +0200 | [diff] [blame] | 20 | import scala.Some |
Florian Nücke | af286d1 | 2013-10-27 01:37:34 +0200 | [diff] [blame] | 21 | import scala.collection.convert.WrapAsScala._ |
| 22 | import scala.collection.mutable |
Florian Nücke | 1fd375f | 2013-10-29 22:43:42 +0100 | [diff] [blame] | 23 | import scala.math.ScalaNumber |
Florian Nücke | eb3a8e1 | 2013-11-07 03:30:17 +0100 | [diff] [blame] | 24 | import scala.runtime.BoxedUnit |
Florian Nücke | 61f53b9 | 2013-08-20 21:03:38 +0200 | [diff] [blame] | 25 | |
Florian Nücke | f135d86 | 2013-11-17 12:54:58 +0100 | [diff] [blame] | 26 | class Computer(val owner: tileentity.Computer) extends ManagedComponent with Context with Runnable { |
Florian Nücke | 6e1ba98 | 2013-11-16 19:51:56 +0100 | [diff] [blame] | 27 | val node = api.Network.newNode(this, Visibility.Network). |
| 28 | withComponent("computer", Visibility.Neighbors). |
Florian Nücke | 0a83286 | 2013-12-11 00:49:06 +0100 | [diff] [blame] | 29 | withConnector(if (isRobot) Settings.get.bufferRobot + 30 * Settings.get.bufferPerLevel else Settings.get.bufferComputer). |
Florian Nücke | 6e1ba98 | 2013-11-16 19:51:56 +0100 | [diff] [blame] | 30 | create() |
| 31 | |
Florian Nücke | ac72c27 | 2013-11-13 17:17:19 +0100 | [diff] [blame] | 32 | val rom = Option(api.FileSystem.asManagedEnvironment(api.FileSystem. |
Florian Nücke | ac5fe3c | 2013-11-25 21:54:59 +0100 | [diff] [blame] | 33 | fromClass(OpenComputers.getClass, Settings.resourceDomain, "lua/rom"), "rom")) |
Florian Nücke | ac72c27 | 2013-11-13 17:17:19 +0100 | [diff] [blame] | 34 | |
Florian Nücke | 083e7c2 | 2013-11-29 22:30:10 +0100 | [diff] [blame] | 35 | val tmp = if (Settings.get.tmpSize > 0) { |
| 36 | Option(api.FileSystem.asManagedEnvironment(api.FileSystem. |
| 37 | fromMemory(Settings.get.tmpSize * 1024), "tmpfs")) |
| 38 | } else None |
Florian Nücke | ac72c27 | 2013-11-13 17:17:19 +0100 | [diff] [blame] | 39 | |
Florian Nücke | bb7684a | 2013-11-07 00:25:46 +0100 | [diff] [blame] | 40 | private val state = mutable.Stack(Computer.State.Stopped) |
Florian Nücke | 0327cec | 2013-08-31 17:58:17 +0200 | [diff] [blame] | 41 | |
Florian Nücke | 0927f07 | 2013-10-03 22:33:48 +0200 | [diff] [blame] | 42 | private var lua: LuaState = null |
Florian Nücke | d24f726 | 2013-09-28 17:32:23 +0200 | [diff] [blame] | 43 | |
Florian Nücke | 0927f07 | 2013-10-03 22:33:48 +0200 | [diff] [blame] | 44 | private var kernelMemory = 0 |
| 45 | |
Florian Nücke | af286d1 | 2013-10-27 01:37:34 +0200 | [diff] [blame] | 46 | private val components = mutable.Map.empty[String, String] |
| 47 | |
Florian Nücke | fe446e9 | 2013-10-30 00:50:13 +0100 | [diff] [blame] | 48 | private val addedComponents = mutable.Set.empty[Component] |
| 49 | |
Florian Nücke | cbf1760 | 2013-12-01 17:16:58 +0100 | [diff] [blame] | 50 | private val _users = mutable.Set.empty[String] |
Florian Nücke | bccee0c | 2013-11-08 14:08:01 +0100 | [diff] [blame] | 51 | |
Florian Nücke | af286d1 | 2013-10-27 01:37:34 +0200 | [diff] [blame] | 52 | private val signals = new mutable.Queue[Computer.Signal] |
Florian Nücke | 9459366 | 2013-10-09 16:38:21 +0200 | [diff] [blame] | 53 | |
Florian Nücke | e286fb7 | 2013-11-15 18:20:25 +0100 | [diff] [blame] | 54 | private val callCounts = mutable.Map.empty[String, mutable.Map[String, Int]] |
| 55 | |
Florian Nücke | d1094c0 | 2013-12-09 22:01:22 +0100 | [diff] [blame] | 56 | private val ramScale = if (LuaStateFactory.is64Bit) Settings.get.ramScaleFor64Bit else 1.0 |
| 57 | |
Florian Nücke | 4e820da | 2013-08-29 22:34:41 +0200 | [diff] [blame] | 58 | // ----------------------------------------------------------------------- // |
Florian Nücke | 0927f07 | 2013-10-03 22:33:48 +0200 | [diff] [blame] | 59 | |
Florian Nücke | d363d03 | 2013-10-06 18:16:03 +0200 | [diff] [blame] | 60 | private var timeStarted = 0L // Game-world time [ms] for os.uptime(). |
Florian Nücke | 0927f07 | 2013-10-03 22:33:48 +0200 | [diff] [blame] | 61 | |
| 62 | private var worldTime = 0L // Game-world time for os.time(). |
| 63 | |
Florian Nücke | d363d03 | 2013-10-06 18:16:03 +0200 | [diff] [blame] | 64 | private var cpuTime = 0L // Pseudo-real-world time [ns] for os.clock(). |
| 65 | |
| 66 | private var cpuStart = 0L // Pseudo-real-world time [ns] for os.clock(). |
| 67 | |
Florian Nücke | 9ab4c5c | 2013-12-15 16:18:16 +0100 | [diff] [blame] | 68 | private var remainIdle = 0 // Ticks left to sleep before resuming. |
Florian Nücke | 0927f07 | 2013-10-03 22:33:48 +0200 | [diff] [blame] | 69 | |
Florian Nücke | 827344b | 2013-11-19 16:20:11 +0100 | [diff] [blame] | 70 | private var remainingPause = 0 // Ticks left to wait before resuming. |
| 71 | |
Florian Nücke | cbf1760 | 2013-12-01 17:16:58 +0100 | [diff] [blame] | 72 | private var usersChanged = false // Send updated users list to clients? |
| 73 | |
Florian Nücke | d363d03 | 2013-10-06 18:16:03 +0200 | [diff] [blame] | 74 | private var message: Option[String] = None // For error messages. |
Florian Nücke | 0927f07 | 2013-10-03 22:33:48 +0200 | [diff] [blame] | 75 | |
Florian Nücke | 4e820da | 2013-08-29 22:34:41 +0200 | [diff] [blame] | 76 | // ----------------------------------------------------------------------- // |
| 77 | |
Florian Nücke | d4c1934 | 2013-11-22 17:58:22 +0100 | [diff] [blame] | 78 | def recomputeMemory() = Option(lua) match { |
| 79 | case Some(l) => |
| 80 | l.setTotalMemory(Int.MaxValue) |
| 81 | l.gc(LuaState.GcAction.COLLECT, 0) |
| 82 | if (kernelMemory > 0) { |
Florian Nücke | d1094c0 | 2013-12-09 22:01:22 +0100 | [diff] [blame] | 83 | l.setTotalMemory(kernelMemory + math.ceil(owner.installedMemory * ramScale).toInt) |
Florian Nücke | d4c1934 | 2013-11-22 17:58:22 +0100 | [diff] [blame] | 84 | } |
| 85 | case _ => |
| 86 | } |
Florian Nücke | 41c1f40 | 2013-09-29 17:24:32 +0200 | [diff] [blame] | 87 | |
Florian Nücke | 39b2c97 | 2013-11-09 23:38:17 +0100 | [diff] [blame] | 88 | def lastError = message |
| 89 | |
Florian Nücke | cbf1760 | 2013-12-01 17:16:58 +0100 | [diff] [blame] | 90 | def users = _users.synchronized(_users.toArray) |
| 91 | |
Florian Nücke | ba1f66e | 2013-11-19 03:29:57 +0100 | [diff] [blame] | 92 | def isRobot = false |
| 93 | |
Florian Nücke | a845ce1 | 2013-12-11 13:25:34 +0100 | [diff] [blame] | 94 | private val cost = (if (isRobot) Settings.get.robotCost else Settings.get.computerCost) * Settings.get.tickFrequency |
Florian Nücke | 2de8622 | 2013-12-09 02:56:53 +0100 | [diff] [blame] | 95 | |
Florian Nücke | f9cd7fd | 2013-09-10 17:26:25 +0200 | [diff] [blame] | 96 | // ----------------------------------------------------------------------- // |
| 97 | |
Florian Nücke | 6e1ba98 | 2013-11-16 19:51:56 +0100 | [diff] [blame] | 98 | def address = node.address |
Florian Nücke | 0927f07 | 2013-10-03 22:33:48 +0200 | [diff] [blame] | 99 | |
Florian Nücke | 9af2023 | 2013-11-26 03:05:16 +0100 | [diff] [blame] | 100 | def canInteract(player: String) = !Settings.get.canComputersBeOwned || |
Florian Nücke | cbf1760 | 2013-12-01 17:16:58 +0100 | [diff] [blame] | 101 | _users.synchronized(_users.isEmpty || _users.contains(player)) || |
Florian Nücke | bccee0c | 2013-11-08 14:08:01 +0100 | [diff] [blame] | 102 | MinecraftServer.getServer.isSinglePlayer || |
| 103 | MinecraftServer.getServer.getConfigurationManager.isPlayerOpped(player) |
Florian Nücke | 827344b | 2013-11-19 16:20:11 +0100 | [diff] [blame] | 104 | |
| 105 | def isRunning = state.synchronized(state.top != Computer.State.Stopped && state.top != Computer.State.Stopping) |
| 106 | |
| 107 | def isPaused = state.synchronized(state.top == Computer.State.Paused && remainingPause > 0) |
| 108 | |
| 109 | def start() = state.synchronized(state.top match { |
| 110 | case Computer.State.Stopped if owner.installedMemory > 0 && init() => |
| 111 | switchTo(Computer.State.Starting) |
| 112 | timeStarted = owner.world.getWorldTime |
| 113 | node.sendToReachable("computer.started") |
| 114 | true |
| 115 | case Computer.State.Paused if remainingPause > 0 => |
| 116 | remainingPause = 0 |
| 117 | true |
| 118 | case Computer.State.Stopping => |
| 119 | switchTo(Computer.State.Restarting) |
| 120 | true |
| 121 | case _ => |
| 122 | false |
| 123 | }) |
| 124 | |
| 125 | def pause(seconds: Double): Boolean = { |
Florian Nücke | 407d251 | 2013-12-09 01:32:31 +0100 | [diff] [blame] | 126 | val ticksToPause = math.max((seconds * 20).toInt, 0) |
Florian Nücke | 827344b | 2013-11-19 16:20:11 +0100 | [diff] [blame] | 127 | def shouldPause(state: Computer.State.Value) = state match { |
| 128 | case Computer.State.Stopping | Computer.State.Stopped => false |
| 129 | case Computer.State.Paused if ticksToPause <= remainingPause => false |
| 130 | case _ => true |
| 131 | } |
| 132 | if (shouldPause(state.synchronized(state.top))) { |
| 133 | // Check again when we get the lock, might have changed since. |
| 134 | this.synchronized(state.synchronized(if (shouldPause(state.top)) { |
| 135 | if (state.top != Computer.State.Paused) { |
Florian Nücke | 5aa9ad4 | 2013-11-22 01:12:57 +0100 | [diff] [blame] | 136 | assert(!state.contains(Computer.State.Paused)) |
Florian Nücke | 827344b | 2013-11-19 16:20:11 +0100 | [diff] [blame] | 137 | state.push(Computer.State.Paused) |
| 138 | } |
| 139 | remainingPause = ticksToPause |
| 140 | owner.markAsChanged() |
| 141 | return true |
| 142 | })) |
| 143 | } |
| 144 | false |
| 145 | } |
| 146 | |
| 147 | def stop() = state.synchronized(state.top match { |
| 148 | case Computer.State.Stopped | Computer.State.Stopping => |
| 149 | false |
| 150 | case _ => |
| 151 | state.push(Computer.State.Stopping) |
| 152 | true |
| 153 | }) |
| 154 | |
| 155 | protected def crash(message: String) = { |
| 156 | this.message = Option(message) |
| 157 | stop() |
| 158 | } |
Florian Nücke | bccee0c | 2013-11-08 14:08:01 +0100 | [diff] [blame] | 159 | |
Florian Nücke | 6e1ba98 | 2013-11-16 19:51:56 +0100 | [diff] [blame] | 160 | def signal(name: String, args: AnyRef*) = state.synchronized(state.top match { |
Florian Nücke | 0927f07 | 2013-10-03 22:33:48 +0200 | [diff] [blame] | 161 | case Computer.State.Stopped | Computer.State.Stopping => false |
Florian Nücke | bb7684a | 2013-11-07 00:25:46 +0100 | [diff] [blame] | 162 | case _ => signals.synchronized { |
| 163 | if (signals.size >= 256) false |
| 164 | else { |
| 165 | signals.enqueue(new Computer.Signal(name, args.map { |
| 166 | case null | Unit | None => Unit |
| 167 | case arg: java.lang.Boolean => arg |
| 168 | case arg: java.lang.Byte => arg.toDouble |
| 169 | case arg: java.lang.Character => arg.toDouble |
| 170 | case arg: java.lang.Short => arg.toDouble |
| 171 | case arg: java.lang.Integer => arg.toDouble |
| 172 | case arg: java.lang.Long => arg.toDouble |
| 173 | case arg: java.lang.Float => arg.toDouble |
| 174 | case arg: java.lang.Double => arg |
| 175 | case arg: java.lang.String => arg |
| 176 | case arg: Array[Byte] => arg |
| 177 | case arg => |
| 178 | OpenComputers.log.warning("Trying to push signal with an unsupported argument of type " + arg.getClass.getName) |
| 179 | Unit |
| 180 | }.toArray)) |
| 181 | true |
| 182 | } |
| 183 | } |
Florian Nücke | 0927f07 | 2013-10-03 22:33:48 +0200 | [diff] [blame] | 184 | }) |
Florian Nücke | fb6b92b | 2013-11-28 02:52:42 +0100 | [diff] [blame] | 185 | |
Florian Nücke | bccee0c | 2013-11-08 14:08:01 +0100 | [diff] [blame] | 186 | // ----------------------------------------------------------------------- // |
Florian Nücke | 61f53b9 | 2013-08-20 21:03:38 +0200 | [diff] [blame] | 187 | |
Florian Nücke | 5f6a9cf | 2013-11-17 22:06:24 +0100 | [diff] [blame] | 188 | @LuaCallback("start") |
| 189 | def start(context: Context, args: Arguments): Array[AnyRef] = |
Florian Nücke | eee27e4 | 2013-12-07 15:15:38 +0100 | [diff] [blame] | 190 | result(!isPaused && start()) |
Florian Nücke | 5f6a9cf | 2013-11-17 22:06:24 +0100 | [diff] [blame] | 191 | |
| 192 | @LuaCallback("stop") |
| 193 | def stop(context: Context, args: Arguments): Array[AnyRef] = |
Florian Nücke | 827344b | 2013-11-19 16:20:11 +0100 | [diff] [blame] | 194 | result(stop()) |
Florian Nücke | 5f6a9cf | 2013-11-17 22:06:24 +0100 | [diff] [blame] | 195 | |
| 196 | @LuaCallback(value = "isRunning", direct = true) |
| 197 | def isRunning(context: Context, args: Arguments): Array[AnyRef] = |
Florian Nücke | 827344b | 2013-11-19 16:20:11 +0100 | [diff] [blame] | 198 | result(isRunning) |
Florian Nücke | 5f6a9cf | 2013-11-17 22:06:24 +0100 | [diff] [blame] | 199 | |
| 200 | // ----------------------------------------------------------------------- // |
| 201 | |
Florian Nücke | 2de8622 | 2013-12-09 02:56:53 +0100 | [diff] [blame] | 202 | override val canUpdate = true |
| 203 | |
Florian Nücke | 6c8210b | 2013-12-12 02:11:30 +0100 | [diff] [blame] | 204 | override def update() = if (state.synchronized(state.top != Computer.State.Stopped)) { |
Florian Nücke | fe446e9 | 2013-10-30 00:50:13 +0100 | [diff] [blame] | 205 | // Add components that were added since the last update to the actual list |
| 206 | // of components if we can see them. We use this delayed approach to avoid |
| 207 | // issues with components that have a visibility lower than their |
| 208 | // reachability, because in that case if they get connected in the wrong |
| 209 | // order we wouldn't add them (since they'd be invisible in their connect |
| 210 | // message, and only become visible with a later node-to-node connection, |
| 211 | // but that wouldn't trigger a connect message anymore due to the higher |
| 212 | // reachability). |
| 213 | processAddedComponents() |
| 214 | |
Florian Nücke | 2a48cb2 | 2013-11-07 17:02:25 +0100 | [diff] [blame] | 215 | // Update world time for time(). |
| 216 | worldTime = owner.world.getWorldTime |
Florian Nücke | 0927f07 | 2013-10-03 22:33:48 +0200 | [diff] [blame] | 217 | |
Florian Nücke | 827344b | 2013-11-19 16:20:11 +0100 | [diff] [blame] | 218 | // We can have rollbacks from '/time set'. Avoid getting negative uptimes. |
Florian Nücke | 407d251 | 2013-12-09 01:32:31 +0100 | [diff] [blame] | 219 | timeStarted = math.min(timeStarted, worldTime) |
Florian Nücke | 9ab4c5c | 2013-12-15 16:18:16 +0100 | [diff] [blame] | 220 | |
| 221 | if (remainIdle > 0) { |
| 222 | remainIdle -= 1 |
| 223 | } |
Florian Nücke | 827344b | 2013-11-19 16:20:11 +0100 | [diff] [blame] | 224 | |
| 225 | // Reset direct call limits. |
Florian Nücke | 407d251 | 2013-12-09 01:32:31 +0100 | [diff] [blame] | 226 | callCounts.synchronized(if (callCounts.size > 0) callCounts.clear()) |
Florian Nücke | e286fb7 | 2013-11-15 18:20:25 +0100 | [diff] [blame] | 227 | |
Florian Nücke | 6e1ba98 | 2013-11-16 19:51:56 +0100 | [diff] [blame] | 228 | // Make sure we have enough power. |
Florian Nücke | 2de8622 | 2013-12-09 02:56:53 +0100 | [diff] [blame] | 229 | if (worldTime % Settings.get.tickFrequency == 0) { |
Florian Nücke | 407d251 | 2013-12-09 01:32:31 +0100 | [diff] [blame] | 230 | state.synchronized(state.top match { |
| 231 | case Computer.State.Paused | |
| 232 | Computer.State.Restarting | |
| 233 | Computer.State.Stopping | |
| 234 | Computer.State.Stopped => // No power consumption. |
Florian Nücke | 9ab4c5c | 2013-12-15 16:18:16 +0100 | [diff] [blame] | 235 | case Computer.State.Sleeping if remainIdle > 0 && signals.isEmpty => |
Florian Nücke | 407d251 | 2013-12-09 01:32:31 +0100 | [diff] [blame] | 236 | if (!node.tryChangeBuffer(-cost * Settings.get.sleepCostFactor)) { |
| 237 | crash("not enough energy") |
| 238 | } |
| 239 | case _ => |
| 240 | if (!node.tryChangeBuffer(-cost)) { |
| 241 | crash("not enough energy") |
| 242 | } |
| 243 | }) |
| 244 | } |
Florian Nücke | cbf1760 | 2013-12-01 17:16:58 +0100 | [diff] [blame] | 245 | |
| 246 | // Avoid spamming user list across the network. |
| 247 | if (worldTime % 20 == 0 && usersChanged) { |
| 248 | val list = _users.synchronized { |
| 249 | usersChanged = false |
| 250 | users |
| 251 | } |
| 252 | PacketSender.sendComputerUserList(owner, list) |
| 253 | } |
Florian Nücke | 6e1ba98 | 2013-11-16 19:51:56 +0100 | [diff] [blame] | 254 | |
Florian Nücke | e286fb7 | 2013-11-15 18:20:25 +0100 | [diff] [blame] | 255 | // Check if we should switch states. These are all the states in which we're |
| 256 | // guaranteed that the executor thread isn't running anymore. |
Florian Nücke | bb7684a | 2013-11-07 00:25:46 +0100 | [diff] [blame] | 257 | state.synchronized(state.top match { |
| 258 | // Booting up. |
Florian Nücke | 08a412b | 2013-12-03 23:42:23 +0100 | [diff] [blame] | 259 | case Computer.State.Starting => |
Florian Nücke | bb7684a | 2013-11-07 00:25:46 +0100 | [diff] [blame] | 260 | verifyComponents() |
| 261 | switchTo(Computer.State.Yielded) |
Florian Nücke | 1a20aed | 2013-10-02 21:28:58 +0200 | [diff] [blame] | 262 | // Computer is rebooting. |
Florian Nücke | 08a412b | 2013-12-03 23:42:23 +0100 | [diff] [blame] | 263 | case Computer.State.Restarting => |
Florian Nücke | c51d551 | 2013-10-28 12:36:10 +0100 | [diff] [blame] | 264 | close() |
Florian Nücke | bb7684a | 2013-11-07 00:25:46 +0100 | [diff] [blame] | 265 | tmp.foreach(_.node.remove()) // To force deleting contents. |
Florian Nücke | 6e1ba98 | 2013-11-16 19:51:56 +0100 | [diff] [blame] | 266 | node.sendToReachable("computer.stopped") |
Florian Nücke | 1a20aed | 2013-10-02 21:28:58 +0200 | [diff] [blame] | 267 | start() |
Florian Nücke | bb7684a | 2013-11-07 00:25:46 +0100 | [diff] [blame] | 268 | // Resume from pauses based on sleep or signal underflow. |
Florian Nücke | 9ab4c5c | 2013-12-15 16:18:16 +0100 | [diff] [blame] | 269 | case Computer.State.Sleeping if remainIdle <= 0 || !signals.isEmpty => |
Florian Nücke | bb7684a | 2013-11-07 00:25:46 +0100 | [diff] [blame] | 270 | switchTo(Computer.State.Yielded) |
Florian Nücke | d24f726 | 2013-09-28 17:32:23 +0200 | [diff] [blame] | 271 | // Resume in case we paused because the game was paused. |
Florian Nücke | 08a412b | 2013-12-03 23:42:23 +0100 | [diff] [blame] | 272 | case Computer.State.Paused => |
Florian Nücke | 827344b | 2013-11-19 16:20:11 +0100 | [diff] [blame] | 273 | if (remainingPause > 0) { |
| 274 | remainingPause -= 1 |
| 275 | } |
| 276 | else { |
| 277 | verifyComponents() // In case we're resuming after loading. |
| 278 | state.pop() |
| 279 | switchTo(state.top) // Trigger execution if necessary. |
| 280 | } |
Florian Nücke | d24f726 | 2013-09-28 17:32:23 +0200 | [diff] [blame] | 281 | // Perform a synchronized call (message sending). |
Florian Nücke | 3d343ec | 2013-12-04 14:11:54 +0100 | [diff] [blame] | 282 | case Computer.State.SynchronizedCall => |
Florian Nücke | d24f726 | 2013-09-28 17:32:23 +0200 | [diff] [blame] | 283 | // These three asserts are all guaranteed by run(). |
| 284 | assert(lua.getTop == 2) |
| 285 | assert(lua.isThread(1)) |
| 286 | assert(lua.isFunction(2)) |
Florian Nücke | c8bcc59 | 2013-11-16 02:35:55 +0100 | [diff] [blame] | 287 | // Clear direct call limits again, just to be on the safe side... |
| 288 | // Theoretically it'd be possible for the executor to do some direct |
| 289 | // calls between the clear and the state check, which could in turn |
| 290 | // make this synchronized call fail due the limit still being maxed. |
| 291 | callCounts.clear() |
Florian Nücke | d24f726 | 2013-09-28 17:32:23 +0200 | [diff] [blame] | 292 | // We switch into running state, since we'll behave as though the call |
| 293 | // were performed from our executor thread. |
Florian Nücke | bb7684a | 2013-11-07 00:25:46 +0100 | [diff] [blame] | 294 | switchTo(Computer.State.Running) |
Florian Nücke | d24f726 | 2013-09-28 17:32:23 +0200 | [diff] [blame] | 295 | try { |
| 296 | // Synchronized call protocol requires the called function to return |
| 297 | // a table, which holds the results of the call, to be passed back |
| 298 | // to the coroutine.yield() that triggered the call. |
| 299 | lua.call(0, 1) |
| 300 | lua.checkType(2, LuaType.TABLE) |
Florian Nücke | 827344b | 2013-11-19 16:20:11 +0100 | [diff] [blame] | 301 | // Check if the callback called pause() or stop(). |
| 302 | state.top match { |
| 303 | case Computer.State.Running => |
| 304 | switchTo(Computer.State.SynchronizedReturn) |
| 305 | case Computer.State.Paused => |
Florian Nücke | 5aa9ad4 | 2013-11-22 01:12:57 +0100 | [diff] [blame] | 306 | state.pop() // Paused |
| 307 | state.pop() // Running, no switchTo to avoid new future. |
Florian Nücke | 827344b | 2013-11-19 16:20:11 +0100 | [diff] [blame] | 308 | state.push(Computer.State.SynchronizedReturn) |
| 309 | state.push(Computer.State.Paused) |
| 310 | case Computer.State.Stopping => // Nothing to do, we'll die anyway. |
| 311 | case _ => throw new AssertionError() |
Florian Nücke | 90bbdc2 | 2013-10-02 15:11:49 +0200 | [diff] [blame] | 312 | } |
Florian Nücke | d24f726 | 2013-09-28 17:32:23 +0200 | [diff] [blame] | 313 | } catch { |
| 314 | case _: LuaMemoryAllocationException => |
| 315 | // This can happen if we run out of memory while converting a Java |
| 316 | // exception to a string (which we have to do to avoid keeping |
| 317 | // userdata on the stack, which cannot be persisted). |
Florian Nücke | 6e1ba98 | 2013-11-16 19:51:56 +0100 | [diff] [blame] | 318 | crash("not enough memory") |
Florian Nücke | 1da52f8 | 2013-10-01 10:03:01 +0200 | [diff] [blame] | 319 | case e: java.lang.Error if e.getMessage == "not enough memory" => |
Florian Nücke | 6e1ba98 | 2013-11-16 19:51:56 +0100 | [diff] [blame] | 320 | crash("not enough memory") |
Florian Nücke | 1454512 | 2013-10-24 21:39:21 +0200 | [diff] [blame] | 321 | case e: Throwable => |
Florian Nücke | d24f726 | 2013-09-28 17:32:23 +0200 | [diff] [blame] | 322 | OpenComputers.log.log(Level.WARNING, "Faulty Lua implementation for synchronized calls.", e) |
Florian Nücke | 6e1ba98 | 2013-11-16 19:51:56 +0100 | [diff] [blame] | 323 | crash("protocol error") |
Florian Nücke | d24f726 | 2013-09-28 17:32:23 +0200 | [diff] [blame] | 324 | } |
Florian Nücke | d24f726 | 2013-09-28 17:32:23 +0200 | [diff] [blame] | 325 | case _ => // Nothing special to do, just avoid match errors. |
| 326 | }) |
Florian Nücke | e286fb7 | 2013-11-15 18:20:25 +0100 | [diff] [blame] | 327 | |
| 328 | // Finally check if we should stop the computer. We cannot lock the state |
| 329 | // because we may have to wait for the executor thread to finish, which |
| 330 | // might turn into a deadlock depending on where it currently is. |
| 331 | state.synchronized(state.top) match { |
| 332 | // Computer is shutting down. |
| 333 | case Computer.State.Stopping => this.synchronized(state.synchronized { |
Florian Nücke | e286fb7 | 2013-11-15 18:20:25 +0100 | [diff] [blame] | 334 | close() |
| 335 | rom.foreach(_.node.remove()) |
| 336 | tmp.foreach(_.node.remove()) |
Florian Nücke | 6e1ba98 | 2013-11-16 19:51:56 +0100 | [diff] [blame] | 337 | node.sendToReachable("computer.stopped") |
Florian Nücke | e286fb7 | 2013-11-15 18:20:25 +0100 | [diff] [blame] | 338 | }) |
| 339 | case _ => |
Florian Nücke | 6e1ba98 | 2013-11-16 19:51:56 +0100 | [diff] [blame] | 340 | } |
| 341 | } |
| 342 | |
| 343 | // ----------------------------------------------------------------------- // |
| 344 | |
Florian Nücke | ebb5390 | 2013-11-21 00:21:56 +0100 | [diff] [blame] | 345 | override def onMessage(message: Message) { |
| 346 | message.data match { |
| 347 | case Array(name: String, args@_*) if message.name == "computer.signal" => |
| 348 | signal(name, Seq(message.source.address) ++ args: _*) |
| 349 | case Array(player: EntityPlayer, name: String, args@_*) if message.name == "computer.checked_signal" => |
Florian Nücke | 9af2023 | 2013-11-26 03:05:16 +0100 | [diff] [blame] | 350 | if (canInteract(player.getCommandSenderName)) |
Florian Nücke | ebb5390 | 2013-11-21 00:21:56 +0100 | [diff] [blame] | 351 | signal(name, Seq(message.source.address) ++ args: _*) |
| 352 | case _ => |
| 353 | } |
| 354 | } |
| 355 | |
Florian Nücke | f135d86 | 2013-11-17 12:54:58 +0100 | [diff] [blame] | 356 | override def onConnect(node: Node) { |
Florian Nücke | 6e1ba98 | 2013-11-16 19:51:56 +0100 | [diff] [blame] | 357 | if (node == this.node) { |
Florian Nücke | 5f6a9cf | 2013-11-17 22:06:24 +0100 | [diff] [blame] | 358 | components += this.node.address -> this.node.name |
Florian Nücke | 6e1ba98 | 2013-11-16 19:51:56 +0100 | [diff] [blame] | 359 | rom.foreach(rom => node.connect(rom.node)) |
| 360 | tmp.foreach(tmp => node.connect(tmp.node)) |
| 361 | } |
| 362 | else { |
| 363 | node match { |
| 364 | case component: Component => addComponent(component) |
| 365 | case _ => |
| 366 | } |
| 367 | } |
Florian Nücke | 827344b | 2013-11-19 16:20:11 +0100 | [diff] [blame] | 368 | // For computers, to generate the components in their inventory. |
| 369 | owner.onConnect(node) |
Florian Nücke | 6e1ba98 | 2013-11-16 19:51:56 +0100 | [diff] [blame] | 370 | } |
| 371 | |
Florian Nücke | f135d86 | 2013-11-17 12:54:58 +0100 | [diff] [blame] | 372 | override def onDisconnect(node: Node) { |
Florian Nücke | 6e1ba98 | 2013-11-16 19:51:56 +0100 | [diff] [blame] | 373 | if (node == this.node) { |
| 374 | stop() |
| 375 | rom.foreach(_.node.remove()) |
| 376 | tmp.foreach(_.node.remove()) |
| 377 | } |
| 378 | else { |
| 379 | node match { |
| 380 | case component: Component => removeComponent(component) |
| 381 | case _ => |
| 382 | } |
| 383 | } |
Florian Nücke | 827344b | 2013-11-19 16:20:11 +0100 | [diff] [blame] | 384 | // For computers, to save the components in their inventory. |
| 385 | owner.onDisconnect(node) |
Florian Nücke | 6e1ba98 | 2013-11-16 19:51:56 +0100 | [diff] [blame] | 386 | } |
| 387 | |
| 388 | // ----------------------------------------------------------------------- // |
| 389 | |
| 390 | def addComponent(component: Component) { |
| 391 | if (!components.contains(component.address)) { |
| 392 | addedComponents += component |
| 393 | } |
| 394 | } |
| 395 | |
| 396 | def removeComponent(component: Component) { |
| 397 | if (components.contains(component.address)) { |
Florian Nücke | 5f6a9cf | 2013-11-17 22:06:24 +0100 | [diff] [blame] | 398 | components.synchronized(components -= component.address) |
Florian Nücke | 6e1ba98 | 2013-11-16 19:51:56 +0100 | [diff] [blame] | 399 | signal("component_removed", component.address, component.name) |
| 400 | } |
| 401 | addedComponents -= component |
| 402 | } |
| 403 | |
| 404 | private def processAddedComponents() { |
Florian Nücke | 407d251 | 2013-12-09 01:32:31 +0100 | [diff] [blame] | 405 | if (addedComponents.size > 0) { |
| 406 | for (component <- addedComponents) { |
| 407 | if (component.canBeSeenFrom(node)) { |
| 408 | components.synchronized(components += component.address -> component.name) |
| 409 | // Skip the signal if we're not initialized yet, since we'd generate a |
| 410 | // duplicate in the startup script otherwise. |
| 411 | if (kernelMemory > 0) { |
| 412 | signal("component_added", component.address, component.name) |
| 413 | } |
Florian Nücke | 5f6a9cf | 2013-11-17 22:06:24 +0100 | [diff] [blame] | 414 | } |
Florian Nücke | 6e1ba98 | 2013-11-16 19:51:56 +0100 | [diff] [blame] | 415 | } |
Florian Nücke | 407d251 | 2013-12-09 01:32:31 +0100 | [diff] [blame] | 416 | addedComponents.clear() |
Florian Nücke | 6e1ba98 | 2013-11-16 19:51:56 +0100 | [diff] [blame] | 417 | } |
Florian Nücke | 6e1ba98 | 2013-11-16 19:51:56 +0100 | [diff] [blame] | 418 | } |
| 419 | |
| 420 | private def verifyComponents() { |
| 421 | val invalid = mutable.Set.empty[String] |
| 422 | for ((address, name) <- components) { |
| 423 | if (node.network.node(address) == null) { |
| 424 | OpenComputers.log.warning("A component of type '" + name + |
| 425 | "' disappeared! This usually means that it didn't save its node.") |
| 426 | signal("component_removed", address, name) |
| 427 | invalid += address |
| 428 | } |
| 429 | } |
| 430 | for (address <- invalid) { |
| 431 | components -= address |
Florian Nücke | e286fb7 | 2013-11-15 18:20:25 +0100 | [diff] [blame] | 432 | } |
Florian Nücke | 61f53b9 | 2013-08-20 21:03:38 +0200 | [diff] [blame] | 433 | } |
| 434 | |
Florian Nücke | f9cd7fd | 2013-09-10 17:26:25 +0200 | [diff] [blame] | 435 | // ----------------------------------------------------------------------- // |
Florian Nücke | 0327cec | 2013-08-31 17:58:17 +0200 | [diff] [blame] | 436 | |
Florian Nücke | f135d86 | 2013-11-17 12:54:58 +0100 | [diff] [blame] | 437 | override def load(nbt: NBTTagCompound) = this.synchronized { |
Florian Nücke | bb7684a | 2013-11-07 00:25:46 +0100 | [diff] [blame] | 438 | assert(state.top == Computer.State.Stopped) |
Florian Nücke | cbf1760 | 2013-12-01 17:16:58 +0100 | [diff] [blame] | 439 | assert(_users.isEmpty) |
Florian Nücke | f135d86 | 2013-11-17 12:54:58 +0100 | [diff] [blame] | 440 | assert(signals.isEmpty) |
Florian Nücke | bb7684a | 2013-11-07 00:25:46 +0100 | [diff] [blame] | 441 | state.clear() |
Florian Nücke | 61f53b9 | 2013-08-20 21:03:38 +0200 | [diff] [blame] | 442 | |
Florian Nücke | f135d86 | 2013-11-17 12:54:58 +0100 | [diff] [blame] | 443 | super.load(nbt) |
Florian Nücke | bccee0c | 2013-11-08 14:08:01 +0100 | [diff] [blame] | 444 | |
Florian Nücke | 0f0418b | 2013-11-22 02:15:14 +0100 | [diff] [blame] | 445 | state.pushAll(nbt.getTagList("state").iterator[NBTTagInt].reverse.map(s => Computer.State(s.data))) |
Florian Nücke | cbf1760 | 2013-12-01 17:16:58 +0100 | [diff] [blame] | 446 | nbt.getTagList("users").foreach[NBTTagString](u => _users += u.data) |
Florian Nücke | f135d86 | 2013-11-17 12:54:58 +0100 | [diff] [blame] | 447 | |
| 448 | if (state.size > 0 && state.top != Computer.State.Stopped && init()) { |
Florian Nücke | d24f726 | 2013-09-28 17:32:23 +0200 | [diff] [blame] | 449 | // Unlimit memory use while unpersisting. |
Florian Nücke | 0327cec | 2013-08-31 17:58:17 +0200 | [diff] [blame] | 450 | lua.setTotalMemory(Integer.MAX_VALUE) |
Florian Nücke | af286d1 | 2013-10-27 01:37:34 +0200 | [diff] [blame] | 451 | |
Florian Nücke | 0327cec | 2013-08-31 17:58:17 +0200 | [diff] [blame] | 452 | try { |
Florian Nücke | d24f726 | 2013-09-28 17:32:23 +0200 | [diff] [blame] | 453 | // Try unpersisting Lua, because that's what all of the rest depends |
| 454 | // on. First, clear the stack, meaning the current kernel. |
| 455 | lua.setTop(0) |
| 456 | |
Florian Nücke | f135d86 | 2013-11-17 12:54:58 +0100 | [diff] [blame] | 457 | unpersist(nbt.getByteArray("kernel")) |
Florian Nücke | bb7684a | 2013-11-07 00:25:46 +0100 | [diff] [blame] | 458 | if (!lua.isThread(1)) { |
Florian Nücke | d24f726 | 2013-09-28 17:32:23 +0200 | [diff] [blame] | 459 | // This shouldn't really happen, but there's a chance it does if |
| 460 | // the save was corrupt (maybe someone modified the Lua files). |
Florian Nücke | bb7684a | 2013-11-07 00:25:46 +0100 | [diff] [blame] | 461 | throw new IllegalArgumentException("Invalid kernel.") |
Florian Nücke | d24f726 | 2013-09-28 17:32:23 +0200 | [diff] [blame] | 462 | } |
Florian Nücke | bb7684a | 2013-11-07 00:25:46 +0100 | [diff] [blame] | 463 | if (state.contains(Computer.State.SynchronizedCall) || state.contains(Computer.State.SynchronizedReturn)) { |
Florian Nücke | f135d86 | 2013-11-17 12:54:58 +0100 | [diff] [blame] | 464 | unpersist(nbt.getByteArray("stack")) |
Florian Nücke | bb7684a | 2013-11-07 00:25:46 +0100 | [diff] [blame] | 465 | if (!(if (state.contains(Computer.State.SynchronizedCall)) lua.isFunction(2) else lua.isTable(2))) { |
Florian Nücke | d24f726 | 2013-09-28 17:32:23 +0200 | [diff] [blame] | 466 | // Same as with the above, should not really happen normally, but |
| 467 | // could for the same reasons. |
Florian Nücke | bb7684a | 2013-11-07 00:25:46 +0100 | [diff] [blame] | 468 | throw new IllegalArgumentException("Invalid stack.") |
Florian Nücke | a5fdb98 | 2013-08-31 20:32:03 +0200 | [diff] [blame] | 469 | } |
Florian Nücke | a5fdb98 | 2013-08-31 20:32:03 +0200 | [diff] [blame] | 470 | } |
Florian Nücke | 4e820da | 2013-08-29 22:34:41 +0200 | [diff] [blame] | 471 | |
Florian Nücke | 5f6a9cf | 2013-11-17 22:06:24 +0100 | [diff] [blame] | 472 | components ++= nbt.getTagList("components").iterator[NBTTagCompound].map(c => |
Florian Nücke | f135d86 | 2013-11-17 12:54:58 +0100 | [diff] [blame] | 473 | c.getString("address") -> c.getString("name")) |
Florian Nücke | af286d1 | 2013-10-27 01:37:34 +0200 | [diff] [blame] | 474 | |
Florian Nücke | f135d86 | 2013-11-17 12:54:58 +0100 | [diff] [blame] | 475 | signals ++= nbt.getTagList("signals").iterator[NBTTagCompound].map(signalNbt => { |
Florian Nücke | d24f726 | 2013-09-28 17:32:23 +0200 | [diff] [blame] | 476 | val argsNbt = signalNbt.getCompoundTag("args") |
| 477 | val argsLength = argsNbt.getInteger("length") |
| 478 | new Computer.Signal(signalNbt.getString("name"), |
| 479 | (0 until argsLength).map("arg" + _).map(argsNbt.getTag).map { |
| 480 | case tag: NBTTagByte if tag.data == -1 => Unit |
| 481 | case tag: NBTTagByte => tag.data == 1 |
| 482 | case tag: NBTTagDouble => tag.data |
| 483 | case tag: NBTTagString => tag.data |
Florian Nücke | af286d1 | 2013-10-27 01:37:34 +0200 | [diff] [blame] | 484 | case tag: NBTTagByteArray => tag.byteArray |
| 485 | case _ => Unit |
Florian Nücke | d24f726 | 2013-09-28 17:32:23 +0200 | [diff] [blame] | 486 | }.toArray) |
Florian Nücke | af286d1 | 2013-10-27 01:37:34 +0200 | [diff] [blame] | 487 | }) |
Florian Nücke | d24f726 | 2013-09-28 17:32:23 +0200 | [diff] [blame] | 488 | |
Florian Nücke | f135d86 | 2013-11-17 12:54:58 +0100 | [diff] [blame] | 489 | rom.foreach(rom => rom.load(nbt.getCompoundTag("rom"))) |
| 490 | tmp.foreach(tmp => tmp.load(nbt.getCompoundTag("tmp"))) |
Florian Nücke | 0a83286 | 2013-12-11 00:49:06 +0100 | [diff] [blame] | 491 | kernelMemory = (nbt.getInteger("kernelMemory") * ramScale).toInt |
Florian Nücke | f135d86 | 2013-11-17 12:54:58 +0100 | [diff] [blame] | 492 | timeStarted = nbt.getLong("timeStarted") |
| 493 | cpuTime = nbt.getLong("cpuTime") |
Florian Nücke | 827344b | 2013-11-19 16:20:11 +0100 | [diff] [blame] | 494 | remainingPause = nbt.getInteger("remainingPause") |
Florian Nücke | f135d86 | 2013-11-17 12:54:58 +0100 | [diff] [blame] | 495 | if (nbt.hasKey("message")) { |
| 496 | message = Some(nbt.getString("message")) |
Florian Nücke | af286d1 | 2013-10-27 01:37:34 +0200 | [diff] [blame] | 497 | } |
Florian Nücke | d24f726 | 2013-09-28 17:32:23 +0200 | [diff] [blame] | 498 | |
Florian Nücke | 4a664bb | 2013-10-24 18:34:15 +0200 | [diff] [blame] | 499 | // Limit memory again. |
Florian Nücke | 41c1f40 | 2013-09-29 17:24:32 +0200 | [diff] [blame] | 500 | recomputeMemory() |
Florian Nücke | d24f726 | 2013-09-28 17:32:23 +0200 | [diff] [blame] | 501 | |
Florian Nücke | 1b57193 | 2013-11-04 13:53:43 +0100 | [diff] [blame] | 502 | // Delay execution for a second to allow the world around us to settle. |
Florian Nücke | ac5fe3c | 2013-11-25 21:54:59 +0100 | [diff] [blame] | 503 | pause(Settings.get.startupDelay) |
Florian Nücke | d24f726 | 2013-09-28 17:32:23 +0200 | [diff] [blame] | 504 | } catch { |
Florian Nücke | 3d343ec | 2013-12-04 14:11:54 +0100 | [diff] [blame] | 505 | case e: LuaRuntimeException => |
Florian Nücke | af286d1 | 2013-10-27 01:37:34 +0200 | [diff] [blame] | 506 | OpenComputers.log.warning("Could not unpersist computer.\n" + e.toString + "\tat " + e.getLuaStackTrace.mkString("\n\tat ")) |
Florian Nücke | bb7684a | 2013-11-07 00:25:46 +0100 | [diff] [blame] | 507 | state.push(Computer.State.Stopping) |
Florian Nücke | 4e820da | 2013-08-29 22:34:41 +0200 | [diff] [blame] | 508 | } |
Florian Nücke | d24f726 | 2013-09-28 17:32:23 +0200 | [diff] [blame] | 509 | } |
Florian Nücke | bb7684a | 2013-11-07 00:25:46 +0100 | [diff] [blame] | 510 | else close() // Clean up in case we got a weird state stack. |
Florian Nücke | d24f726 | 2013-09-28 17:32:23 +0200 | [diff] [blame] | 511 | } |
| 512 | |
Florian Nücke | f135d86 | 2013-11-17 12:54:58 +0100 | [diff] [blame] | 513 | override def save(nbt: NBTTagCompound): Unit = this.synchronized { |
Florian Nücke | bb7684a | 2013-11-07 00:25:46 +0100 | [diff] [blame] | 514 | assert(state.top != Computer.State.Running) // Lock on 'this' should guarantee this. |
Florian Nücke | f135d86 | 2013-11-17 12:54:58 +0100 | [diff] [blame] | 515 | |
Florian Nücke | c52e18a | 2013-11-26 14:35:14 +0100 | [diff] [blame] | 516 | // Make sure we don't continue running until everything has saved. |
| 517 | pause(0.05) |
| 518 | |
Florian Nücke | f135d86 | 2013-11-17 12:54:58 +0100 | [diff] [blame] | 519 | super.save(nbt) |
Florian Nücke | fe446e9 | 2013-10-30 00:50:13 +0100 | [diff] [blame] | 520 | |
Florian Nücke | bb7684a | 2013-11-07 00:25:46 +0100 | [diff] [blame] | 521 | // Make sure the component list is up-to-date. |
Florian Nücke | fe446e9 | 2013-10-30 00:50:13 +0100 | [diff] [blame] | 522 | processAddedComponents() |
Florian Nücke | d24f726 | 2013-09-28 17:32:23 +0200 | [diff] [blame] | 523 | |
Florian Nücke | f135d86 | 2013-11-17 12:54:58 +0100 | [diff] [blame] | 524 | nbt.setNewTagList("state", state.map(_.id)) |
Florian Nücke | cbf1760 | 2013-12-01 17:16:58 +0100 | [diff] [blame] | 525 | nbt.setNewTagList("users", _users) |
Florian Nücke | bb7684a | 2013-11-07 00:25:46 +0100 | [diff] [blame] | 526 | |
| 527 | if (state.top != Computer.State.Stopped) { |
Florian Nücke | af286d1 | 2013-10-27 01:37:34 +0200 | [diff] [blame] | 528 | // Unlimit memory while persisting. |
| 529 | lua.setTotalMemory(Integer.MAX_VALUE) |
Florian Nücke | d24f726 | 2013-09-28 17:32:23 +0200 | [diff] [blame] | 530 | |
Florian Nücke | af286d1 | 2013-10-27 01:37:34 +0200 | [diff] [blame] | 531 | try { |
| 532 | // Try persisting Lua, because that's what all of the rest depends on. |
| 533 | // Save the kernel state (which is always at stack index one). |
| 534 | assert(lua.isThread(1)) |
Florian Nücke | f135d86 | 2013-11-17 12:54:58 +0100 | [diff] [blame] | 535 | nbt.setByteArray("kernel", persist(1)) |
Florian Nücke | af286d1 | 2013-10-27 01:37:34 +0200 | [diff] [blame] | 536 | // While in a driver call we have one object on the global stack: either |
| 537 | // the function to call the driver with, or the result of the call. |
Florian Nücke | bb7684a | 2013-11-07 00:25:46 +0100 | [diff] [blame] | 538 | if (state.contains(Computer.State.SynchronizedCall) || state.contains(Computer.State.SynchronizedReturn)) { |
| 539 | assert(if (state.contains(Computer.State.SynchronizedCall)) lua.isFunction(2) else lua.isTable(2)) |
Florian Nücke | f135d86 | 2013-11-17 12:54:58 +0100 | [diff] [blame] | 540 | nbt.setByteArray("stack", persist(2)) |
Florian Nücke | d24f726 | 2013-09-28 17:32:23 +0200 | [diff] [blame] | 541 | } |
Florian Nücke | d24f726 | 2013-09-28 17:32:23 +0200 | [diff] [blame] | 542 | |
Florian Nücke | af286d1 | 2013-10-27 01:37:34 +0200 | [diff] [blame] | 543 | val componentsNbt = new NBTTagList() |
| 544 | for ((address, name) <- components) { |
| 545 | val componentNbt = new NBTTagCompound() |
| 546 | componentNbt.setString("address", address) |
| 547 | componentNbt.setString("name", name) |
| 548 | componentsNbt.appendTag(componentNbt) |
| 549 | } |
Florian Nücke | f135d86 | 2013-11-17 12:54:58 +0100 | [diff] [blame] | 550 | nbt.setTag("components", componentsNbt) |
Florian Nücke | af286d1 | 2013-10-27 01:37:34 +0200 | [diff] [blame] | 551 | |
| 552 | val signalsNbt = new NBTTagList() |
| 553 | for (s <- signals.iterator) { |
| 554 | val signalNbt = new NBTTagCompound() |
| 555 | signalNbt.setString("name", s.name) |
Florian Nücke | f135d86 | 2013-11-17 12:54:58 +0100 | [diff] [blame] | 556 | signalNbt.setNewCompoundTag("args", args => { |
| 557 | args.setInteger("length", s.args.length) |
| 558 | s.args.zipWithIndex.foreach { |
| 559 | case (Unit, i) => args.setByte("arg" + i, -1) |
| 560 | case (arg: Boolean, i) => args.setByte("arg" + i, if (arg) 1 else 0) |
| 561 | case (arg: Double, i) => args.setDouble("arg" + i, arg) |
| 562 | case (arg: String, i) => args.setString("arg" + i, arg) |
| 563 | case (arg: Array[Byte], i) => args.setByteArray("arg" + i, arg) |
| 564 | } |
| 565 | }) |
Florian Nücke | af286d1 | 2013-10-27 01:37:34 +0200 | [diff] [blame] | 566 | signalsNbt.appendTag(signalNbt) |
| 567 | } |
Florian Nücke | f135d86 | 2013-11-17 12:54:58 +0100 | [diff] [blame] | 568 | nbt.setTag("signals", signalsNbt) |
Florian Nücke | af286d1 | 2013-10-27 01:37:34 +0200 | [diff] [blame] | 569 | |
Florian Nücke | f135d86 | 2013-11-17 12:54:58 +0100 | [diff] [blame] | 570 | rom.foreach(rom => nbt.setNewCompoundTag("rom", rom.save)) |
| 571 | tmp.foreach(tmp => nbt.setNewCompoundTag("tmp", tmp.save)) |
Florian Nücke | af286d1 | 2013-10-27 01:37:34 +0200 | [diff] [blame] | 572 | |
Florian Nücke | 0a83286 | 2013-12-11 00:49:06 +0100 | [diff] [blame] | 573 | nbt.setInteger("kernelMemory", math.ceil(kernelMemory / ramScale).toInt) |
Florian Nücke | f135d86 | 2013-11-17 12:54:58 +0100 | [diff] [blame] | 574 | nbt.setLong("timeStarted", timeStarted) |
| 575 | nbt.setLong("cpuTime", cpuTime) |
Florian Nücke | 827344b | 2013-11-19 16:20:11 +0100 | [diff] [blame] | 576 | nbt.setInteger("remainingPause", remainingPause) |
Florian Nücke | f135d86 | 2013-11-17 12:54:58 +0100 | [diff] [blame] | 577 | message.foreach(nbt.setString("message", _)) |
Florian Nücke | af286d1 | 2013-10-27 01:37:34 +0200 | [diff] [blame] | 578 | } catch { |
Florian Nücke | 3d343ec | 2013-12-04 14:11:54 +0100 | [diff] [blame] | 579 | case e: LuaRuntimeException => |
Florian Nücke | af286d1 | 2013-10-27 01:37:34 +0200 | [diff] [blame] | 580 | OpenComputers.log.warning("Could not persist computer.\n" + e.toString + "\tat " + e.getLuaStackTrace.mkString("\n\tat ")) |
Florian Nücke | f135d86 | 2013-11-17 12:54:58 +0100 | [diff] [blame] | 581 | nbt.removeTag("state") |
Florian Nücke | af286d1 | 2013-10-27 01:37:34 +0200 | [diff] [blame] | 582 | } |
| 583 | |
Florian Nücke | 4a664bb | 2013-10-24 18:34:15 +0200 | [diff] [blame] | 584 | // Limit memory again. |
| 585 | recomputeMemory() |
Florian Nücke | d24f726 | 2013-09-28 17:32:23 +0200 | [diff] [blame] | 586 | } |
| 587 | } |
Florian Nücke | 4e820da | 2013-08-29 22:34:41 +0200 | [diff] [blame] | 588 | |
Florian Nücke | 2b1d410 | 2013-09-24 05:21:59 +0200 | [diff] [blame] | 589 | private def persist(index: Int): Array[Byte] = { |
Florian Nücke | d92257d | 2013-11-01 19:56:35 +0100 | [diff] [blame] | 590 | lua.getGlobal("eris") /* ... eris */ |
| 591 | lua.getField(-1, "persist") /* ... eris persist */ |
Florian Nücke | 86b7b84 | 2013-09-26 09:21:14 +0200 | [diff] [blame] | 592 | if (lua.isFunction(-1)) { |
Florian Nücke | d92257d | 2013-11-01 19:56:35 +0100 | [diff] [blame] | 593 | lua.getField(LuaState.REGISTRYINDEX, "perms") /* ... eris persist perms */ |
| 594 | lua.pushValue(index) // ... eris persist perms obj |
| 595 | try { |
| 596 | lua.call(2, 1) // ... eris str? |
| 597 | } catch { |
| 598 | case e: Throwable => |
| 599 | lua.pop(1) |
| 600 | throw e |
| 601 | } |
Florian Nücke | 86b7b84 | 2013-09-26 09:21:14 +0200 | [diff] [blame] | 602 | if (lua.isString(-1)) { |
Florian Nücke | d92257d | 2013-11-01 19:56:35 +0100 | [diff] [blame] | 603 | // ... eris str |
Florian Nücke | 0327cec | 2013-08-31 17:58:17 +0200 | [diff] [blame] | 604 | val result = lua.toByteArray(-1) |
Florian Nücke | d92257d | 2013-11-01 19:56:35 +0100 | [diff] [blame] | 605 | lua.pop(2) // ... |
Florian Nücke | 0327cec | 2013-08-31 17:58:17 +0200 | [diff] [blame] | 606 | return result |
Florian Nücke | d92257d | 2013-11-01 19:56:35 +0100 | [diff] [blame] | 607 | } // ... eris :( |
| 608 | } // ... eris :( |
| 609 | lua.pop(2) // ... |
Florian Nücke | 6bcfab2 | 2013-09-22 16:59:37 +0200 | [diff] [blame] | 610 | Array[Byte]() |
Florian Nücke | 0327cec | 2013-08-31 17:58:17 +0200 | [diff] [blame] | 611 | } |
| 612 | |
| 613 | private def unpersist(value: Array[Byte]): Boolean = { |
Florian Nücke | d92257d | 2013-11-01 19:56:35 +0100 | [diff] [blame] | 614 | lua.getGlobal("eris") // ... eris |
| 615 | lua.getField(-1, "unpersist") // ... eris unpersist |
Florian Nücke | 86b7b84 | 2013-09-26 09:21:14 +0200 | [diff] [blame] | 616 | if (lua.isFunction(-1)) { |
Florian Nücke | d92257d | 2013-11-01 19:56:35 +0100 | [diff] [blame] | 617 | lua.getField(LuaState.REGISTRYINDEX, "uperms") /* ... eris persist uperms */ |
| 618 | lua.pushByteArray(value) // ... eris unpersist uperms str |
| 619 | lua.call(2, 1) // ... eris obj |
| 620 | lua.insert(-2) // ... obj eris |
| 621 | lua.pop(1) |
| 622 | return true |
Florian Nücke | 0327cec | 2013-08-31 17:58:17 +0200 | [diff] [blame] | 623 | } // ... :( |
Florian Nücke | d92257d | 2013-11-01 19:56:35 +0100 | [diff] [blame] | 624 | lua.pop(1) |
| 625 | false |
Florian Nücke | 0327cec | 2013-08-31 17:58:17 +0200 | [diff] [blame] | 626 | } |
| 627 | |
Florian Nücke | f9cd7fd | 2013-09-10 17:26:25 +0200 | [diff] [blame] | 628 | // ----------------------------------------------------------------------- // |
| 629 | |
| 630 | private def init(): Boolean = { |
Florian Nücke | d886527 | 2013-10-29 23:49:28 +0100 | [diff] [blame] | 631 | // Utility functions for varargs callbacks. |
| 632 | def parseArgument(lua: LuaState, index: Int): AnyRef = lua.`type`(index) match { |
| 633 | case LuaType.BOOLEAN => Boolean.box(lua.toBoolean(index)) |
| 634 | case LuaType.NUMBER => Double.box(lua.toNumber(index)) |
| 635 | case LuaType.STRING => lua.toByteArray(index) |
| 636 | case _ => Unit |
| 637 | } |
| 638 | |
| 639 | def parseArguments(lua: LuaState, start: Int) = |
| 640 | for (index <- start to lua.getTop) yield parseArgument(lua, index) |
| 641 | |
| 642 | def pushList(value: Iterator[(Any, Int)]) { |
| 643 | lua.newTable() |
| 644 | var count = 0 |
| 645 | value.foreach { |
Florian Nücke | 3d343ec | 2013-12-04 14:11:54 +0100 | [diff] [blame] | 646 | case (x, index) => x match { |
| 647 | case (entry: ScalaNumber) => |
| 648 | pushResult(lua, entry.underlying()) |
| 649 | case (entry) => |
| 650 | pushResult(lua, entry.asInstanceOf[AnyRef]) |
| 651 | } |
Florian Nücke | d886527 | 2013-10-29 23:49:28 +0100 | [diff] [blame] | 652 | lua.rawSet(-2, index + 1) |
| 653 | count = count + 1 |
Florian Nücke | d886527 | 2013-10-29 23:49:28 +0100 | [diff] [blame] | 654 | } |
| 655 | lua.pushString("n") |
| 656 | lua.pushInteger(count) |
| 657 | lua.rawSet(-3) |
| 658 | } |
| 659 | |
| 660 | def pushResult(lua: LuaState, value: AnyRef): Unit = value match { |
Florian Nücke | eb3a8e1 | 2013-11-07 03:30:17 +0100 | [diff] [blame] | 661 | case null | Unit | _: BoxedUnit => lua.pushNil() |
Florian Nücke | d886527 | 2013-10-29 23:49:28 +0100 | [diff] [blame] | 662 | case value: java.lang.Boolean => lua.pushBoolean(value.booleanValue) |
| 663 | case value: java.lang.Byte => lua.pushNumber(value.byteValue) |
| 664 | case value: java.lang.Character => lua.pushString(String.valueOf(value)) |
| 665 | case value: java.lang.Short => lua.pushNumber(value.shortValue) |
| 666 | case value: java.lang.Integer => lua.pushNumber(value.intValue) |
| 667 | case value: java.lang.Long => lua.pushNumber(value.longValue) |
| 668 | case value: java.lang.Float => lua.pushNumber(value.floatValue) |
| 669 | case value: java.lang.Double => lua.pushNumber(value.doubleValue) |
| 670 | case value: java.lang.String => lua.pushString(value) |
| 671 | case value: Array[Byte] => lua.pushByteArray(value) |
| 672 | case value: Array[_] => pushList(value.zipWithIndex.iterator) |
| 673 | case value: Product => pushList(value.productIterator.zipWithIndex) |
| 674 | case value: Seq[_] => pushList(value.zipWithIndex.iterator) |
| 675 | // TODO maps? |
| 676 | case _ => |
| 677 | OpenComputers.log.warning("A component callback tried to return an unsupported value of type " + value.getClass.getName + ".") |
| 678 | lua.pushNil() |
| 679 | } |
| 680 | |
Florian Nücke | 4a664bb | 2013-10-24 18:34:15 +0200 | [diff] [blame] | 681 | // Reset error state. |
| 682 | message = None |
| 683 | |
Florian Nücke | 0327cec | 2013-08-31 17:58:17 +0200 | [diff] [blame] | 684 | // Creates a new state with all base libraries and the persistence library |
| 685 | // loaded into it. This means the state has much more power than it |
| 686 | // rightfully should have, so we sandbox it a bit in the following. |
Florian Nücke | 87892c3 | 2013-09-14 16:47:21 +0200 | [diff] [blame] | 687 | LuaStateFactory.createState() match { |
| 688 | case None => |
Florian Nücke | 86b7b84 | 2013-09-26 09:21:14 +0200 | [diff] [blame] | 689 | lua = null |
| 690 | return false |
Florian Nücke | 6bcfab2 | 2013-09-22 16:59:37 +0200 | [diff] [blame] | 691 | case Some(value) => lua = value |
Florian Nücke | 87892c3 | 2013-09-14 16:47:21 +0200 | [diff] [blame] | 692 | } |
Florian Nücke | 61f53b9 | 2013-08-20 21:03:38 +0200 | [diff] [blame] | 693 | |
Florian Nücke | 5aad6d6 | 2013-11-01 04:30:07 +0100 | [diff] [blame] | 694 | // Connect the ROM and `/tmp` node to our owner. We're not in a network in |
| 695 | // case we're loading, which is why we have to check it here. |
Florian Nücke | 6e1ba98 | 2013-11-16 19:51:56 +0100 | [diff] [blame] | 696 | if (node.network != null) { |
| 697 | rom.foreach(rom => node.connect(rom.node)) |
| 698 | tmp.foreach(tmp => node.connect(tmp.node)) |
Florian Nücke | a7ec5c0 | 2013-10-27 05:33:33 +0100 | [diff] [blame] | 699 | } |
| 700 | |
Florian Nücke | 0327cec | 2013-08-31 17:58:17 +0200 | [diff] [blame] | 701 | try { |
Florian Nücke | 4e820da | 2013-08-29 22:34:41 +0200 | [diff] [blame] | 702 | // Push a couple of functions that override original Lua API functions or |
Florian Nücke | f9cd7fd | 2013-09-10 17:26:25 +0200 | [diff] [blame] | 703 | // that add new functionality to it. |
Florian Nücke | f9cd7fd | 2013-09-10 17:26:25 +0200 | [diff] [blame] | 704 | lua.getGlobal("os") |
Florian Nücke | 4e820da | 2013-08-29 22:34:41 +0200 | [diff] [blame] | 705 | |
| 706 | // Custom os.clock() implementation returning the time the computer has |
Florian Nücke | d363d03 | 2013-10-06 18:16:03 +0200 | [diff] [blame] | 707 | // been actively running, instead of the native library... |
Florian Nücke | bb491ec | 2013-09-29 14:15:40 +0200 | [diff] [blame] | 708 | lua.pushScalaFunction(lua => { |
Florian Nücke | d363d03 | 2013-10-06 18:16:03 +0200 | [diff] [blame] | 709 | lua.pushNumber((cpuTime + (System.nanoTime() - cpuStart)) * 10e-10) |
Florian Nücke | 5aea2ef | 2013-09-22 23:20:30 +0200 | [diff] [blame] | 710 | 1 |
Florian Nücke | bb491ec | 2013-09-29 14:15:40 +0200 | [diff] [blame] | 711 | }) |
Florian Nücke | 4e820da | 2013-08-29 22:34:41 +0200 | [diff] [blame] | 712 | lua.setField(-2, "clock") |
Florian Nücke | f9cd7fd | 2013-09-10 17:26:25 +0200 | [diff] [blame] | 713 | |
Florian Nücke | 2a48cb2 | 2013-11-07 17:02:25 +0100 | [diff] [blame] | 714 | // Date formatting function. |
| 715 | lua.pushScalaFunction(lua => { |
| 716 | val format = |
| 717 | if (lua.getTop > 0 && lua.isString(1)) lua.toString(1) |
| 718 | else "%d/%m/%y %H:%M:%S" |
| 719 | val time = |
| 720 | if (lua.getTop > 1 && lua.isNumber(2)) lua.toNumber(2) * 1000 / 60 / 60 |
| 721 | else worldTime + 6000 |
| 722 | |
| 723 | val dt = GameTimeFormatter.parse(time) |
| 724 | def fmt(format: String) { |
| 725 | if (format == "*t") { |
| 726 | lua.newTable(0, 8) |
| 727 | lua.pushInteger(dt.year) |
| 728 | lua.setField(-2, "year") |
| 729 | lua.pushInteger(dt.month) |
| 730 | lua.setField(-2, "month") |
| 731 | lua.pushInteger(dt.day) |
| 732 | lua.setField(-2, "day") |
| 733 | lua.pushInteger(dt.hour) |
| 734 | lua.setField(-2, "hour") |
| 735 | lua.pushInteger(dt.minute) |
| 736 | lua.setField(-2, "min") |
| 737 | lua.pushInteger(dt.second) |
| 738 | lua.setField(-2, "sec") |
| 739 | lua.pushInteger(dt.weekDay) |
| 740 | lua.setField(-2, "wday") |
| 741 | lua.pushInteger(dt.yearDay) |
| 742 | lua.setField(-2, "yday") |
| 743 | } |
| 744 | else { |
| 745 | lua.pushString(GameTimeFormatter.format(format, dt)) |
| 746 | } |
| 747 | } |
| 748 | |
| 749 | // Just ignore the allowed leading '!', Minecraft has no time zones... |
| 750 | if (format.startsWith("!")) |
| 751 | fmt(format.substring(1)) |
| 752 | else |
| 753 | fmt(format) |
| 754 | 1 |
| 755 | }) |
| 756 | lua.setField(-2, "date") |
| 757 | |
Florian Nücke | f9cd7fd | 2013-09-10 17:26:25 +0200 | [diff] [blame] | 758 | // Return ingame time for os.time(). |
Florian Nücke | bb491ec | 2013-09-29 14:15:40 +0200 | [diff] [blame] | 759 | lua.pushScalaFunction(lua => { |
Florian Nücke | 5aea2ef | 2013-09-22 23:20:30 +0200 | [diff] [blame] | 760 | // Game time is in ticks, so that each day has 24000 ticks, meaning |
| 761 | // one hour is game time divided by one thousand. Also, Minecraft |
| 762 | // starts days at 6 o'clock, so we add those six hours. Thus: |
Florian Nücke | 2a48cb2 | 2013-11-07 17:02:25 +0100 | [diff] [blame] | 763 | // timestamp = (time + 6000) * 60[kh] * 60[km] / 1000[s] |
| 764 | lua.pushNumber((worldTime + 6000) * 60 * 60 / 1000) |
Florian Nücke | 5aea2ef | 2013-09-22 23:20:30 +0200 | [diff] [blame] | 765 | 1 |
Florian Nücke | bb491ec | 2013-09-29 14:15:40 +0200 | [diff] [blame] | 766 | }) |
Florian Nücke | f9cd7fd | 2013-09-10 17:26:25 +0200 | [diff] [blame] | 767 | lua.setField(-2, "time") |
Florian Nücke | d363d03 | 2013-10-06 18:16:03 +0200 | [diff] [blame] | 768 | |
Florian Nücke | c54f285 | 2013-12-06 15:44:16 +0100 | [diff] [blame] | 769 | // Pop the os table. |
| 770 | lua.pop(1) |
| 771 | |
| 772 | // Computer API, stuff that kinda belongs to os, but we don't want to |
| 773 | // clutter it. |
| 774 | lua.newTable() |
| 775 | |
Florian Nücke | 3a87ecd | 2013-12-20 19:53:57 +0100 | [diff] [blame] | 776 | // Allow getting the real world time for timeouts. |
Florian Nücke | c54f285 | 2013-12-06 15:44:16 +0100 | [diff] [blame] | 777 | lua.pushScalaFunction(lua => { |
| 778 | lua.pushNumber(System.currentTimeMillis() / 1000.0) |
| 779 | 1 |
| 780 | }) |
| 781 | lua.setField(-2, "realTime") |
| 782 | |
Florian Nücke | d363d03 | 2013-10-06 18:16:03 +0200 | [diff] [blame] | 783 | // The time the computer has been running, as opposed to the CPU time. |
| 784 | lua.pushScalaFunction(lua => { |
| 785 | // World time is in ticks, and each second has 20 ticks. Since we |
Florian Nücke | 3a87ecd | 2013-12-20 19:53:57 +0100 | [diff] [blame] | 786 | // want uptime() to return real seconds, though, we'll divide it |
Florian Nücke | d363d03 | 2013-10-06 18:16:03 +0200 | [diff] [blame] | 787 | // accordingly. |
| 788 | lua.pushNumber((worldTime - timeStarted) / 20.0) |
| 789 | 1 |
| 790 | }) |
| 791 | lua.setField(-2, "uptime") |
Florian Nücke | 0327cec | 2013-08-31 17:58:17 +0200 | [diff] [blame] | 792 | |
Florian Nücke | 12da32b | 2013-09-27 07:12:33 +0200 | [diff] [blame] | 793 | // Allow the computer to figure out its own id in the component network. |
Florian Nücke | bb491ec | 2013-09-29 14:15:40 +0200 | [diff] [blame] | 794 | lua.pushScalaFunction(lua => { |
Florian Nücke | 6e1ba98 | 2013-11-16 19:51:56 +0100 | [diff] [blame] | 795 | Option(node.address) match { |
Florian Nücke | 59febb0 | 2013-10-01 22:10:54 +0200 | [diff] [blame] | 796 | case None => lua.pushNil() |
| 797 | case Some(address) => lua.pushString(address) |
| 798 | } |
Florian Nücke | 12da32b | 2013-09-27 07:12:33 +0200 | [diff] [blame] | 799 | 1 |
Florian Nücke | bb491ec | 2013-09-29 14:15:40 +0200 | [diff] [blame] | 800 | }) |
Florian Nücke | 12da32b | 2013-09-27 07:12:33 +0200 | [diff] [blame] | 801 | lua.setField(-2, "address") |
Florian Nücke | 4e820da | 2013-08-29 22:34:41 +0200 | [diff] [blame] | 802 | |
Florian Nücke | ba1f66e | 2013-11-19 03:29:57 +0100 | [diff] [blame] | 803 | // Are we a robot? (No this is not a CAPTCHA.) |
| 804 | lua.pushScalaFunction(lua => { |
| 805 | lua.pushBoolean(isRobot) |
| 806 | 1 |
| 807 | }) |
| 808 | lua.setField(-2, "isRobot") |
| 809 | |
| 810 | lua.pushScalaFunction(lua => { |
| 811 | // This is *very* unlikely, but still: avoid this getting larger than |
| 812 | // what we report as the total memory. |
Florian Nücke | d1094c0 | 2013-12-09 22:01:22 +0100 | [diff] [blame] | 813 | lua.pushInteger(((lua.getFreeMemory min (lua.getTotalMemory - kernelMemory)) / ramScale).toInt) |
Florian Nücke | ba1f66e | 2013-11-19 03:29:57 +0100 | [diff] [blame] | 814 | 1 |
| 815 | }) |
| 816 | lua.setField(-2, "freeMemory") |
| 817 | |
| 818 | // Allow the system to read how much memory it uses and has available. |
| 819 | lua.pushScalaFunction(lua => { |
Florian Nücke | d1094c0 | 2013-12-09 22:01:22 +0100 | [diff] [blame] | 820 | lua.pushInteger(((lua.getTotalMemory - kernelMemory) / ramScale).toInt) |
Florian Nücke | ba1f66e | 2013-11-19 03:29:57 +0100 | [diff] [blame] | 821 | 1 |
| 822 | }) |
| 823 | lua.setField(-2, "totalMemory") |
| 824 | |
| 825 | lua.pushScalaFunction(lua => { |
| 826 | lua.pushBoolean(signal(lua.checkString(1), parseArguments(lua, 2): _*)) |
| 827 | 1 |
| 828 | }) |
| 829 | lua.setField(-2, "pushSignal") |
| 830 | |
Florian Nücke | 3a87ecd | 2013-12-20 19:53:57 +0100 | [diff] [blame] | 831 | // And its ROM address. |
Florian Nücke | 90bbdc2 | 2013-10-02 15:11:49 +0200 | [diff] [blame] | 832 | lua.pushScalaFunction(lua => { |
Florian Nücke | af286d1 | 2013-10-27 01:37:34 +0200 | [diff] [blame] | 833 | rom.foreach(rom => Option(rom.node.address) match { |
Florian Nücke | 90bbdc2 | 2013-10-02 15:11:49 +0200 | [diff] [blame] | 834 | case None => lua.pushNil() |
| 835 | case Some(address) => lua.pushString(address) |
Florian Nücke | 0927f07 | 2013-10-03 22:33:48 +0200 | [diff] [blame] | 836 | }) |
Florian Nücke | 90bbdc2 | 2013-10-02 15:11:49 +0200 | [diff] [blame] | 837 | 1 |
| 838 | }) |
| 839 | lua.setField(-2, "romAddress") |
| 840 | |
Florian Nücke | 9459366 | 2013-10-09 16:38:21 +0200 | [diff] [blame] | 841 | // And it's /tmp address... |
| 842 | lua.pushScalaFunction(lua => { |
Florian Nücke | af286d1 | 2013-10-27 01:37:34 +0200 | [diff] [blame] | 843 | tmp.foreach(tmp => Option(tmp.node.address) match { |
Florian Nücke | 9459366 | 2013-10-09 16:38:21 +0200 | [diff] [blame] | 844 | case None => lua.pushNil() |
| 845 | case Some(address) => lua.pushString(address) |
| 846 | }) |
| 847 | 1 |
| 848 | }) |
| 849 | lua.setField(-2, "tmpAddress") |
Florian Nücke | bccee0c | 2013-11-08 14:08:01 +0100 | [diff] [blame] | 850 | |
| 851 | // User management. |
| 852 | lua.pushScalaFunction(lua => { |
Florian Nücke | cbf1760 | 2013-12-01 17:16:58 +0100 | [diff] [blame] | 853 | _users.foreach(lua.pushString) |
| 854 | _users.size |
Florian Nücke | bccee0c | 2013-11-08 14:08:01 +0100 | [diff] [blame] | 855 | }) |
| 856 | lua.setField(-2, "users") |
| 857 | |
| 858 | lua.pushScalaFunction(lua => try { |
Florian Nücke | cbf1760 | 2013-12-01 17:16:58 +0100 | [diff] [blame] | 859 | if (_users.size >= Settings.get.maxUsers) |
Florian Nücke | bccee0c | 2013-11-08 14:08:01 +0100 | [diff] [blame] | 860 | throw new Exception("too many users") |
| 861 | |
| 862 | val name = lua.checkString(1) |
| 863 | |
Florian Nücke | cbf1760 | 2013-12-01 17:16:58 +0100 | [diff] [blame] | 864 | if (_users.contains(name)) |
Florian Nücke | bccee0c | 2013-11-08 14:08:01 +0100 | [diff] [blame] | 865 | throw new Exception("user exists") |
Florian Nücke | ac5fe3c | 2013-11-25 21:54:59 +0100 | [diff] [blame] | 866 | if (name.length > Settings.get.maxUsernameLength) |
Florian Nücke | bccee0c | 2013-11-08 14:08:01 +0100 | [diff] [blame] | 867 | throw new Exception("username too long") |
| 868 | if (!MinecraftServer.getServer.getConfigurationManager.getAllUsernames.contains(name)) |
| 869 | throw new Exception("player must be online") |
| 870 | |
Florian Nücke | cbf1760 | 2013-12-01 17:16:58 +0100 | [diff] [blame] | 871 | _users.synchronized { |
| 872 | _users += name |
| 873 | usersChanged = true |
| 874 | } |
Florian Nücke | bccee0c | 2013-11-08 14:08:01 +0100 | [diff] [blame] | 875 | lua.pushBoolean(true) |
| 876 | 1 |
| 877 | } catch { |
| 878 | case e: Throwable => |
| 879 | lua.pushNil() |
| 880 | lua.pushString(Option(e.getMessage).getOrElse(e.toString)) |
| 881 | 2 |
| 882 | }) |
| 883 | lua.setField(-2, "addUser") |
| 884 | |
| 885 | lua.pushScalaFunction(lua => { |
| 886 | val name = lua.checkString(1) |
Florian Nücke | cbf1760 | 2013-12-01 17:16:58 +0100 | [diff] [blame] | 887 | _users.synchronized { |
| 888 | val success = _users.remove(name) |
| 889 | if (success) { |
| 890 | usersChanged = true |
| 891 | } |
| 892 | lua.pushBoolean(success) |
| 893 | } |
Florian Nücke | bccee0c | 2013-11-08 14:08:01 +0100 | [diff] [blame] | 894 | 1 |
| 895 | }) |
| 896 | lua.setField(-2, "removeUser") |
Florian Nücke | 9459366 | 2013-10-09 16:38:21 +0200 | [diff] [blame] | 897 | |
Florian Nücke | 0a83286 | 2013-12-11 00:49:06 +0100 | [diff] [blame] | 898 | lua.pushScalaFunction(lua => { |
| 899 | lua.pushNumber(node.globalBuffer) |
| 900 | 1 |
| 901 | }) |
| 902 | lua.setField(-2, "energy") |
| 903 | |
| 904 | lua.pushScalaFunction(lua => { |
| 905 | lua.pushNumber(node.globalBufferSize) |
| 906 | 1 |
| 907 | }) |
| 908 | lua.setField(-2, "maxEnergy") |
| 909 | |
Florian Nücke | c54f285 | 2013-12-06 15:44:16 +0100 | [diff] [blame] | 910 | // Set the computer table. |
| 911 | lua.setGlobal("computer") |
Florian Nücke | 4e820da | 2013-08-29 22:34:41 +0200 | [diff] [blame] | 912 | |
Florian Nücke | 0327cec | 2013-08-31 17:58:17 +0200 | [diff] [blame] | 913 | // Until we get to ingame screens we log to Java's stdout. |
Florian Nücke | bb491ec | 2013-09-29 14:15:40 +0200 | [diff] [blame] | 914 | lua.pushScalaFunction(lua => { |
Florian Nücke | a7ec5c0 | 2013-10-27 05:33:33 +0100 | [diff] [blame] | 915 | println((1 to lua.getTop).map(i => lua.`type`(i) match { |
| 916 | case LuaType.NIL => "nil" |
| 917 | case LuaType.BOOLEAN => lua.toBoolean(i) |
| 918 | case LuaType.NUMBER => lua.toNumber(i) |
| 919 | case LuaType.STRING => lua.toString(i) |
| 920 | case LuaType.TABLE => "table" |
| 921 | case LuaType.FUNCTION => "function" |
| 922 | case LuaType.THREAD => "thread" |
| 923 | case LuaType.LIGHTUSERDATA | LuaType.USERDATA => "userdata" |
| 924 | }).mkString(" ")) |
Florian Nücke | 5aea2ef | 2013-09-22 23:20:30 +0200 | [diff] [blame] | 925 | 0 |
Florian Nücke | bb491ec | 2013-09-29 14:15:40 +0200 | [diff] [blame] | 926 | }) |
Florian Nücke | 0327cec | 2013-08-31 17:58:17 +0200 | [diff] [blame] | 927 | lua.setGlobal("print") |
| 928 | |
Florian Nücke | af286d1 | 2013-10-27 01:37:34 +0200 | [diff] [blame] | 929 | // How long programs may run without yielding before we stop them. |
Florian Nücke | ac5fe3c | 2013-11-25 21:54:59 +0100 | [diff] [blame] | 930 | lua.pushNumber(Settings.get.timeout) |
Florian Nücke | af286d1 | 2013-10-27 01:37:34 +0200 | [diff] [blame] | 931 | lua.setGlobal("timeout") |
| 932 | |
Florian Nücke | c51d551 | 2013-10-28 12:36:10 +0100 | [diff] [blame] | 933 | // Component interaction stuff. |
| 934 | lua.newTable() |
| 935 | |
Florian Nücke | af286d1 | 2013-10-27 01:37:34 +0200 | [diff] [blame] | 936 | lua.pushScalaFunction(lua => components.synchronized { |
| 937 | val filter = if (lua.isString(1)) Option(lua.toString(1)) else None |
| 938 | lua.newTable(0, components.size) |
| 939 | for ((address, name) <- components) { |
Florian Nücke | fe446e9 | 2013-10-30 00:50:13 +0100 | [diff] [blame] | 940 | if (filter.isEmpty || name.contains(filter.get)) { |
Florian Nücke | af286d1 | 2013-10-27 01:37:34 +0200 | [diff] [blame] | 941 | lua.pushString(address) |
| 942 | lua.pushString(name) |
Florian Nücke | a7ec5c0 | 2013-10-27 05:33:33 +0100 | [diff] [blame] | 943 | lua.rawSet(-3) |
Florian Nücke | af286d1 | 2013-10-27 01:37:34 +0200 | [diff] [blame] | 944 | } |
| 945 | } |
| 946 | 1 |
| 947 | }) |
Florian Nücke | c51d551 | 2013-10-28 12:36:10 +0100 | [diff] [blame] | 948 | lua.setField(-2, "list") |
Florian Nücke | af286d1 | 2013-10-27 01:37:34 +0200 | [diff] [blame] | 949 | |
| 950 | lua.pushScalaFunction(lua => components.synchronized { |
| 951 | components.get(lua.checkString(1)) match { |
| 952 | case Some(name: String) => |
| 953 | lua.pushString(name) |
| 954 | 1 |
| 955 | case _ => |
| 956 | lua.pushNil() |
| 957 | lua.pushString("no such component") |
| 958 | 2 |
| 959 | } |
| 960 | }) |
Florian Nücke | c51d551 | 2013-10-28 12:36:10 +0100 | [diff] [blame] | 961 | lua.setField(-2, "type") |
Florian Nücke | af286d1 | 2013-10-27 01:37:34 +0200 | [diff] [blame] | 962 | |
Florian Nücke | a7ec5c0 | 2013-10-27 05:33:33 +0100 | [diff] [blame] | 963 | lua.pushScalaFunction(lua => { |
Florian Nücke | 6e1ba98 | 2013-11-16 19:51:56 +0100 | [diff] [blame] | 964 | Option(node.network.node(lua.checkString(1))) match { |
Florian Nücke | 5f6a9cf | 2013-11-17 22:06:24 +0100 | [diff] [blame] | 965 | case Some(component: server.network.Component) if component.canBeSeenFrom(node) || component == node => |
Florian Nücke | a7ec5c0 | 2013-10-27 05:33:33 +0100 | [diff] [blame] | 966 | lua.newTable() |
Florian Nücke | 730c8f0 | 2013-11-01 01:12:55 +0100 | [diff] [blame] | 967 | for (method <- component.methods()) { |
| 968 | lua.pushString(method) |
Florian Nücke | e286fb7 | 2013-11-15 18:20:25 +0100 | [diff] [blame] | 969 | lua.pushBoolean(component.isDirect(method)) |
Florian Nücke | 730c8f0 | 2013-11-01 01:12:55 +0100 | [diff] [blame] | 970 | lua.rawSet(-3) |
Florian Nücke | a7ec5c0 | 2013-10-27 05:33:33 +0100 | [diff] [blame] | 971 | } |
| 972 | 1 |
| 973 | case _ => |
| 974 | lua.pushNil() |
| 975 | lua.pushString("no such component") |
| 976 | 2 |
| 977 | } |
| 978 | }) |
Florian Nücke | c51d551 | 2013-10-28 12:36:10 +0100 | [diff] [blame] | 979 | lua.setField(-2, "methods") |
Florian Nücke | 48cf012 | 2013-09-23 15:08:27 +0200 | [diff] [blame] | 980 | |
Florian Nücke | e286fb7 | 2013-11-15 18:20:25 +0100 | [diff] [blame] | 981 | class LimitReachedException extends Exception |
| 982 | |
Florian Nücke | bb491ec | 2013-09-29 14:15:40 +0200 | [diff] [blame] | 983 | lua.pushScalaFunction(lua => { |
Florian Nücke | af286d1 | 2013-10-27 01:37:34 +0200 | [diff] [blame] | 984 | val address = lua.checkString(1) |
| 985 | val method = lua.checkString(2) |
| 986 | val args = parseArguments(lua, 3) |
| 987 | try { |
Florian Nücke | 6e1ba98 | 2013-11-16 19:51:56 +0100 | [diff] [blame] | 988 | (Option(node.network.node(address)) match { |
Florian Nücke | 5f6a9cf | 2013-11-17 22:06:24 +0100 | [diff] [blame] | 989 | case Some(component: server.network.Component) if component.canBeSeenFrom(node) || component == node => |
Florian Nücke | e286fb7 | 2013-11-15 18:20:25 +0100 | [diff] [blame] | 990 | val direct = component.isDirect(method) |
| 991 | if (direct) callCounts.synchronized { |
| 992 | val limit = component.limit(method) |
| 993 | val counts = callCounts.getOrElseUpdate(component.address, mutable.Map.empty[String, Int]) |
| 994 | val count = counts.getOrElseUpdate(method, 0) |
| 995 | if (count >= limit) { |
| 996 | throw new LimitReachedException() |
| 997 | } |
| 998 | counts(method) += 1 |
| 999 | } |
Florian Nücke | 6e1ba98 | 2013-11-16 19:51:56 +0100 | [diff] [blame] | 1000 | component.invoke(method, this, args: _*) |
Florian Nücke | af286d1 | 2013-10-27 01:37:34 +0200 | [diff] [blame] | 1001 | case _ => throw new Exception("no such component") |
| 1002 | }) match { |
Florian Nücke | 78a426c | 2013-10-27 15:06:21 +0100 | [diff] [blame] | 1003 | case results: Array[_] => |
| 1004 | lua.pushBoolean(true) |
Florian Nücke | af286d1 | 2013-10-27 01:37:34 +0200 | [diff] [blame] | 1005 | results.foreach(pushResult(lua, _)) |
Florian Nücke | 78a426c | 2013-10-27 15:06:21 +0100 | [diff] [blame] | 1006 | 1 + results.length |
Florian Nücke | af286d1 | 2013-10-27 01:37:34 +0200 | [diff] [blame] | 1007 | case _ => |
Florian Nücke | 78a426c | 2013-10-27 15:06:21 +0100 | [diff] [blame] | 1008 | lua.pushBoolean(true) |
| 1009 | 1 |
Florian Nücke | af286d1 | 2013-10-27 01:37:34 +0200 | [diff] [blame] | 1010 | } |
| 1011 | } catch { |
Florian Nücke | e286fb7 | 2013-11-15 18:20:25 +0100 | [diff] [blame] | 1012 | case _: LimitReachedException => |
| 1013 | 0 |
Florian Nücke | 78a426c | 2013-10-27 15:06:21 +0100 | [diff] [blame] | 1014 | case e: IllegalArgumentException if e.getMessage != null => |
| 1015 | lua.pushBoolean(false) |
| 1016 | lua.pushString(e.getMessage) |
Florian Nücke | af286d1 | 2013-10-27 01:37:34 +0200 | [diff] [blame] | 1017 | 2 |
Florian Nücke | 78a426c | 2013-10-27 15:06:21 +0100 | [diff] [blame] | 1018 | case e: Throwable if e.getMessage != null => |
| 1019 | lua.pushBoolean(true) |
| 1020 | lua.pushNil() |
| 1021 | lua.pushString(e.getMessage) |
| 1022 | 3 |
Florian Nücke | 1fd375f | 2013-10-29 22:43:42 +0100 | [diff] [blame] | 1023 | case _: ArrayIndexOutOfBoundsException => |
| 1024 | lua.pushBoolean(false) |
| 1025 | lua.pushString("index out of bounds") |
| 1026 | 2 |
| 1027 | case _: IllegalArgumentException => |
| 1028 | lua.pushBoolean(false) |
| 1029 | lua.pushString("bad argument") |
| 1030 | 2 |
| 1031 | case _: NoSuchMethodException => |
| 1032 | lua.pushBoolean(false) |
| 1033 | lua.pushString("no such method") |
| 1034 | 2 |
Florian Nücke | 78a426c | 2013-10-27 15:06:21 +0100 | [diff] [blame] | 1035 | case _: FileNotFoundException => |
| 1036 | lua.pushBoolean(true) |
| 1037 | lua.pushNil() |
| 1038 | lua.pushString("file not found") |
| 1039 | 3 |
| 1040 | case _: SecurityException => |
| 1041 | lua.pushBoolean(true) |
| 1042 | lua.pushNil() |
| 1043 | lua.pushString("access denied") |
| 1044 | 3 |
| 1045 | case _: IOException => |
| 1046 | lua.pushBoolean(true) |
| 1047 | lua.pushNil() |
| 1048 | lua.pushString("i/o error") |
| 1049 | 3 |
Florian Nücke | cf6a3e3 | 2013-11-18 15:09:58 +0100 | [diff] [blame] | 1050 | case e: Throwable => |
| 1051 | OpenComputers.log.log(Level.WARNING, "Unexpected error in Lua callback.", e) |
Florian Nücke | 78a426c | 2013-10-27 15:06:21 +0100 | [diff] [blame] | 1052 | lua.pushBoolean(true) |
Florian Nücke | a7ec5c0 | 2013-10-27 05:33:33 +0100 | [diff] [blame] | 1053 | lua.pushNil() |
Florian Nücke | af286d1 | 2013-10-27 01:37:34 +0200 | [diff] [blame] | 1054 | lua.pushString("unknown error") |
Florian Nücke | 78a426c | 2013-10-27 15:06:21 +0100 | [diff] [blame] | 1055 | 3 |
Florian Nücke | 48cf012 | 2013-09-23 15:08:27 +0200 | [diff] [blame] | 1056 | } |
Florian Nücke | bb491ec | 2013-09-29 14:15:40 +0200 | [diff] [blame] | 1057 | }) |
Florian Nücke | c51d551 | 2013-10-28 12:36:10 +0100 | [diff] [blame] | 1058 | lua.setField(-2, "invoke") |
| 1059 | |
| 1060 | lua.setGlobal("component") |
Florian Nücke | abddfae | 2013-09-29 11:57:14 +0200 | [diff] [blame] | 1061 | |
Florian Nücke | d92257d | 2013-11-01 19:56:35 +0100 | [diff] [blame] | 1062 | initPerms() |
Florian Nücke | 48cf012 | 2013-09-23 15:08:27 +0200 | [diff] [blame] | 1063 | |
Florian Nücke | ac5fe3c | 2013-11-25 21:54:59 +0100 | [diff] [blame] | 1064 | lua.load(classOf[Computer].getResourceAsStream(Settings.scriptPath + "kernel.lua"), "=kernel", "t") |
Florian Nücke | abddfae | 2013-09-29 11:57:14 +0200 | [diff] [blame] | 1065 | lua.newThread() // Left as the first value on the stack. |
Florian Nücke | 4e820da | 2013-08-29 22:34:41 +0200 | [diff] [blame] | 1066 | |
Florian Nücke | 0327cec | 2013-08-31 17:58:17 +0200 | [diff] [blame] | 1067 | // Clear any left-over signals from a previous run. |
| 1068 | signals.clear() |
Florian Nücke | 90bbdc2 | 2013-10-02 15:11:49 +0200 | [diff] [blame] | 1069 | |
Florian Nücke | 4e820da | 2013-08-29 22:34:41 +0200 | [diff] [blame] | 1070 | return true |
Florian Nücke | 61f53b9 | 2013-08-20 21:03:38 +0200 | [diff] [blame] | 1071 | } |
| 1072 | catch { |
Florian Nücke | 3d343ec | 2013-12-04 14:11:54 +0100 | [diff] [blame] | 1073 | case ex: Throwable => |
Florian Nücke | 86b7b84 | 2013-09-26 09:21:14 +0200 | [diff] [blame] | 1074 | OpenComputers.log.log(Level.WARNING, "Failed initializing computer.", ex) |
Florian Nücke | 4e820da | 2013-08-29 22:34:41 +0200 | [diff] [blame] | 1075 | close() |
Florian Nücke | 61f53b9 | 2013-08-20 21:03:38 +0200 | [diff] [blame] | 1076 | } |
Florian Nücke | 6bcfab2 | 2013-09-22 16:59:37 +0200 | [diff] [blame] | 1077 | false |
Florian Nücke | 61f53b9 | 2013-08-20 21:03:38 +0200 | [diff] [blame] | 1078 | } |
Florian Nücke | 4e820da | 2013-08-29 22:34:41 +0200 | [diff] [blame] | 1079 | |
Florian Nücke | d92257d | 2013-11-01 19:56:35 +0100 | [diff] [blame] | 1080 | private def initPerms() { |
| 1081 | // These tables must contain all java callbacks (i.e. C functions, since |
| 1082 | // they are wrapped on the native side using a C function, of course). |
| 1083 | // They are used when persisting/unpersisting the state so that the |
| 1084 | // persistence library knows which values it doesn't have to serialize |
| 1085 | // (since it cannot persist C functions). |
| 1086 | lua.newTable() /* ... perms */ |
| 1087 | lua.newTable() /* ... uperms */ |
| 1088 | |
| 1089 | val perms = lua.getTop - 1 |
| 1090 | val uperms = lua.getTop |
| 1091 | |
| 1092 | def flattenAndStore() { |
| 1093 | /* ... k v */ |
| 1094 | // We only care for tables and functions, any value types are safe. |
| 1095 | if (lua.isFunction(-1) || lua.isTable(-1)) { |
| 1096 | lua.pushValue(-2) /* ... k v k */ |
| 1097 | lua.getTable(uperms) /* ... k v uperms[k] */ |
| 1098 | assert(lua.isNil(-1), "duplicate permanent value named " + lua.toString(-3)) |
| 1099 | lua.pop(1) /* ... k v */ |
| 1100 | // If we have aliases its enough to store the value once. |
| 1101 | lua.pushValue(-1) /* ... k v v */ |
| 1102 | lua.getTable(perms) /* ... k v perms[v] */ |
| 1103 | val isNew = lua.isNil(-1) |
| 1104 | lua.pop(1) /* ... k v */ |
| 1105 | if (isNew) { |
| 1106 | lua.pushValue(-1) /* ... k v v */ |
| 1107 | lua.pushValue(-3) /* ... k v v k */ |
| 1108 | lua.rawSet(perms) /* ... k v ; perms[v] = k */ |
| 1109 | lua.pushValue(-2) /* ... k v k */ |
| 1110 | lua.pushValue(-2) /* ... k v k v */ |
| 1111 | lua.rawSet(uperms) /* ... k v ; uperms[k] = v */ |
| 1112 | // Recurse into tables. |
| 1113 | if (lua.isTable(-1)) { |
| 1114 | // Enforce a deterministic order when determining the keys, to ensure |
| 1115 | // the keys are the same when unpersisting again. |
| 1116 | val key = lua.toString(-2) |
| 1117 | val childKeys = mutable.ArrayBuffer.empty[String] |
| 1118 | lua.pushNil() /* ... k v nil */ |
| 1119 | while (lua.next(-2)) { |
| 1120 | /* ... k v ck cv */ |
| 1121 | lua.pop(1) /* ... k v ck */ |
| 1122 | childKeys += lua.toString(-1) |
| 1123 | } |
| 1124 | /* ... k v */ |
| 1125 | childKeys.sortWith((a, b) => a.compareTo(b) < 0) |
| 1126 | for (childKey <- childKeys) { |
| 1127 | lua.pushString(key + "." + childKey) /* ... k v ck */ |
| 1128 | lua.getField(-2, childKey) /* ... k v ck cv */ |
| 1129 | flattenAndStore() /* ... k v */ |
| 1130 | } |
| 1131 | /* ... k v */ |
| 1132 | } |
| 1133 | /* ... k v */ |
| 1134 | } |
| 1135 | /* ... k v */ |
| 1136 | } |
| 1137 | lua.pop(2) /* ... */ |
| 1138 | } |
| 1139 | |
| 1140 | // Mark everything that's globally reachable at this point as permanent. |
| 1141 | lua.pushString("_G") /* ... perms uperms k */ |
| 1142 | lua.getGlobal("_G") /* ... perms uperms k v */ |
| 1143 | |
| 1144 | flattenAndStore() /* ... perms uperms */ |
| 1145 | lua.setField(LuaState.REGISTRYINDEX, "uperms") /* ... perms */ |
| 1146 | lua.setField(LuaState.REGISTRYINDEX, "perms") /* ... */ |
| 1147 | } |
| 1148 | |
Florian Nücke | bb7684a | 2013-11-07 00:25:46 +0100 | [diff] [blame] | 1149 | private def close() = state.synchronized( |
Florian Nücke | f135d86 | 2013-11-17 12:54:58 +0100 | [diff] [blame] | 1150 | if (state.size == 0 || state.top != Computer.State.Stopped) { |
Florian Nücke | bb7684a | 2013-11-07 00:25:46 +0100 | [diff] [blame] | 1151 | state.clear() |
| 1152 | state.push(Computer.State.Stopped) |
Florian Nücke | 9a8da53 | 2013-12-07 23:33:26 +0100 | [diff] [blame] | 1153 | if (lua != null) { |
| 1154 | lua.setTotalMemory(Integer.MAX_VALUE) |
| 1155 | lua.close() |
| 1156 | } |
Florian Nücke | 0327cec | 2013-08-31 17:58:17 +0200 | [diff] [blame] | 1157 | lua = null |
| 1158 | kernelMemory = 0 |
| 1159 | signals.clear() |
| 1160 | timeStarted = 0 |
Florian Nücke | d363d03 | 2013-10-06 18:16:03 +0200 | [diff] [blame] | 1161 | cpuTime = 0 |
| 1162 | cpuStart = 0 |
Florian Nücke | 9ab4c5c | 2013-12-15 16:18:16 +0100 | [diff] [blame] | 1163 | remainIdle = 0 |
Florian Nücke | 551fd3b | 2013-09-20 02:31:24 +0200 | [diff] [blame] | 1164 | |
| 1165 | // Mark state change in owner, to send it to clients. |
Florian Nücke | 35d8e07 | 2013-11-08 19:19:57 +0100 | [diff] [blame] | 1166 | owner.markAsChanged() |
Florian Nücke | 0327cec | 2013-08-31 17:58:17 +0200 | [diff] [blame] | 1167 | }) |
Florian Nücke | d363d03 | 2013-10-06 18:16:03 +0200 | [diff] [blame] | 1168 | |
| 1169 | // ----------------------------------------------------------------------- // |
Florian Nücke | 4e820da | 2013-08-29 22:34:41 +0200 | [diff] [blame] | 1170 | |
Florian Nücke | bb7684a | 2013-11-07 00:25:46 +0100 | [diff] [blame] | 1171 | private def switchTo(value: Computer.State.Value) = { |
| 1172 | val result = state.pop() |
| 1173 | state.push(value) |
| 1174 | if (value == Computer.State.Yielded || value == Computer.State.SynchronizedReturn) { |
Florian Nücke | 9ab4c5c | 2013-12-15 16:18:16 +0100 | [diff] [blame] | 1175 | remainIdle = 0 |
Florian Nücke | ac5fe3c | 2013-11-25 21:54:59 +0100 | [diff] [blame] | 1176 | Computer.threadPool.submit(this) |
Florian Nücke | bb7684a | 2013-11-07 00:25:46 +0100 | [diff] [blame] | 1177 | } |
Florian Nücke | 827344b | 2013-11-19 16:20:11 +0100 | [diff] [blame] | 1178 | |
| 1179 | // Mark state change in owner, to send it to clients. |
| 1180 | owner.markAsChanged() |
| 1181 | |
Florian Nücke | bb7684a | 2013-11-07 00:25:46 +0100 | [diff] [blame] | 1182 | result |
Florian Nücke | d24f726 | 2013-09-28 17:32:23 +0200 | [diff] [blame] | 1183 | } |
Florian Nücke | 9ab4c5c | 2013-12-15 16:18:16 +0100 | [diff] [blame] | 1184 | |
| 1185 | private def isGamePaused = !MinecraftServer.getServer.isDedicatedServer && (MinecraftServer.getServer match { |
| 1186 | case integrated: IntegratedServer => integrated.getServerListeningThread.isGamePaused |
| 1187 | case _ => false |
| 1188 | }) |
Florian Nücke | d24f726 | 2013-09-28 17:32:23 +0200 | [diff] [blame] | 1189 | |
Florian Nücke | 0327cec | 2013-08-31 17:58:17 +0200 | [diff] [blame] | 1190 | // This is a really high level lock that we only use for saving and loading. |
Florian Nücke | 5aea2ef | 2013-09-22 23:20:30 +0200 | [diff] [blame] | 1191 | override def run(): Unit = this.synchronized { |
Florian Nücke | bb7684a | 2013-11-07 00:25:46 +0100 | [diff] [blame] | 1192 | val enterState = state.synchronized { |
Florian Nücke | c8bcc59 | 2013-11-16 02:35:55 +0100 | [diff] [blame] | 1193 | if (state.top == Computer.State.Stopped || |
| 1194 | state.top == Computer.State.Stopping || |
| 1195 | state.top == Computer.State.Paused) { |
| 1196 | return |
| 1197 | } |
Florian Nücke | d24f726 | 2013-09-28 17:32:23 +0200 | [diff] [blame] | 1198 | // See if the game appears to be paused, in which case we also pause. |
Florian Nücke | 9ab4c5c | 2013-12-15 16:18:16 +0100 | [diff] [blame] | 1199 | if (isGamePaused) { |
Florian Nücke | bb7684a | 2013-11-07 00:25:46 +0100 | [diff] [blame] | 1200 | state.push(Computer.State.Paused) |
Florian Nücke | a5fdb98 | 2013-08-31 20:32:03 +0200 | [diff] [blame] | 1201 | return |
| 1202 | } |
Florian Nücke | bb7684a | 2013-11-07 00:25:46 +0100 | [diff] [blame] | 1203 | switchTo(Computer.State.Running) |
Florian Nücke | 0327cec | 2013-08-31 17:58:17 +0200 | [diff] [blame] | 1204 | } |
| 1205 | |
Florian Nücke | 4e820da | 2013-08-29 22:34:41 +0200 | [diff] [blame] | 1206 | try { |
Florian Nücke | bb7684a | 2013-11-07 00:25:46 +0100 | [diff] [blame] | 1207 | // The kernel thread will always be at stack index one. |
| 1208 | assert(lua.isThread(1)) |
| 1209 | |
Florian Nücke | 3617b71 | 2013-12-03 18:11:32 +0100 | [diff] [blame] | 1210 | if (Settings.get.activeGC) { |
| 1211 | // Help out the GC a little. The emergency GC has a few limitations |
| 1212 | // that will make it free less memory than doing a full step manually. |
| 1213 | lua.gc(LuaState.GcAction.COLLECT, 0) |
| 1214 | } |
| 1215 | |
Florian Nücke | 0327cec | 2013-08-31 17:58:17 +0200 | [diff] [blame] | 1216 | // Resume the Lua state and remember the number of results we get. |
Florian Nücke | d363d03 | 2013-10-06 18:16:03 +0200 | [diff] [blame] | 1217 | cpuStart = System.nanoTime() |
Florian Nücke | bb7684a | 2013-11-07 00:25:46 +0100 | [diff] [blame] | 1218 | val (results, runtime) = enterState match { |
| 1219 | case Computer.State.SynchronizedReturn => |
| 1220 | // If we were doing a synchronized call, continue where we left off. |
| 1221 | assert(lua.getTop == 2) |
| 1222 | assert(lua.isTable(2)) |
| 1223 | (lua.resume(1, 1), System.nanoTime() - cpuStart) |
| 1224 | case Computer.State.Yielded => |
| 1225 | if (kernelMemory == 0) { |
| 1226 | // We're doing the initialization run. |
Florian Nücke | 1996332 | 2013-11-24 03:19:34 +0100 | [diff] [blame] | 1227 | if (lua.resume(1, 0) > 0) { |
| 1228 | // We expect to get nothing here, if we do we had an error. |
| 1229 | (0, 0L) |
| 1230 | } |
| 1231 | else { |
| 1232 | // Run the garbage collector to get rid of stuff left behind after |
| 1233 | // the initialization phase to get a good estimate of the base |
| 1234 | // memory usage the kernel has (including libraries). We remember |
| 1235 | // that size to grant user-space programs a fixed base amount of |
| 1236 | // memory, regardless of the memory need of the underlying system |
| 1237 | // (which may change across releases). |
| 1238 | lua.gc(LuaState.GcAction.COLLECT, 0) |
Florian Nücke | 407d251 | 2013-12-09 01:32:31 +0100 | [diff] [blame] | 1239 | kernelMemory = math.max(lua.getTotalMemory - lua.getFreeMemory, 1) |
Florian Nücke | 1996332 | 2013-11-24 03:19:34 +0100 | [diff] [blame] | 1240 | recomputeMemory() |
Florian Nücke | bb7684a | 2013-11-07 00:25:46 +0100 | [diff] [blame] | 1241 | |
Florian Nücke | 1996332 | 2013-11-24 03:19:34 +0100 | [diff] [blame] | 1242 | // Fake zero sleep to avoid stopping if there are no signals. |
| 1243 | lua.pushInteger(0) |
| 1244 | (1, 0L) |
| 1245 | } |
Florian Nücke | 86b7b84 | 2013-09-26 09:21:14 +0200 | [diff] [blame] | 1246 | } |
Florian Nücke | bb7684a | 2013-11-07 00:25:46 +0100 | [diff] [blame] | 1247 | else (signals.synchronized(if (signals.isEmpty) None else Some(signals.dequeue())) match { |
| 1248 | case Some(signal) => |
| 1249 | lua.pushString(signal.name) |
| 1250 | signal.args.foreach { |
| 1251 | case Unit => lua.pushNil() |
| 1252 | case arg: Boolean => lua.pushBoolean(arg) |
| 1253 | case arg: Double => lua.pushNumber(arg) |
| 1254 | case arg: String => lua.pushString(arg) |
| 1255 | case arg: Array[Byte] => lua.pushByteArray(arg) |
| 1256 | } |
| 1257 | lua.resume(1, 1 + signal.args.length) |
| 1258 | case _ => |
| 1259 | lua.resume(1, 0) |
| 1260 | }, System.nanoTime() - cpuStart) |
Florian Nücke | afd383d | 2013-11-30 21:07:44 +0100 | [diff] [blame] | 1261 | case s => throw new AssertionError("Running computer from invalid state " + s.toString) |
Florian Nücke | 4e820da | 2013-08-29 22:34:41 +0200 | [diff] [blame] | 1262 | } |
| 1263 | |
Florian Nücke | bb7684a | 2013-11-07 00:25:46 +0100 | [diff] [blame] | 1264 | // Keep track of time spent executing the computer. |
Florian Nücke | 1b57193 | 2013-11-04 13:53:43 +0100 | [diff] [blame] | 1265 | cpuTime += runtime |
Florian Nücke | 78a426c | 2013-10-27 15:06:21 +0100 | [diff] [blame] | 1266 | |
Florian Nücke | d24f726 | 2013-09-28 17:32:23 +0200 | [diff] [blame] | 1267 | // Check if the kernel is still alive. |
Florian Nücke | bb7684a | 2013-11-07 00:25:46 +0100 | [diff] [blame] | 1268 | state.synchronized(if (lua.status(1) == LuaState.YIELD) { |
Florian Nücke | 827344b | 2013-11-19 16:20:11 +0100 | [diff] [blame] | 1269 | // Check if someone called pause() or stop() in the meantime. |
| 1270 | state.top match { |
| 1271 | case Computer.State.Running => |
| 1272 | // If we get one function it must be a wrapper for a synchronized |
| 1273 | // call. The protocol is that a closure is pushed that is then called |
| 1274 | // from the main server thread, and returns a table, which is in turn |
| 1275 | // passed to the originating coroutine.yield(). |
| 1276 | if (results == 1 && lua.isFunction(2)) { |
| 1277 | switchTo(Computer.State.SynchronizedCall) |
| 1278 | } |
| 1279 | // Check if we are shutting down, and if so if we're rebooting. This |
| 1280 | // is signalled by boolean values, where `false` means shut down, |
| 1281 | // `true` means reboot (i.e shutdown then start again). |
| 1282 | else if (results == 1 && lua.isBoolean(2)) { |
| 1283 | if (lua.toBoolean(2)) switchTo(Computer.State.Restarting) |
| 1284 | else switchTo(Computer.State.Stopping) |
| 1285 | } |
| 1286 | else { |
| 1287 | // If we have a single number, that's how long we may wait before |
| 1288 | // resuming the state again. Note that the sleep may be interrupted |
| 1289 | // early if a signal arrives in the meantime. If we have something |
| 1290 | // else we just process the next signal or wait for one. |
| 1291 | val sleep = |
Florian Nücke | 9ab4c5c | 2013-12-15 16:18:16 +0100 | [diff] [blame] | 1292 | if (results == 1 && lua.isNumber(2)) (lua.toNumber(2) * 20).toInt |
| 1293 | else Int.MaxValue |
Florian Nücke | 827344b | 2013-11-19 16:20:11 +0100 | [diff] [blame] | 1294 | lua.pop(results) |
| 1295 | signals.synchronized { |
| 1296 | // Immediately check for signals to allow processing more than one |
| 1297 | // signal per game tick. |
Florian Nücke | 9ab4c5c | 2013-12-15 16:18:16 +0100 | [diff] [blame] | 1298 | if (signals.isEmpty && sleep > 0) { |
Florian Nücke | 827344b | 2013-11-19 16:20:11 +0100 | [diff] [blame] | 1299 | switchTo(Computer.State.Sleeping) |
Florian Nücke | 9ab4c5c | 2013-12-15 16:18:16 +0100 | [diff] [blame] | 1300 | remainIdle = sleep |
Florian Nücke | 827344b | 2013-11-19 16:20:11 +0100 | [diff] [blame] | 1301 | } else { |
| 1302 | switchTo(Computer.State.Yielded) |
| 1303 | } |
Florian Nücke | bb7684a | 2013-11-07 00:25:46 +0100 | [diff] [blame] | 1304 | } |
| 1305 | } |
Florian Nücke | 827344b | 2013-11-19 16:20:11 +0100 | [diff] [blame] | 1306 | case Computer.State.Paused => |
Florian Nücke | 5aa9ad4 | 2013-11-22 01:12:57 +0100 | [diff] [blame] | 1307 | state.pop() // Paused |
| 1308 | state.pop() // Running, no switchTo to avoid new future. |
Florian Nücke | 827344b | 2013-11-19 16:20:11 +0100 | [diff] [blame] | 1309 | state.push(Computer.State.Yielded) |
| 1310 | state.push(Computer.State.Paused) |
| 1311 | case Computer.State.Stopping => // Nothing to do, we'll die anyway. |
Florian Nücke | 5aa9ad4 | 2013-11-22 01:12:57 +0100 | [diff] [blame] | 1312 | case _ => throw new AssertionError( |
| 1313 | "Invalid state in executor post-processing.") |
Florian Nücke | bb7684a | 2013-11-07 00:25:46 +0100 | [diff] [blame] | 1314 | } |
Florian Nücke | 0327cec | 2013-08-31 17:58:17 +0200 | [diff] [blame] | 1315 | } |
Florian Nücke | 3403396 | 2013-11-26 01:34:10 +0100 | [diff] [blame] | 1316 | // The kernel thread returned. If it threw we'd be in the catch below. |
Florian Nücke | d24f726 | 2013-09-28 17:32:23 +0200 | [diff] [blame] | 1317 | else { |
| 1318 | assert(lua.isThread(1)) |
| 1319 | // We're expecting the result of a pcall, if anything, so boolean + (result | string). |
| 1320 | if (!lua.isBoolean(2) || !(lua.isString(3) || lua.isNil(3))) { |
| 1321 | OpenComputers.log.warning("Kernel returned unexpected results.") |
| 1322 | } |
| 1323 | // The pcall *should* never return normally... but check for it nonetheless. |
| 1324 | if (lua.toBoolean(2)) { |
| 1325 | OpenComputers.log.warning("Kernel stopped unexpectedly.") |
Florian Nücke | 6e1ba98 | 2013-11-16 19:51:56 +0100 | [diff] [blame] | 1326 | stop() |
Florian Nücke | d24f726 | 2013-09-28 17:32:23 +0200 | [diff] [blame] | 1327 | } |
| 1328 | else { |
Florian Nücke | 6544161 | 2013-10-07 03:02:54 +0200 | [diff] [blame] | 1329 | lua.setTotalMemory(Int.MaxValue) |
Florian Nücke | acd5603 | 2013-10-07 05:05:51 +0200 | [diff] [blame] | 1330 | val error = lua.toString(3) |
Florian Nücke | 6e1ba98 | 2013-11-16 19:51:56 +0100 | [diff] [blame] | 1331 | if (error != null) crash(error) |
| 1332 | else crash("unknown error") |
Florian Nücke | d24f726 | 2013-09-28 17:32:23 +0200 | [diff] [blame] | 1333 | } |
Florian Nücke | d24f726 | 2013-09-28 17:32:23 +0200 | [diff] [blame] | 1334 | }) |
Florian Nücke | 4e820da | 2013-08-29 22:34:41 +0200 | [diff] [blame] | 1335 | } |
| 1336 | catch { |
Florian Nücke | d24f726 | 2013-09-28 17:32:23 +0200 | [diff] [blame] | 1337 | case e: LuaRuntimeException => |
| 1338 | OpenComputers.log.warning("Kernel crashed. This is a bug!\n" + e.toString + "\tat " + e.getLuaStackTrace.mkString("\n\tat ")) |
Florian Nücke | 6e1ba98 | 2013-11-16 19:51:56 +0100 | [diff] [blame] | 1339 | crash("kernel panic") |
Florian Nücke | 1454512 | 2013-10-24 21:39:21 +0200 | [diff] [blame] | 1340 | case e: LuaGcMetamethodException => |
Florian Nücke | 6e1ba98 | 2013-11-16 19:51:56 +0100 | [diff] [blame] | 1341 | if (e.getMessage != null) crash("kernel panic:\n" + e.getMessage) |
| 1342 | else crash("kernel panic:\nerror in garbage collection metamethod") |
Florian Nücke | abddfae | 2013-09-29 11:57:14 +0200 | [diff] [blame] | 1343 | case e: LuaMemoryAllocationException => |
Florian Nücke | 6e1ba98 | 2013-11-16 19:51:56 +0100 | [diff] [blame] | 1344 | crash("not enough memory") |
Florian Nücke | abddfae | 2013-09-29 11:57:14 +0200 | [diff] [blame] | 1345 | case e: java.lang.Error if e.getMessage == "not enough memory" => |
Florian Nücke | 6e1ba98 | 2013-11-16 19:51:56 +0100 | [diff] [blame] | 1346 | crash("not enough memory") |
Florian Nücke | 1454512 | 2013-10-24 21:39:21 +0200 | [diff] [blame] | 1347 | case e: Throwable => |
| 1348 | OpenComputers.log.log(Level.WARNING, "Unexpected error in kernel. This is a bug!\n", e) |
Florian Nücke | 6e1ba98 | 2013-11-16 19:51:56 +0100 | [diff] [blame] | 1349 | crash("kernel panic") |
Florian Nücke | 4e820da | 2013-08-29 22:34:41 +0200 | [diff] [blame] | 1350 | } |
Florian Nücke | 12da32b | 2013-09-27 07:12:33 +0200 | [diff] [blame] | 1351 | } |
| 1352 | } |
| 1353 | |
| 1354 | object Computer { |
Florian Nücke | bb491ec | 2013-09-29 14:15:40 +0200 | [diff] [blame] | 1355 | |
Florian Nücke | 0327cec | 2013-08-31 17:58:17 +0200 | [diff] [blame] | 1356 | /** Signals are messages sent to the Lua state from Java asynchronously. */ |
| 1357 | private class Signal(val name: String, val args: Array[Any]) |
Florian Nücke | 4e820da | 2013-08-29 22:34:41 +0200 | [diff] [blame] | 1358 | |
| 1359 | /** Possible states of the computer, and in particular its executor. */ |
| 1360 | private object State extends Enumeration { |
Florian Nücke | 0327cec | 2013-08-31 17:58:17 +0200 | [diff] [blame] | 1361 | /** The computer is not running right now and there is no Lua state. */ |
Florian Nücke | 4e820da | 2013-08-29 22:34:41 +0200 | [diff] [blame] | 1362 | val Stopped = Value("Stopped") |
Florian Nücke | 0327cec | 2013-08-31 17:58:17 +0200 | [diff] [blame] | 1363 | |
Florian Nücke | bb7684a | 2013-11-07 00:25:46 +0100 | [diff] [blame] | 1364 | /** Booting up, doing the first run to initialize the kernel and libs. */ |
| 1365 | val Starting = Value("Starting") |
Florian Nücke | d24f726 | 2013-09-28 17:32:23 +0200 | [diff] [blame] | 1366 | |
Florian Nücke | bb7684a | 2013-11-07 00:25:46 +0100 | [diff] [blame] | 1367 | /** Computer is currently rebooting. */ |
| 1368 | val Restarting = Value("Restarting") |
| 1369 | |
| 1370 | /** The computer is currently shutting down. */ |
| 1371 | val Stopping = Value("Stopping") |
Florian Nücke | 4e820da | 2013-08-29 22:34:41 +0200 | [diff] [blame] | 1372 | |
Florian Nücke | a5fdb98 | 2013-08-31 20:32:03 +0200 | [diff] [blame] | 1373 | /** The computer is paused and waiting for the game to resume. */ |
| 1374 | val Paused = Value("Paused") |
| 1375 | |
Florian Nücke | 48cf012 | 2013-09-23 15:08:27 +0200 | [diff] [blame] | 1376 | /** The computer executor is waiting for a synchronized call to be made. */ |
| 1377 | val SynchronizedCall = Value("SynchronizedCall") |
Florian Nücke | 0327cec | 2013-08-31 17:58:17 +0200 | [diff] [blame] | 1378 | |
Florian Nücke | 48cf012 | 2013-09-23 15:08:27 +0200 | [diff] [blame] | 1379 | /** The computer should resume with the result of a synchronized call. */ |
| 1380 | val SynchronizedReturn = Value("SynchronizedReturn") |
Florian Nücke | a5fdb98 | 2013-08-31 20:32:03 +0200 | [diff] [blame] | 1381 | |
Florian Nücke | bb7684a | 2013-11-07 00:25:46 +0100 | [diff] [blame] | 1382 | /** The computer will resume as soon as possible. */ |
| 1383 | val Yielded = Value("Yielded") |
Florian Nücke | 1a20aed | 2013-10-02 21:28:58 +0200 | [diff] [blame] | 1384 | |
Florian Nücke | 827344b | 2013-11-19 16:20:11 +0100 | [diff] [blame] | 1385 | /** The computer is yielding for a longer amount of time. */ |
| 1386 | val Sleeping = Value("Sleeping") |
| 1387 | |
Florian Nücke | bb7684a | 2013-11-07 00:25:46 +0100 | [diff] [blame] | 1388 | /** The computer is up and running, executing Lua code. */ |
| 1389 | val Running = Value("Running") |
Florian Nücke | 86b7b84 | 2013-09-26 09:21:14 +0200 | [diff] [blame] | 1390 | } |
Florian Nücke | f9cd7fd | 2013-09-10 17:26:25 +0200 | [diff] [blame] | 1391 | |
Florian Nücke | ac5fe3c | 2013-11-25 21:54:59 +0100 | [diff] [blame] | 1392 | private val threadPool = ThreadPoolFactory.create("Lua", Settings.get.threads) |
Florian Nücke | 86b7b84 | 2013-09-26 09:21:14 +0200 | [diff] [blame] | 1393 | |
Florian Nücke | f9cd7fd | 2013-09-10 17:26:25 +0200 | [diff] [blame] | 1394 | } |