blob: c819e246a5509619052ec0d072996eed9b575f64 [file] [log] [blame] [raw]
Florian Nückebb491ec2013-09-29 14:15:40 +02001package li.cil.oc.server.component
Florian Nücke61f53b92013-08-20 21:03:38 +02002
Florian Nücke14545122013-10-24 21:39:21 +02003import com.naef.jnlua._
Florian Nückeaf286d12013-10-27 01:37:34 +02004import java.io.{FileNotFoundException, IOException}
Florian Nücke4dfa9ab2013-09-25 17:24:40 +02005import java.util.logging.Level
Florian Nücke90bbdc22013-10-02 15:11:49 +02006import li.cil.oc.api
Florian Nücke730c8f02013-11-01 01:12:55 +01007import li.cil.oc.api.network._
Florian Nückec1fd2c02013-09-29 14:57:05 +02008import li.cil.oc.common.tileentity
Florian Nücke730c8f02013-11-01 01:12:55 +01009import li.cil.oc.server
Florian Nückecbf17602013-12-01 17:16:58 +010010import li.cil.oc.server.PacketSender
Florian Nückec1fd2c02013-09-29 14:57:05 +020011import li.cil.oc.util.ExtendedLuaState.extendLuaState
Florian Nückef135d862013-11-17 12:54:58 +010012import li.cil.oc.util.ExtendedNBT._
Florian Nückeac5fe3c2013-11-25 21:54:59 +010013import li.cil.oc.util.{ThreadPoolFactory, GameTimeFormatter, LuaStateFactory}
14import li.cil.oc.{OpenComputers, Settings}
Florian Nücke6e1ba982013-11-16 19:51:56 +010015import net.minecraft.entity.player.EntityPlayer
Florian Nücke4e820da2013-08-29 22:34:41 +020016import net.minecraft.nbt._
Florian Nückebccee0c2013-11-08 14:08:01 +010017import net.minecraft.server.MinecraftServer
Florian Nücke9ab4c5c2013-12-15 16:18:16 +010018import net.minecraft.server.integrated.IntegratedServer
Florian Nücke0930f892013-09-23 01:30:57 +020019import scala.Array.canBuildFrom
Florian Nücke4dfa9ab2013-09-25 17:24:40 +020020import scala.Some
Florian Nückeaf286d12013-10-27 01:37:34 +020021import scala.collection.convert.WrapAsScala._
22import scala.collection.mutable
Florian Nücke1fd375f2013-10-29 22:43:42 +010023import scala.math.ScalaNumber
Florian Nückeeb3a8e12013-11-07 03:30:17 +010024import scala.runtime.BoxedUnit
Florian Nücke61f53b92013-08-20 21:03:38 +020025
Florian Nückef135d862013-11-17 12:54:58 +010026class Computer(val owner: tileentity.Computer) extends ManagedComponent with Context with Runnable {
Florian Nücke6e1ba982013-11-16 19:51:56 +010027 val node = api.Network.newNode(this, Visibility.Network).
28 withComponent("computer", Visibility.Neighbors).
Florian Nücke0a832862013-12-11 00:49:06 +010029 withConnector(if (isRobot) Settings.get.bufferRobot + 30 * Settings.get.bufferPerLevel else Settings.get.bufferComputer).
Florian Nücke6e1ba982013-11-16 19:51:56 +010030 create()
31
Florian Nückeac72c272013-11-13 17:17:19 +010032 val rom = Option(api.FileSystem.asManagedEnvironment(api.FileSystem.
Florian Nückeac5fe3c2013-11-25 21:54:59 +010033 fromClass(OpenComputers.getClass, Settings.resourceDomain, "lua/rom"), "rom"))
Florian Nückeac72c272013-11-13 17:17:19 +010034
Florian Nücke083e7c22013-11-29 22:30:10 +010035 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ückeac72c272013-11-13 17:17:19 +010039
Florian Nückebb7684a2013-11-07 00:25:46 +010040 private val state = mutable.Stack(Computer.State.Stopped)
Florian Nücke0327cec2013-08-31 17:58:17 +020041
Florian Nücke0927f072013-10-03 22:33:48 +020042 private var lua: LuaState = null
Florian Nücked24f7262013-09-28 17:32:23 +020043
Florian Nücke0927f072013-10-03 22:33:48 +020044 private var kernelMemory = 0
45
Florian Nückeaf286d12013-10-27 01:37:34 +020046 private val components = mutable.Map.empty[String, String]
47
Florian Nückefe446e92013-10-30 00:50:13 +010048 private val addedComponents = mutable.Set.empty[Component]
49
Florian Nückecbf17602013-12-01 17:16:58 +010050 private val _users = mutable.Set.empty[String]
Florian Nückebccee0c2013-11-08 14:08:01 +010051
Florian Nückeaf286d12013-10-27 01:37:34 +020052 private val signals = new mutable.Queue[Computer.Signal]
Florian Nücke94593662013-10-09 16:38:21 +020053
Florian Nückee286fb72013-11-15 18:20:25 +010054 private val callCounts = mutable.Map.empty[String, mutable.Map[String, Int]]
55
Florian Nücked1094c02013-12-09 22:01:22 +010056 private val ramScale = if (LuaStateFactory.is64Bit) Settings.get.ramScaleFor64Bit else 1.0
57
Florian Nücke4e820da2013-08-29 22:34:41 +020058 // ----------------------------------------------------------------------- //
Florian Nücke0927f072013-10-03 22:33:48 +020059
Florian Nücked363d032013-10-06 18:16:03 +020060 private var timeStarted = 0L // Game-world time [ms] for os.uptime().
Florian Nücke0927f072013-10-03 22:33:48 +020061
62 private var worldTime = 0L // Game-world time for os.time().
63
Florian Nücked363d032013-10-06 18:16:03 +020064 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ücke9ab4c5c2013-12-15 16:18:16 +010068 private var remainIdle = 0 // Ticks left to sleep before resuming.
Florian Nücke0927f072013-10-03 22:33:48 +020069
Florian Nücke827344b2013-11-19 16:20:11 +010070 private var remainingPause = 0 // Ticks left to wait before resuming.
71
Florian Nückecbf17602013-12-01 17:16:58 +010072 private var usersChanged = false // Send updated users list to clients?
73
Florian Nücked363d032013-10-06 18:16:03 +020074 private var message: Option[String] = None // For error messages.
Florian Nücke0927f072013-10-03 22:33:48 +020075
Florian Nücke4e820da2013-08-29 22:34:41 +020076 // ----------------------------------------------------------------------- //
77
Florian Nücked4c19342013-11-22 17:58:22 +010078 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ücked1094c02013-12-09 22:01:22 +010083 l.setTotalMemory(kernelMemory + math.ceil(owner.installedMemory * ramScale).toInt)
Florian Nücked4c19342013-11-22 17:58:22 +010084 }
85 case _ =>
86 }
Florian Nücke41c1f402013-09-29 17:24:32 +020087
Florian Nücke39b2c972013-11-09 23:38:17 +010088 def lastError = message
89
Florian Nückecbf17602013-12-01 17:16:58 +010090 def users = _users.synchronized(_users.toArray)
91
Florian Nückeba1f66e2013-11-19 03:29:57 +010092 def isRobot = false
93
Florian Nückea845ce12013-12-11 13:25:34 +010094 private val cost = (if (isRobot) Settings.get.robotCost else Settings.get.computerCost) * Settings.get.tickFrequency
Florian Nücke2de86222013-12-09 02:56:53 +010095
Florian Nückef9cd7fd2013-09-10 17:26:25 +020096 // ----------------------------------------------------------------------- //
97
Florian Nücke6e1ba982013-11-16 19:51:56 +010098 def address = node.address
Florian Nücke0927f072013-10-03 22:33:48 +020099
Florian Nücke9af20232013-11-26 03:05:16 +0100100 def canInteract(player: String) = !Settings.get.canComputersBeOwned ||
Florian Nückecbf17602013-12-01 17:16:58 +0100101 _users.synchronized(_users.isEmpty || _users.contains(player)) ||
Florian Nückebccee0c2013-11-08 14:08:01 +0100102 MinecraftServer.getServer.isSinglePlayer ||
103 MinecraftServer.getServer.getConfigurationManager.isPlayerOpped(player)
Florian Nücke827344b2013-11-19 16:20:11 +0100104
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ücke407d2512013-12-09 01:32:31 +0100126 val ticksToPause = math.max((seconds * 20).toInt, 0)
Florian Nücke827344b2013-11-19 16:20:11 +0100127 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ücke5aa9ad42013-11-22 01:12:57 +0100136 assert(!state.contains(Computer.State.Paused))
Florian Nücke827344b2013-11-19 16:20:11 +0100137 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ückebccee0c2013-11-08 14:08:01 +0100159
Florian Nücke6e1ba982013-11-16 19:51:56 +0100160 def signal(name: String, args: AnyRef*) = state.synchronized(state.top match {
Florian Nücke0927f072013-10-03 22:33:48 +0200161 case Computer.State.Stopped | Computer.State.Stopping => false
Florian Nückebb7684a2013-11-07 00:25:46 +0100162 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ücke0927f072013-10-03 22:33:48 +0200184 })
Florian Nückefb6b92b2013-11-28 02:52:42 +0100185
Florian Nückebccee0c2013-11-08 14:08:01 +0100186 // ----------------------------------------------------------------------- //
Florian Nücke61f53b92013-08-20 21:03:38 +0200187
Florian Nücke5f6a9cf2013-11-17 22:06:24 +0100188 @LuaCallback("start")
189 def start(context: Context, args: Arguments): Array[AnyRef] =
Florian Nückeeee27e42013-12-07 15:15:38 +0100190 result(!isPaused && start())
Florian Nücke5f6a9cf2013-11-17 22:06:24 +0100191
192 @LuaCallback("stop")
193 def stop(context: Context, args: Arguments): Array[AnyRef] =
Florian Nücke827344b2013-11-19 16:20:11 +0100194 result(stop())
Florian Nücke5f6a9cf2013-11-17 22:06:24 +0100195
196 @LuaCallback(value = "isRunning", direct = true)
197 def isRunning(context: Context, args: Arguments): Array[AnyRef] =
Florian Nücke827344b2013-11-19 16:20:11 +0100198 result(isRunning)
Florian Nücke5f6a9cf2013-11-17 22:06:24 +0100199
200 // ----------------------------------------------------------------------- //
201
Florian Nücke2de86222013-12-09 02:56:53 +0100202 override val canUpdate = true
203
Florian Nücke6c8210b2013-12-12 02:11:30 +0100204 override def update() = if (state.synchronized(state.top != Computer.State.Stopped)) {
Florian Nückefe446e92013-10-30 00:50:13 +0100205 // 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ücke2a48cb22013-11-07 17:02:25 +0100215 // Update world time for time().
216 worldTime = owner.world.getWorldTime
Florian Nücke0927f072013-10-03 22:33:48 +0200217
Florian Nücke827344b2013-11-19 16:20:11 +0100218 // We can have rollbacks from '/time set'. Avoid getting negative uptimes.
Florian Nücke407d2512013-12-09 01:32:31 +0100219 timeStarted = math.min(timeStarted, worldTime)
Florian Nücke9ab4c5c2013-12-15 16:18:16 +0100220
221 if (remainIdle > 0) {
222 remainIdle -= 1
223 }
Florian Nücke827344b2013-11-19 16:20:11 +0100224
225 // Reset direct call limits.
Florian Nücke407d2512013-12-09 01:32:31 +0100226 callCounts.synchronized(if (callCounts.size > 0) callCounts.clear())
Florian Nückee286fb72013-11-15 18:20:25 +0100227
Florian Nücke6e1ba982013-11-16 19:51:56 +0100228 // Make sure we have enough power.
Florian Nücke2de86222013-12-09 02:56:53 +0100229 if (worldTime % Settings.get.tickFrequency == 0) {
Florian Nücke407d2512013-12-09 01:32:31 +0100230 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ücke9ab4c5c2013-12-15 16:18:16 +0100235 case Computer.State.Sleeping if remainIdle > 0 && signals.isEmpty =>
Florian Nücke407d2512013-12-09 01:32:31 +0100236 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ückecbf17602013-12-01 17:16:58 +0100245
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ücke6e1ba982013-11-16 19:51:56 +0100254
Florian Nückee286fb72013-11-15 18:20:25 +0100255 // 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ückebb7684a2013-11-07 00:25:46 +0100257 state.synchronized(state.top match {
258 // Booting up.
Florian Nücke08a412b2013-12-03 23:42:23 +0100259 case Computer.State.Starting =>
Florian Nückebb7684a2013-11-07 00:25:46 +0100260 verifyComponents()
261 switchTo(Computer.State.Yielded)
Florian Nücke1a20aed2013-10-02 21:28:58 +0200262 // Computer is rebooting.
Florian Nücke08a412b2013-12-03 23:42:23 +0100263 case Computer.State.Restarting =>
Florian Nückec51d5512013-10-28 12:36:10 +0100264 close()
Florian Nückebb7684a2013-11-07 00:25:46 +0100265 tmp.foreach(_.node.remove()) // To force deleting contents.
Florian Nücke6e1ba982013-11-16 19:51:56 +0100266 node.sendToReachable("computer.stopped")
Florian Nücke1a20aed2013-10-02 21:28:58 +0200267 start()
Florian Nückebb7684a2013-11-07 00:25:46 +0100268 // Resume from pauses based on sleep or signal underflow.
Florian Nücke9ab4c5c2013-12-15 16:18:16 +0100269 case Computer.State.Sleeping if remainIdle <= 0 || !signals.isEmpty =>
Florian Nückebb7684a2013-11-07 00:25:46 +0100270 switchTo(Computer.State.Yielded)
Florian Nücked24f7262013-09-28 17:32:23 +0200271 // Resume in case we paused because the game was paused.
Florian Nücke08a412b2013-12-03 23:42:23 +0100272 case Computer.State.Paused =>
Florian Nücke827344b2013-11-19 16:20:11 +0100273 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ücked24f7262013-09-28 17:32:23 +0200281 // Perform a synchronized call (message sending).
Florian Nücke3d343ec2013-12-04 14:11:54 +0100282 case Computer.State.SynchronizedCall =>
Florian Nücked24f7262013-09-28 17:32:23 +0200283 // 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ückec8bcc592013-11-16 02:35:55 +0100287 // 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ücked24f7262013-09-28 17:32:23 +0200292 // We switch into running state, since we'll behave as though the call
293 // were performed from our executor thread.
Florian Nückebb7684a2013-11-07 00:25:46 +0100294 switchTo(Computer.State.Running)
Florian Nücked24f7262013-09-28 17:32:23 +0200295 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ücke827344b2013-11-19 16:20:11 +0100301 // 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ücke5aa9ad42013-11-22 01:12:57 +0100306 state.pop() // Paused
307 state.pop() // Running, no switchTo to avoid new future.
Florian Nücke827344b2013-11-19 16:20:11 +0100308 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ücke90bbdc22013-10-02 15:11:49 +0200312 }
Florian Nücked24f7262013-09-28 17:32:23 +0200313 } 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ücke6e1ba982013-11-16 19:51:56 +0100318 crash("not enough memory")
Florian Nücke1da52f82013-10-01 10:03:01 +0200319 case e: java.lang.Error if e.getMessage == "not enough memory" =>
Florian Nücke6e1ba982013-11-16 19:51:56 +0100320 crash("not enough memory")
Florian Nücke14545122013-10-24 21:39:21 +0200321 case e: Throwable =>
Florian Nücked24f7262013-09-28 17:32:23 +0200322 OpenComputers.log.log(Level.WARNING, "Faulty Lua implementation for synchronized calls.", e)
Florian Nücke6e1ba982013-11-16 19:51:56 +0100323 crash("protocol error")
Florian Nücked24f7262013-09-28 17:32:23 +0200324 }
Florian Nücked24f7262013-09-28 17:32:23 +0200325 case _ => // Nothing special to do, just avoid match errors.
326 })
Florian Nückee286fb72013-11-15 18:20:25 +0100327
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ückee286fb72013-11-15 18:20:25 +0100334 close()
335 rom.foreach(_.node.remove())
336 tmp.foreach(_.node.remove())
Florian Nücke6e1ba982013-11-16 19:51:56 +0100337 node.sendToReachable("computer.stopped")
Florian Nückee286fb72013-11-15 18:20:25 +0100338 })
339 case _ =>
Florian Nücke6e1ba982013-11-16 19:51:56 +0100340 }
341 }
342
343 // ----------------------------------------------------------------------- //
344
Florian Nückeebb53902013-11-21 00:21:56 +0100345 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ücke9af20232013-11-26 03:05:16 +0100350 if (canInteract(player.getCommandSenderName))
Florian Nückeebb53902013-11-21 00:21:56 +0100351 signal(name, Seq(message.source.address) ++ args: _*)
352 case _ =>
353 }
354 }
355
Florian Nückef135d862013-11-17 12:54:58 +0100356 override def onConnect(node: Node) {
Florian Nücke6e1ba982013-11-16 19:51:56 +0100357 if (node == this.node) {
Florian Nücke5f6a9cf2013-11-17 22:06:24 +0100358 components += this.node.address -> this.node.name
Florian Nücke6e1ba982013-11-16 19:51:56 +0100359 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ücke827344b2013-11-19 16:20:11 +0100368 // For computers, to generate the components in their inventory.
369 owner.onConnect(node)
Florian Nücke6e1ba982013-11-16 19:51:56 +0100370 }
371
Florian Nückef135d862013-11-17 12:54:58 +0100372 override def onDisconnect(node: Node) {
Florian Nücke6e1ba982013-11-16 19:51:56 +0100373 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ücke827344b2013-11-19 16:20:11 +0100384 // For computers, to save the components in their inventory.
385 owner.onDisconnect(node)
Florian Nücke6e1ba982013-11-16 19:51:56 +0100386 }
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ücke5f6a9cf2013-11-17 22:06:24 +0100398 components.synchronized(components -= component.address)
Florian Nücke6e1ba982013-11-16 19:51:56 +0100399 signal("component_removed", component.address, component.name)
400 }
401 addedComponents -= component
402 }
403
404 private def processAddedComponents() {
Florian Nücke407d2512013-12-09 01:32:31 +0100405 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ücke5f6a9cf2013-11-17 22:06:24 +0100414 }
Florian Nücke6e1ba982013-11-16 19:51:56 +0100415 }
Florian Nücke407d2512013-12-09 01:32:31 +0100416 addedComponents.clear()
Florian Nücke6e1ba982013-11-16 19:51:56 +0100417 }
Florian Nücke6e1ba982013-11-16 19:51:56 +0100418 }
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ückee286fb72013-11-15 18:20:25 +0100432 }
Florian Nücke61f53b92013-08-20 21:03:38 +0200433 }
434
Florian Nückef9cd7fd2013-09-10 17:26:25 +0200435 // ----------------------------------------------------------------------- //
Florian Nücke0327cec2013-08-31 17:58:17 +0200436
Florian Nückef135d862013-11-17 12:54:58 +0100437 override def load(nbt: NBTTagCompound) = this.synchronized {
Florian Nückebb7684a2013-11-07 00:25:46 +0100438 assert(state.top == Computer.State.Stopped)
Florian Nückecbf17602013-12-01 17:16:58 +0100439 assert(_users.isEmpty)
Florian Nückef135d862013-11-17 12:54:58 +0100440 assert(signals.isEmpty)
Florian Nückebb7684a2013-11-07 00:25:46 +0100441 state.clear()
Florian Nücke61f53b92013-08-20 21:03:38 +0200442
Florian Nückef135d862013-11-17 12:54:58 +0100443 super.load(nbt)
Florian Nückebccee0c2013-11-08 14:08:01 +0100444
Florian Nücke0f0418b2013-11-22 02:15:14 +0100445 state.pushAll(nbt.getTagList("state").iterator[NBTTagInt].reverse.map(s => Computer.State(s.data)))
Florian Nückecbf17602013-12-01 17:16:58 +0100446 nbt.getTagList("users").foreach[NBTTagString](u => _users += u.data)
Florian Nückef135d862013-11-17 12:54:58 +0100447
448 if (state.size > 0 && state.top != Computer.State.Stopped && init()) {
Florian Nücked24f7262013-09-28 17:32:23 +0200449 // Unlimit memory use while unpersisting.
Florian Nücke0327cec2013-08-31 17:58:17 +0200450 lua.setTotalMemory(Integer.MAX_VALUE)
Florian Nückeaf286d12013-10-27 01:37:34 +0200451
Florian Nücke0327cec2013-08-31 17:58:17 +0200452 try {
Florian Nücked24f7262013-09-28 17:32:23 +0200453 // 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ückef135d862013-11-17 12:54:58 +0100457 unpersist(nbt.getByteArray("kernel"))
Florian Nückebb7684a2013-11-07 00:25:46 +0100458 if (!lua.isThread(1)) {
Florian Nücked24f7262013-09-28 17:32:23 +0200459 // 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ückebb7684a2013-11-07 00:25:46 +0100461 throw new IllegalArgumentException("Invalid kernel.")
Florian Nücked24f7262013-09-28 17:32:23 +0200462 }
Florian Nückebb7684a2013-11-07 00:25:46 +0100463 if (state.contains(Computer.State.SynchronizedCall) || state.contains(Computer.State.SynchronizedReturn)) {
Florian Nückef135d862013-11-17 12:54:58 +0100464 unpersist(nbt.getByteArray("stack"))
Florian Nückebb7684a2013-11-07 00:25:46 +0100465 if (!(if (state.contains(Computer.State.SynchronizedCall)) lua.isFunction(2) else lua.isTable(2))) {
Florian Nücked24f7262013-09-28 17:32:23 +0200466 // Same as with the above, should not really happen normally, but
467 // could for the same reasons.
Florian Nückebb7684a2013-11-07 00:25:46 +0100468 throw new IllegalArgumentException("Invalid stack.")
Florian Nückea5fdb982013-08-31 20:32:03 +0200469 }
Florian Nückea5fdb982013-08-31 20:32:03 +0200470 }
Florian Nücke4e820da2013-08-29 22:34:41 +0200471
Florian Nücke5f6a9cf2013-11-17 22:06:24 +0100472 components ++= nbt.getTagList("components").iterator[NBTTagCompound].map(c =>
Florian Nückef135d862013-11-17 12:54:58 +0100473 c.getString("address") -> c.getString("name"))
Florian Nückeaf286d12013-10-27 01:37:34 +0200474
Florian Nückef135d862013-11-17 12:54:58 +0100475 signals ++= nbt.getTagList("signals").iterator[NBTTagCompound].map(signalNbt => {
Florian Nücked24f7262013-09-28 17:32:23 +0200476 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ückeaf286d12013-10-27 01:37:34 +0200484 case tag: NBTTagByteArray => tag.byteArray
485 case _ => Unit
Florian Nücked24f7262013-09-28 17:32:23 +0200486 }.toArray)
Florian Nückeaf286d12013-10-27 01:37:34 +0200487 })
Florian Nücked24f7262013-09-28 17:32:23 +0200488
Florian Nückef135d862013-11-17 12:54:58 +0100489 rom.foreach(rom => rom.load(nbt.getCompoundTag("rom")))
490 tmp.foreach(tmp => tmp.load(nbt.getCompoundTag("tmp")))
Florian Nücke0a832862013-12-11 00:49:06 +0100491 kernelMemory = (nbt.getInteger("kernelMemory") * ramScale).toInt
Florian Nückef135d862013-11-17 12:54:58 +0100492 timeStarted = nbt.getLong("timeStarted")
493 cpuTime = nbt.getLong("cpuTime")
Florian Nücke827344b2013-11-19 16:20:11 +0100494 remainingPause = nbt.getInteger("remainingPause")
Florian Nückef135d862013-11-17 12:54:58 +0100495 if (nbt.hasKey("message")) {
496 message = Some(nbt.getString("message"))
Florian Nückeaf286d12013-10-27 01:37:34 +0200497 }
Florian Nücked24f7262013-09-28 17:32:23 +0200498
Florian Nücke4a664bb2013-10-24 18:34:15 +0200499 // Limit memory again.
Florian Nücke41c1f402013-09-29 17:24:32 +0200500 recomputeMemory()
Florian Nücked24f7262013-09-28 17:32:23 +0200501
Florian Nücke1b571932013-11-04 13:53:43 +0100502 // Delay execution for a second to allow the world around us to settle.
Florian Nückeac5fe3c2013-11-25 21:54:59 +0100503 pause(Settings.get.startupDelay)
Florian Nücked24f7262013-09-28 17:32:23 +0200504 } catch {
Florian Nücke3d343ec2013-12-04 14:11:54 +0100505 case e: LuaRuntimeException =>
Florian Nückeaf286d12013-10-27 01:37:34 +0200506 OpenComputers.log.warning("Could not unpersist computer.\n" + e.toString + "\tat " + e.getLuaStackTrace.mkString("\n\tat "))
Florian Nückebb7684a2013-11-07 00:25:46 +0100507 state.push(Computer.State.Stopping)
Florian Nücke4e820da2013-08-29 22:34:41 +0200508 }
Florian Nücked24f7262013-09-28 17:32:23 +0200509 }
Florian Nückebb7684a2013-11-07 00:25:46 +0100510 else close() // Clean up in case we got a weird state stack.
Florian Nücked24f7262013-09-28 17:32:23 +0200511 }
512
Florian Nückef135d862013-11-17 12:54:58 +0100513 override def save(nbt: NBTTagCompound): Unit = this.synchronized {
Florian Nückebb7684a2013-11-07 00:25:46 +0100514 assert(state.top != Computer.State.Running) // Lock on 'this' should guarantee this.
Florian Nückef135d862013-11-17 12:54:58 +0100515
Florian Nückec52e18a2013-11-26 14:35:14 +0100516 // Make sure we don't continue running until everything has saved.
517 pause(0.05)
518
Florian Nückef135d862013-11-17 12:54:58 +0100519 super.save(nbt)
Florian Nückefe446e92013-10-30 00:50:13 +0100520
Florian Nückebb7684a2013-11-07 00:25:46 +0100521 // Make sure the component list is up-to-date.
Florian Nückefe446e92013-10-30 00:50:13 +0100522 processAddedComponents()
Florian Nücked24f7262013-09-28 17:32:23 +0200523
Florian Nückef135d862013-11-17 12:54:58 +0100524 nbt.setNewTagList("state", state.map(_.id))
Florian Nückecbf17602013-12-01 17:16:58 +0100525 nbt.setNewTagList("users", _users)
Florian Nückebb7684a2013-11-07 00:25:46 +0100526
527 if (state.top != Computer.State.Stopped) {
Florian Nückeaf286d12013-10-27 01:37:34 +0200528 // Unlimit memory while persisting.
529 lua.setTotalMemory(Integer.MAX_VALUE)
Florian Nücked24f7262013-09-28 17:32:23 +0200530
Florian Nückeaf286d12013-10-27 01:37:34 +0200531 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ückef135d862013-11-17 12:54:58 +0100535 nbt.setByteArray("kernel", persist(1))
Florian Nückeaf286d12013-10-27 01:37:34 +0200536 // 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ückebb7684a2013-11-07 00:25:46 +0100538 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ückef135d862013-11-17 12:54:58 +0100540 nbt.setByteArray("stack", persist(2))
Florian Nücked24f7262013-09-28 17:32:23 +0200541 }
Florian Nücked24f7262013-09-28 17:32:23 +0200542
Florian Nückeaf286d12013-10-27 01:37:34 +0200543 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ückef135d862013-11-17 12:54:58 +0100550 nbt.setTag("components", componentsNbt)
Florian Nückeaf286d12013-10-27 01:37:34 +0200551
552 val signalsNbt = new NBTTagList()
553 for (s <- signals.iterator) {
554 val signalNbt = new NBTTagCompound()
555 signalNbt.setString("name", s.name)
Florian Nückef135d862013-11-17 12:54:58 +0100556 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ückeaf286d12013-10-27 01:37:34 +0200566 signalsNbt.appendTag(signalNbt)
567 }
Florian Nückef135d862013-11-17 12:54:58 +0100568 nbt.setTag("signals", signalsNbt)
Florian Nückeaf286d12013-10-27 01:37:34 +0200569
Florian Nückef135d862013-11-17 12:54:58 +0100570 rom.foreach(rom => nbt.setNewCompoundTag("rom", rom.save))
571 tmp.foreach(tmp => nbt.setNewCompoundTag("tmp", tmp.save))
Florian Nückeaf286d12013-10-27 01:37:34 +0200572
Florian Nücke0a832862013-12-11 00:49:06 +0100573 nbt.setInteger("kernelMemory", math.ceil(kernelMemory / ramScale).toInt)
Florian Nückef135d862013-11-17 12:54:58 +0100574 nbt.setLong("timeStarted", timeStarted)
575 nbt.setLong("cpuTime", cpuTime)
Florian Nücke827344b2013-11-19 16:20:11 +0100576 nbt.setInteger("remainingPause", remainingPause)
Florian Nückef135d862013-11-17 12:54:58 +0100577 message.foreach(nbt.setString("message", _))
Florian Nückeaf286d12013-10-27 01:37:34 +0200578 } catch {
Florian Nücke3d343ec2013-12-04 14:11:54 +0100579 case e: LuaRuntimeException =>
Florian Nückeaf286d12013-10-27 01:37:34 +0200580 OpenComputers.log.warning("Could not persist computer.\n" + e.toString + "\tat " + e.getLuaStackTrace.mkString("\n\tat "))
Florian Nückef135d862013-11-17 12:54:58 +0100581 nbt.removeTag("state")
Florian Nückeaf286d12013-10-27 01:37:34 +0200582 }
583
Florian Nücke4a664bb2013-10-24 18:34:15 +0200584 // Limit memory again.
585 recomputeMemory()
Florian Nücked24f7262013-09-28 17:32:23 +0200586 }
587 }
Florian Nücke4e820da2013-08-29 22:34:41 +0200588
Florian Nücke2b1d4102013-09-24 05:21:59 +0200589 private def persist(index: Int): Array[Byte] = {
Florian Nücked92257d2013-11-01 19:56:35 +0100590 lua.getGlobal("eris") /* ... eris */
591 lua.getField(-1, "persist") /* ... eris persist */
Florian Nücke86b7b842013-09-26 09:21:14 +0200592 if (lua.isFunction(-1)) {
Florian Nücked92257d2013-11-01 19:56:35 +0100593 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ücke86b7b842013-09-26 09:21:14 +0200602 if (lua.isString(-1)) {
Florian Nücked92257d2013-11-01 19:56:35 +0100603 // ... eris str
Florian Nücke0327cec2013-08-31 17:58:17 +0200604 val result = lua.toByteArray(-1)
Florian Nücked92257d2013-11-01 19:56:35 +0100605 lua.pop(2) // ...
Florian Nücke0327cec2013-08-31 17:58:17 +0200606 return result
Florian Nücked92257d2013-11-01 19:56:35 +0100607 } // ... eris :(
608 } // ... eris :(
609 lua.pop(2) // ...
Florian Nücke6bcfab22013-09-22 16:59:37 +0200610 Array[Byte]()
Florian Nücke0327cec2013-08-31 17:58:17 +0200611 }
612
613 private def unpersist(value: Array[Byte]): Boolean = {
Florian Nücked92257d2013-11-01 19:56:35 +0100614 lua.getGlobal("eris") // ... eris
615 lua.getField(-1, "unpersist") // ... eris unpersist
Florian Nücke86b7b842013-09-26 09:21:14 +0200616 if (lua.isFunction(-1)) {
Florian Nücked92257d2013-11-01 19:56:35 +0100617 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ücke0327cec2013-08-31 17:58:17 +0200623 } // ... :(
Florian Nücked92257d2013-11-01 19:56:35 +0100624 lua.pop(1)
625 false
Florian Nücke0327cec2013-08-31 17:58:17 +0200626 }
627
Florian Nückef9cd7fd2013-09-10 17:26:25 +0200628 // ----------------------------------------------------------------------- //
629
630 private def init(): Boolean = {
Florian Nücked8865272013-10-29 23:49:28 +0100631 // 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ücke3d343ec2013-12-04 14:11:54 +0100646 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ücked8865272013-10-29 23:49:28 +0100652 lua.rawSet(-2, index + 1)
653 count = count + 1
Florian Nücked8865272013-10-29 23:49:28 +0100654 }
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ückeeb3a8e12013-11-07 03:30:17 +0100661 case null | Unit | _: BoxedUnit => lua.pushNil()
Florian Nücked8865272013-10-29 23:49:28 +0100662 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ücke4a664bb2013-10-24 18:34:15 +0200681 // Reset error state.
682 message = None
683
Florian Nücke0327cec2013-08-31 17:58:17 +0200684 // 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ücke87892c32013-09-14 16:47:21 +0200687 LuaStateFactory.createState() match {
688 case None =>
Florian Nücke86b7b842013-09-26 09:21:14 +0200689 lua = null
690 return false
Florian Nücke6bcfab22013-09-22 16:59:37 +0200691 case Some(value) => lua = value
Florian Nücke87892c32013-09-14 16:47:21 +0200692 }
Florian Nücke61f53b92013-08-20 21:03:38 +0200693
Florian Nücke5aad6d62013-11-01 04:30:07 +0100694 // 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ücke6e1ba982013-11-16 19:51:56 +0100696 if (node.network != null) {
697 rom.foreach(rom => node.connect(rom.node))
698 tmp.foreach(tmp => node.connect(tmp.node))
Florian Nückea7ec5c02013-10-27 05:33:33 +0100699 }
700
Florian Nücke0327cec2013-08-31 17:58:17 +0200701 try {
Florian Nücke4e820da2013-08-29 22:34:41 +0200702 // Push a couple of functions that override original Lua API functions or
Florian Nückef9cd7fd2013-09-10 17:26:25 +0200703 // that add new functionality to it.
Florian Nückef9cd7fd2013-09-10 17:26:25 +0200704 lua.getGlobal("os")
Florian Nücke4e820da2013-08-29 22:34:41 +0200705
706 // Custom os.clock() implementation returning the time the computer has
Florian Nücked363d032013-10-06 18:16:03 +0200707 // been actively running, instead of the native library...
Florian Nückebb491ec2013-09-29 14:15:40 +0200708 lua.pushScalaFunction(lua => {
Florian Nücked363d032013-10-06 18:16:03 +0200709 lua.pushNumber((cpuTime + (System.nanoTime() - cpuStart)) * 10e-10)
Florian Nücke5aea2ef2013-09-22 23:20:30 +0200710 1
Florian Nückebb491ec2013-09-29 14:15:40 +0200711 })
Florian Nücke4e820da2013-08-29 22:34:41 +0200712 lua.setField(-2, "clock")
Florian Nückef9cd7fd2013-09-10 17:26:25 +0200713
Florian Nücke2a48cb22013-11-07 17:02:25 +0100714 // 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ückef9cd7fd2013-09-10 17:26:25 +0200758 // Return ingame time for os.time().
Florian Nückebb491ec2013-09-29 14:15:40 +0200759 lua.pushScalaFunction(lua => {
Florian Nücke5aea2ef2013-09-22 23:20:30 +0200760 // 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ücke2a48cb22013-11-07 17:02:25 +0100763 // timestamp = (time + 6000) * 60[kh] * 60[km] / 1000[s]
764 lua.pushNumber((worldTime + 6000) * 60 * 60 / 1000)
Florian Nücke5aea2ef2013-09-22 23:20:30 +0200765 1
Florian Nückebb491ec2013-09-29 14:15:40 +0200766 })
Florian Nückef9cd7fd2013-09-10 17:26:25 +0200767 lua.setField(-2, "time")
Florian Nücked363d032013-10-06 18:16:03 +0200768
Florian Nückec54f2852013-12-06 15:44:16 +0100769 // 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ücke3a87ecd2013-12-20 19:53:57 +0100776 // Allow getting the real world time for timeouts.
Florian Nückec54f2852013-12-06 15:44:16 +0100777 lua.pushScalaFunction(lua => {
778 lua.pushNumber(System.currentTimeMillis() / 1000.0)
779 1
780 })
781 lua.setField(-2, "realTime")
782
Florian Nücked363d032013-10-06 18:16:03 +0200783 // 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ücke3a87ecd2013-12-20 19:53:57 +0100786 // want uptime() to return real seconds, though, we'll divide it
Florian Nücked363d032013-10-06 18:16:03 +0200787 // accordingly.
788 lua.pushNumber((worldTime - timeStarted) / 20.0)
789 1
790 })
791 lua.setField(-2, "uptime")
Florian Nücke0327cec2013-08-31 17:58:17 +0200792
Florian Nücke12da32b2013-09-27 07:12:33 +0200793 // Allow the computer to figure out its own id in the component network.
Florian Nückebb491ec2013-09-29 14:15:40 +0200794 lua.pushScalaFunction(lua => {
Florian Nücke6e1ba982013-11-16 19:51:56 +0100795 Option(node.address) match {
Florian Nücke59febb02013-10-01 22:10:54 +0200796 case None => lua.pushNil()
797 case Some(address) => lua.pushString(address)
798 }
Florian Nücke12da32b2013-09-27 07:12:33 +0200799 1
Florian Nückebb491ec2013-09-29 14:15:40 +0200800 })
Florian Nücke12da32b2013-09-27 07:12:33 +0200801 lua.setField(-2, "address")
Florian Nücke4e820da2013-08-29 22:34:41 +0200802
Florian Nückeba1f66e2013-11-19 03:29:57 +0100803 // 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ücked1094c02013-12-09 22:01:22 +0100813 lua.pushInteger(((lua.getFreeMemory min (lua.getTotalMemory - kernelMemory)) / ramScale).toInt)
Florian Nückeba1f66e2013-11-19 03:29:57 +0100814 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ücked1094c02013-12-09 22:01:22 +0100820 lua.pushInteger(((lua.getTotalMemory - kernelMemory) / ramScale).toInt)
Florian Nückeba1f66e2013-11-19 03:29:57 +0100821 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ücke3a87ecd2013-12-20 19:53:57 +0100831 // And its ROM address.
Florian Nücke90bbdc22013-10-02 15:11:49 +0200832 lua.pushScalaFunction(lua => {
Florian Nückeaf286d12013-10-27 01:37:34 +0200833 rom.foreach(rom => Option(rom.node.address) match {
Florian Nücke90bbdc22013-10-02 15:11:49 +0200834 case None => lua.pushNil()
835 case Some(address) => lua.pushString(address)
Florian Nücke0927f072013-10-03 22:33:48 +0200836 })
Florian Nücke90bbdc22013-10-02 15:11:49 +0200837 1
838 })
839 lua.setField(-2, "romAddress")
840
Florian Nücke94593662013-10-09 16:38:21 +0200841 // And it's /tmp address...
842 lua.pushScalaFunction(lua => {
Florian Nückeaf286d12013-10-27 01:37:34 +0200843 tmp.foreach(tmp => Option(tmp.node.address) match {
Florian Nücke94593662013-10-09 16:38:21 +0200844 case None => lua.pushNil()
845 case Some(address) => lua.pushString(address)
846 })
847 1
848 })
849 lua.setField(-2, "tmpAddress")
Florian Nückebccee0c2013-11-08 14:08:01 +0100850
851 // User management.
852 lua.pushScalaFunction(lua => {
Florian Nückecbf17602013-12-01 17:16:58 +0100853 _users.foreach(lua.pushString)
854 _users.size
Florian Nückebccee0c2013-11-08 14:08:01 +0100855 })
856 lua.setField(-2, "users")
857
858 lua.pushScalaFunction(lua => try {
Florian Nückecbf17602013-12-01 17:16:58 +0100859 if (_users.size >= Settings.get.maxUsers)
Florian Nückebccee0c2013-11-08 14:08:01 +0100860 throw new Exception("too many users")
861
862 val name = lua.checkString(1)
863
Florian Nückecbf17602013-12-01 17:16:58 +0100864 if (_users.contains(name))
Florian Nückebccee0c2013-11-08 14:08:01 +0100865 throw new Exception("user exists")
Florian Nückeac5fe3c2013-11-25 21:54:59 +0100866 if (name.length > Settings.get.maxUsernameLength)
Florian Nückebccee0c2013-11-08 14:08:01 +0100867 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ückecbf17602013-12-01 17:16:58 +0100871 _users.synchronized {
872 _users += name
873 usersChanged = true
874 }
Florian Nückebccee0c2013-11-08 14:08:01 +0100875 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ückecbf17602013-12-01 17:16:58 +0100887 _users.synchronized {
888 val success = _users.remove(name)
889 if (success) {
890 usersChanged = true
891 }
892 lua.pushBoolean(success)
893 }
Florian Nückebccee0c2013-11-08 14:08:01 +0100894 1
895 })
896 lua.setField(-2, "removeUser")
Florian Nücke94593662013-10-09 16:38:21 +0200897
Florian Nücke0a832862013-12-11 00:49:06 +0100898 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ückec54f2852013-12-06 15:44:16 +0100910 // Set the computer table.
911 lua.setGlobal("computer")
Florian Nücke4e820da2013-08-29 22:34:41 +0200912
Florian Nücke0327cec2013-08-31 17:58:17 +0200913 // Until we get to ingame screens we log to Java's stdout.
Florian Nückebb491ec2013-09-29 14:15:40 +0200914 lua.pushScalaFunction(lua => {
Florian Nückea7ec5c02013-10-27 05:33:33 +0100915 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ücke5aea2ef2013-09-22 23:20:30 +0200925 0
Florian Nückebb491ec2013-09-29 14:15:40 +0200926 })
Florian Nücke0327cec2013-08-31 17:58:17 +0200927 lua.setGlobal("print")
928
Florian Nückeaf286d12013-10-27 01:37:34 +0200929 // How long programs may run without yielding before we stop them.
Florian Nückeac5fe3c2013-11-25 21:54:59 +0100930 lua.pushNumber(Settings.get.timeout)
Florian Nückeaf286d12013-10-27 01:37:34 +0200931 lua.setGlobal("timeout")
932
Florian Nückec51d5512013-10-28 12:36:10 +0100933 // Component interaction stuff.
934 lua.newTable()
935
Florian Nückeaf286d12013-10-27 01:37:34 +0200936 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ückefe446e92013-10-30 00:50:13 +0100940 if (filter.isEmpty || name.contains(filter.get)) {
Florian Nückeaf286d12013-10-27 01:37:34 +0200941 lua.pushString(address)
942 lua.pushString(name)
Florian Nückea7ec5c02013-10-27 05:33:33 +0100943 lua.rawSet(-3)
Florian Nückeaf286d12013-10-27 01:37:34 +0200944 }
945 }
946 1
947 })
Florian Nückec51d5512013-10-28 12:36:10 +0100948 lua.setField(-2, "list")
Florian Nückeaf286d12013-10-27 01:37:34 +0200949
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ückec51d5512013-10-28 12:36:10 +0100961 lua.setField(-2, "type")
Florian Nückeaf286d12013-10-27 01:37:34 +0200962
Florian Nückea7ec5c02013-10-27 05:33:33 +0100963 lua.pushScalaFunction(lua => {
Florian Nücke6e1ba982013-11-16 19:51:56 +0100964 Option(node.network.node(lua.checkString(1))) match {
Florian Nücke5f6a9cf2013-11-17 22:06:24 +0100965 case Some(component: server.network.Component) if component.canBeSeenFrom(node) || component == node =>
Florian Nückea7ec5c02013-10-27 05:33:33 +0100966 lua.newTable()
Florian Nücke730c8f02013-11-01 01:12:55 +0100967 for (method <- component.methods()) {
968 lua.pushString(method)
Florian Nückee286fb72013-11-15 18:20:25 +0100969 lua.pushBoolean(component.isDirect(method))
Florian Nücke730c8f02013-11-01 01:12:55 +0100970 lua.rawSet(-3)
Florian Nückea7ec5c02013-10-27 05:33:33 +0100971 }
972 1
973 case _ =>
974 lua.pushNil()
975 lua.pushString("no such component")
976 2
977 }
978 })
Florian Nückec51d5512013-10-28 12:36:10 +0100979 lua.setField(-2, "methods")
Florian Nücke48cf0122013-09-23 15:08:27 +0200980
Florian Nückee286fb72013-11-15 18:20:25 +0100981 class LimitReachedException extends Exception
982
Florian Nückebb491ec2013-09-29 14:15:40 +0200983 lua.pushScalaFunction(lua => {
Florian Nückeaf286d12013-10-27 01:37:34 +0200984 val address = lua.checkString(1)
985 val method = lua.checkString(2)
986 val args = parseArguments(lua, 3)
987 try {
Florian Nücke6e1ba982013-11-16 19:51:56 +0100988 (Option(node.network.node(address)) match {
Florian Nücke5f6a9cf2013-11-17 22:06:24 +0100989 case Some(component: server.network.Component) if component.canBeSeenFrom(node) || component == node =>
Florian Nückee286fb72013-11-15 18:20:25 +0100990 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ücke6e1ba982013-11-16 19:51:56 +01001000 component.invoke(method, this, args: _*)
Florian Nückeaf286d12013-10-27 01:37:34 +02001001 case _ => throw new Exception("no such component")
1002 }) match {
Florian Nücke78a426c2013-10-27 15:06:21 +01001003 case results: Array[_] =>
1004 lua.pushBoolean(true)
Florian Nückeaf286d12013-10-27 01:37:34 +02001005 results.foreach(pushResult(lua, _))
Florian Nücke78a426c2013-10-27 15:06:21 +01001006 1 + results.length
Florian Nückeaf286d12013-10-27 01:37:34 +02001007 case _ =>
Florian Nücke78a426c2013-10-27 15:06:21 +01001008 lua.pushBoolean(true)
1009 1
Florian Nückeaf286d12013-10-27 01:37:34 +02001010 }
1011 } catch {
Florian Nückee286fb72013-11-15 18:20:25 +01001012 case _: LimitReachedException =>
1013 0
Florian Nücke78a426c2013-10-27 15:06:21 +01001014 case e: IllegalArgumentException if e.getMessage != null =>
1015 lua.pushBoolean(false)
1016 lua.pushString(e.getMessage)
Florian Nückeaf286d12013-10-27 01:37:34 +02001017 2
Florian Nücke78a426c2013-10-27 15:06:21 +01001018 case e: Throwable if e.getMessage != null =>
1019 lua.pushBoolean(true)
1020 lua.pushNil()
1021 lua.pushString(e.getMessage)
1022 3
Florian Nücke1fd375f2013-10-29 22:43:42 +01001023 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ücke78a426c2013-10-27 15:06:21 +01001035 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ückecf6a3e32013-11-18 15:09:58 +01001050 case e: Throwable =>
1051 OpenComputers.log.log(Level.WARNING, "Unexpected error in Lua callback.", e)
Florian Nücke78a426c2013-10-27 15:06:21 +01001052 lua.pushBoolean(true)
Florian Nückea7ec5c02013-10-27 05:33:33 +01001053 lua.pushNil()
Florian Nückeaf286d12013-10-27 01:37:34 +02001054 lua.pushString("unknown error")
Florian Nücke78a426c2013-10-27 15:06:21 +01001055 3
Florian Nücke48cf0122013-09-23 15:08:27 +02001056 }
Florian Nückebb491ec2013-09-29 14:15:40 +02001057 })
Florian Nückec51d5512013-10-28 12:36:10 +01001058 lua.setField(-2, "invoke")
1059
1060 lua.setGlobal("component")
Florian Nückeabddfae2013-09-29 11:57:14 +02001061
Florian Nücked92257d2013-11-01 19:56:35 +01001062 initPerms()
Florian Nücke48cf0122013-09-23 15:08:27 +02001063
Florian Nückeac5fe3c2013-11-25 21:54:59 +01001064 lua.load(classOf[Computer].getResourceAsStream(Settings.scriptPath + "kernel.lua"), "=kernel", "t")
Florian Nückeabddfae2013-09-29 11:57:14 +02001065 lua.newThread() // Left as the first value on the stack.
Florian Nücke4e820da2013-08-29 22:34:41 +02001066
Florian Nücke0327cec2013-08-31 17:58:17 +02001067 // Clear any left-over signals from a previous run.
1068 signals.clear()
Florian Nücke90bbdc22013-10-02 15:11:49 +02001069
Florian Nücke4e820da2013-08-29 22:34:41 +02001070 return true
Florian Nücke61f53b92013-08-20 21:03:38 +02001071 }
1072 catch {
Florian Nücke3d343ec2013-12-04 14:11:54 +01001073 case ex: Throwable =>
Florian Nücke86b7b842013-09-26 09:21:14 +02001074 OpenComputers.log.log(Level.WARNING, "Failed initializing computer.", ex)
Florian Nücke4e820da2013-08-29 22:34:41 +02001075 close()
Florian Nücke61f53b92013-08-20 21:03:38 +02001076 }
Florian Nücke6bcfab22013-09-22 16:59:37 +02001077 false
Florian Nücke61f53b92013-08-20 21:03:38 +02001078 }
Florian Nücke4e820da2013-08-29 22:34:41 +02001079
Florian Nücked92257d2013-11-01 19:56:35 +01001080 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ückebb7684a2013-11-07 00:25:46 +01001149 private def close() = state.synchronized(
Florian Nückef135d862013-11-17 12:54:58 +01001150 if (state.size == 0 || state.top != Computer.State.Stopped) {
Florian Nückebb7684a2013-11-07 00:25:46 +01001151 state.clear()
1152 state.push(Computer.State.Stopped)
Florian Nücke9a8da532013-12-07 23:33:26 +01001153 if (lua != null) {
1154 lua.setTotalMemory(Integer.MAX_VALUE)
1155 lua.close()
1156 }
Florian Nücke0327cec2013-08-31 17:58:17 +02001157 lua = null
1158 kernelMemory = 0
1159 signals.clear()
1160 timeStarted = 0
Florian Nücked363d032013-10-06 18:16:03 +02001161 cpuTime = 0
1162 cpuStart = 0
Florian Nücke9ab4c5c2013-12-15 16:18:16 +01001163 remainIdle = 0
Florian Nücke551fd3b2013-09-20 02:31:24 +02001164
1165 // Mark state change in owner, to send it to clients.
Florian Nücke35d8e072013-11-08 19:19:57 +01001166 owner.markAsChanged()
Florian Nücke0327cec2013-08-31 17:58:17 +02001167 })
Florian Nücked363d032013-10-06 18:16:03 +02001168
1169 // ----------------------------------------------------------------------- //
Florian Nücke4e820da2013-08-29 22:34:41 +02001170
Florian Nückebb7684a2013-11-07 00:25:46 +01001171 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ücke9ab4c5c2013-12-15 16:18:16 +01001175 remainIdle = 0
Florian Nückeac5fe3c2013-11-25 21:54:59 +01001176 Computer.threadPool.submit(this)
Florian Nückebb7684a2013-11-07 00:25:46 +01001177 }
Florian Nücke827344b2013-11-19 16:20:11 +01001178
1179 // Mark state change in owner, to send it to clients.
1180 owner.markAsChanged()
1181
Florian Nückebb7684a2013-11-07 00:25:46 +01001182 result
Florian Nücked24f7262013-09-28 17:32:23 +02001183 }
Florian Nücke9ab4c5c2013-12-15 16:18:16 +01001184
1185 private def isGamePaused = !MinecraftServer.getServer.isDedicatedServer && (MinecraftServer.getServer match {
1186 case integrated: IntegratedServer => integrated.getServerListeningThread.isGamePaused
1187 case _ => false
1188 })
Florian Nücked24f7262013-09-28 17:32:23 +02001189
Florian Nücke0327cec2013-08-31 17:58:17 +02001190 // This is a really high level lock that we only use for saving and loading.
Florian Nücke5aea2ef2013-09-22 23:20:30 +02001191 override def run(): Unit = this.synchronized {
Florian Nückebb7684a2013-11-07 00:25:46 +01001192 val enterState = state.synchronized {
Florian Nückec8bcc592013-11-16 02:35:55 +01001193 if (state.top == Computer.State.Stopped ||
1194 state.top == Computer.State.Stopping ||
1195 state.top == Computer.State.Paused) {
1196 return
1197 }
Florian Nücked24f7262013-09-28 17:32:23 +02001198 // See if the game appears to be paused, in which case we also pause.
Florian Nücke9ab4c5c2013-12-15 16:18:16 +01001199 if (isGamePaused) {
Florian Nückebb7684a2013-11-07 00:25:46 +01001200 state.push(Computer.State.Paused)
Florian Nückea5fdb982013-08-31 20:32:03 +02001201 return
1202 }
Florian Nückebb7684a2013-11-07 00:25:46 +01001203 switchTo(Computer.State.Running)
Florian Nücke0327cec2013-08-31 17:58:17 +02001204 }
1205
Florian Nücke4e820da2013-08-29 22:34:41 +02001206 try {
Florian Nückebb7684a2013-11-07 00:25:46 +01001207 // The kernel thread will always be at stack index one.
1208 assert(lua.isThread(1))
1209
Florian Nücke3617b712013-12-03 18:11:32 +01001210 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ücke0327cec2013-08-31 17:58:17 +02001216 // Resume the Lua state and remember the number of results we get.
Florian Nücked363d032013-10-06 18:16:03 +02001217 cpuStart = System.nanoTime()
Florian Nückebb7684a2013-11-07 00:25:46 +01001218 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ücke19963322013-11-24 03:19:34 +01001227 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ücke407d2512013-12-09 01:32:31 +01001239 kernelMemory = math.max(lua.getTotalMemory - lua.getFreeMemory, 1)
Florian Nücke19963322013-11-24 03:19:34 +01001240 recomputeMemory()
Florian Nückebb7684a2013-11-07 00:25:46 +01001241
Florian Nücke19963322013-11-24 03:19:34 +01001242 // Fake zero sleep to avoid stopping if there are no signals.
1243 lua.pushInteger(0)
1244 (1, 0L)
1245 }
Florian Nücke86b7b842013-09-26 09:21:14 +02001246 }
Florian Nückebb7684a2013-11-07 00:25:46 +01001247 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ückeafd383d2013-11-30 21:07:44 +01001261 case s => throw new AssertionError("Running computer from invalid state " + s.toString)
Florian Nücke4e820da2013-08-29 22:34:41 +02001262 }
1263
Florian Nückebb7684a2013-11-07 00:25:46 +01001264 // Keep track of time spent executing the computer.
Florian Nücke1b571932013-11-04 13:53:43 +01001265 cpuTime += runtime
Florian Nücke78a426c2013-10-27 15:06:21 +01001266
Florian Nücked24f7262013-09-28 17:32:23 +02001267 // Check if the kernel is still alive.
Florian Nückebb7684a2013-11-07 00:25:46 +01001268 state.synchronized(if (lua.status(1) == LuaState.YIELD) {
Florian Nücke827344b2013-11-19 16:20:11 +01001269 // 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ücke9ab4c5c2013-12-15 16:18:16 +01001292 if (results == 1 && lua.isNumber(2)) (lua.toNumber(2) * 20).toInt
1293 else Int.MaxValue
Florian Nücke827344b2013-11-19 16:20:11 +01001294 lua.pop(results)
1295 signals.synchronized {
1296 // Immediately check for signals to allow processing more than one
1297 // signal per game tick.
Florian Nücke9ab4c5c2013-12-15 16:18:16 +01001298 if (signals.isEmpty && sleep > 0) {
Florian Nücke827344b2013-11-19 16:20:11 +01001299 switchTo(Computer.State.Sleeping)
Florian Nücke9ab4c5c2013-12-15 16:18:16 +01001300 remainIdle = sleep
Florian Nücke827344b2013-11-19 16:20:11 +01001301 } else {
1302 switchTo(Computer.State.Yielded)
1303 }
Florian Nückebb7684a2013-11-07 00:25:46 +01001304 }
1305 }
Florian Nücke827344b2013-11-19 16:20:11 +01001306 case Computer.State.Paused =>
Florian Nücke5aa9ad42013-11-22 01:12:57 +01001307 state.pop() // Paused
1308 state.pop() // Running, no switchTo to avoid new future.
Florian Nücke827344b2013-11-19 16:20:11 +01001309 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ücke5aa9ad42013-11-22 01:12:57 +01001312 case _ => throw new AssertionError(
1313 "Invalid state in executor post-processing.")
Florian Nückebb7684a2013-11-07 00:25:46 +01001314 }
Florian Nücke0327cec2013-08-31 17:58:17 +02001315 }
Florian Nücke34033962013-11-26 01:34:10 +01001316 // The kernel thread returned. If it threw we'd be in the catch below.
Florian Nücked24f7262013-09-28 17:32:23 +02001317 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ücke6e1ba982013-11-16 19:51:56 +01001326 stop()
Florian Nücked24f7262013-09-28 17:32:23 +02001327 }
1328 else {
Florian Nücke65441612013-10-07 03:02:54 +02001329 lua.setTotalMemory(Int.MaxValue)
Florian Nückeacd56032013-10-07 05:05:51 +02001330 val error = lua.toString(3)
Florian Nücke6e1ba982013-11-16 19:51:56 +01001331 if (error != null) crash(error)
1332 else crash("unknown error")
Florian Nücked24f7262013-09-28 17:32:23 +02001333 }
Florian Nücked24f7262013-09-28 17:32:23 +02001334 })
Florian Nücke4e820da2013-08-29 22:34:41 +02001335 }
1336 catch {
Florian Nücked24f7262013-09-28 17:32:23 +02001337 case e: LuaRuntimeException =>
1338 OpenComputers.log.warning("Kernel crashed. This is a bug!\n" + e.toString + "\tat " + e.getLuaStackTrace.mkString("\n\tat "))
Florian Nücke6e1ba982013-11-16 19:51:56 +01001339 crash("kernel panic")
Florian Nücke14545122013-10-24 21:39:21 +02001340 case e: LuaGcMetamethodException =>
Florian Nücke6e1ba982013-11-16 19:51:56 +01001341 if (e.getMessage != null) crash("kernel panic:\n" + e.getMessage)
1342 else crash("kernel panic:\nerror in garbage collection metamethod")
Florian Nückeabddfae2013-09-29 11:57:14 +02001343 case e: LuaMemoryAllocationException =>
Florian Nücke6e1ba982013-11-16 19:51:56 +01001344 crash("not enough memory")
Florian Nückeabddfae2013-09-29 11:57:14 +02001345 case e: java.lang.Error if e.getMessage == "not enough memory" =>
Florian Nücke6e1ba982013-11-16 19:51:56 +01001346 crash("not enough memory")
Florian Nücke14545122013-10-24 21:39:21 +02001347 case e: Throwable =>
1348 OpenComputers.log.log(Level.WARNING, "Unexpected error in kernel. This is a bug!\n", e)
Florian Nücke6e1ba982013-11-16 19:51:56 +01001349 crash("kernel panic")
Florian Nücke4e820da2013-08-29 22:34:41 +02001350 }
Florian Nücke12da32b2013-09-27 07:12:33 +02001351 }
1352}
1353
1354object Computer {
Florian Nückebb491ec2013-09-29 14:15:40 +02001355
Florian Nücke0327cec2013-08-31 17:58:17 +02001356 /** Signals are messages sent to the Lua state from Java asynchronously. */
1357 private class Signal(val name: String, val args: Array[Any])
Florian Nücke4e820da2013-08-29 22:34:41 +02001358
1359 /** Possible states of the computer, and in particular its executor. */
1360 private object State extends Enumeration {
Florian Nücke0327cec2013-08-31 17:58:17 +02001361 /** The computer is not running right now and there is no Lua state. */
Florian Nücke4e820da2013-08-29 22:34:41 +02001362 val Stopped = Value("Stopped")
Florian Nücke0327cec2013-08-31 17:58:17 +02001363
Florian Nückebb7684a2013-11-07 00:25:46 +01001364 /** Booting up, doing the first run to initialize the kernel and libs. */
1365 val Starting = Value("Starting")
Florian Nücked24f7262013-09-28 17:32:23 +02001366
Florian Nückebb7684a2013-11-07 00:25:46 +01001367 /** Computer is currently rebooting. */
1368 val Restarting = Value("Restarting")
1369
1370 /** The computer is currently shutting down. */
1371 val Stopping = Value("Stopping")
Florian Nücke4e820da2013-08-29 22:34:41 +02001372
Florian Nückea5fdb982013-08-31 20:32:03 +02001373 /** The computer is paused and waiting for the game to resume. */
1374 val Paused = Value("Paused")
1375
Florian Nücke48cf0122013-09-23 15:08:27 +02001376 /** The computer executor is waiting for a synchronized call to be made. */
1377 val SynchronizedCall = Value("SynchronizedCall")
Florian Nücke0327cec2013-08-31 17:58:17 +02001378
Florian Nücke48cf0122013-09-23 15:08:27 +02001379 /** The computer should resume with the result of a synchronized call. */
1380 val SynchronizedReturn = Value("SynchronizedReturn")
Florian Nückea5fdb982013-08-31 20:32:03 +02001381
Florian Nückebb7684a2013-11-07 00:25:46 +01001382 /** The computer will resume as soon as possible. */
1383 val Yielded = Value("Yielded")
Florian Nücke1a20aed2013-10-02 21:28:58 +02001384
Florian Nücke827344b2013-11-19 16:20:11 +01001385 /** The computer is yielding for a longer amount of time. */
1386 val Sleeping = Value("Sleeping")
1387
Florian Nückebb7684a2013-11-07 00:25:46 +01001388 /** The computer is up and running, executing Lua code. */
1389 val Running = Value("Running")
Florian Nücke86b7b842013-09-26 09:21:14 +02001390 }
Florian Nückef9cd7fd2013-09-10 17:26:25 +02001391
Florian Nückeac5fe3c2013-11-25 21:54:59 +01001392 private val threadPool = ThreadPoolFactory.create("Lua", Settings.get.threads)
Florian Nücke86b7b842013-09-26 09:21:14 +02001393
Florian Nückef9cd7fd2013-09-10 17:26:25 +02001394}