diff --git a/src/main/resources/assets/opencomputers/lua/kernel.lua b/src/main/resources/assets/opencomputers/lua/kernel.lua index 6d2f6f2c9..146578dd6 100644 --- a/src/main/resources/assets/opencomputers/lua/kernel.lua +++ b/src/main/resources/assets/opencomputers/lua/kernel.lua @@ -262,7 +262,7 @@ sandbox._G = sandbox -- These functions provide the logic for wrapping and unwrapping (when -- pushed to user code and when pushed back to the host, respectively). local wrapUserdata, wrapSingleUserdata, unwrapUserdata, wrappedUserdataMeta ---[[ + wrappedUserdataMeta = { -- Weak keys, clean up once a proxy is no longer referenced anywhere. __mode="k", @@ -278,7 +278,7 @@ wrappedUserdataMeta = { end } local wrappedUserdata = setmetatable({}, wrappedUserdataMeta) -]] + local function processResult(result) wrapUserdata(result) -- needed for metamethods. if not result[1] then -- error that should be re-thrown. @@ -307,7 +307,7 @@ local function invoke(target, direct, ...) end return processResult(result) end ---[[ + local function udinvoke(f, data, ...) local args = table.pack(...) unwrapUserdata(args) @@ -424,9 +424,6 @@ function unwrapUserdata(values) end unwrapRecursively(values) end -]] -function wrapUserdata(...) return ... end -function unwrapUserdata(...) return ... end ------------------------------------------------------------------------------- diff --git a/src/main/resources/reference.conf b/src/main/resources/reference.conf index 0cc90509e..74ec5211b 100644 --- a/src/main/resources/reference.conf +++ b/src/main/resources/reference.conf @@ -46,6 +46,11 @@ opencomputers { # filled chars to appear (i.e. the block symbol that is used for cursor # blinking for example) on less accurate hardware. fontCharScale: 1.01 + + # The maximum render distance of a hologram projected by a highest tier + # hologram projector when at maximum scale. Render distance is scaled + # down with the actual scale of the hologram. + hologramRenderDistance: 64 } # Computer related settings, concerns server performance and security. @@ -145,25 +150,45 @@ opencomputers { # See also: `canComputersBeOwned`. maxUsernameLength: 32 - # This setting is meant for debugging errors that occur in Lua callbacks. - # Per default, if an error occurs and it has a message set, only the - # message is pushed back to Lua, and that's it. If you encounter weird - # errors or are developing an addon you'll want the stacktrace for those - # errors. Enabling this setting will log them to the game log. This is - # disabled per default to avoid spamming the log with inconsequentual - # exceptions such as IllegalArgumentExceptions and the like. - logCallbackErrors: false - # Whether to delete all contents in the /tmp file system when performing # a 'soft' reboot (i.e. via `computer.shutdown(true)`). The tmpfs will # always be erased when the computer is completely powered off, even if # it crashed. This setting is purely for software-triggered reboots. eraseTmpOnReboot: false - # Forces the use of the LuaJ fallback instead of the native libraries. - # Use this if you have (unfounded) concerns using native libraries or - # experience issues with the native library. Also used for debugging. - forceLuaJ: false + # Debugging related settings. You usually don't want to touch these + # unless asked to do so by a developer. + debug { + # Forces the use of the LuaJ fallback instead of the native libraries. + # Use this if you have concerns using native libraries or experience + # issues with the native library. + forceLuaJ: false + + # This setting is meant for debugging errors that occur in Lua callbacks. + # Per default, if an error occurs and it has a message set, only the + # message is pushed back to Lua, and that's it. If you encounter weird + # errors or are developing an addon you'll want the stacktrace for those + # errors. Enabling this setting will log them to the game log. This is + # disabled per default to avoid spamming the log with inconsequentual + # exceptions such as IllegalArgumentExceptions and the like. + logCallbackErrors: false + + # Disable user data support. This means any otherwise supported + # userdata (implementing the Value interface) will not be pushed + # to the Lua state. + disableUserdata: false + + # Disable computer state persistence. This means that computers will + # automatically be rebooted when loaded after being unloaded, instead + # of resuming with their exection (it also means the state is not even + # saved). Only relevant when using the native library. + disablePersistence: false + + # Disable memory limit enforcement. This means Lua states can + # theoretically use as much memory as they want. Only relevant when + # using the native library. + disableMemoryLimit: false + } } # Robot related settings, what they may do and general balancing. diff --git a/src/main/scala/li/cil/oc/Settings.scala b/src/main/scala/li/cil/oc/Settings.scala index 3eb969585..383e0d438 100644 --- a/src/main/scala/li/cil/oc/Settings.scala +++ b/src/main/scala/li/cil/oc/Settings.scala @@ -22,6 +22,7 @@ class Settings(config: Config) { val robotLabels = config.getBoolean("client.robotLabels") val soundVolume = config.getDouble("client.soundVolume").toFloat max 0 min 2 val fontCharScale = config.getDouble("client.fontCharScale") max 0.5 min 2 + val hologramRenderDistance = config.getDouble("client.hologramRenderDistance") max 0 // ----------------------------------------------------------------------- // // computer @@ -47,10 +48,16 @@ class Settings(config: Config) { val maxUsers = config.getInt("computer.maxUsers") max 0 val maxUsernameLength = config.getInt("computer.maxUsernameLength") max 0 val allowBytecode = config.getBoolean("computer.allowBytecode") - val logLuaCallbackErrors = config.getBoolean("computer.logCallbackErrors") val eraseTmpOnReboot = config.getBoolean("computer.eraseTmpOnReboot") - val forceLuaJ = config.getBoolean("computer.forceLuaJ") - val allowUserdata = false // unstable + + // ----------------------------------------------------------------------- // + // computer.debug + + val logLuaCallbackErrors = config.getBoolean("computer.debug.logCallbackErrors") + val forceLuaJ = config.getBoolean("computer.debug.forceLuaJ") + val allowUserdata = !config.getBoolean("computer.debug.disableUserdata") + val allowPersistence = !config.getBoolean("computer.debug.disablePersistence") + val limitMemory = !config.getBoolean("computer.debug.disableMemoryLimit") // ----------------------------------------------------------------------- // // robot diff --git a/src/main/scala/li/cil/oc/common/tileentity/Hologram.scala b/src/main/scala/li/cil/oc/common/tileentity/Hologram.scala index 7e1f5c372..cb682cb0c 100644 --- a/src/main/scala/li/cil/oc/common/tileentity/Hologram.scala +++ b/src/main/scala/li/cil/oc/common/tileentity/Hologram.scala @@ -274,6 +274,8 @@ class Hologram(var tier: Int) extends traits.Environment with SidedEnvironment w override def shouldRenderInPass(pass: Int) = pass == 1 + override def getMaxRenderDistanceSquared = scale / Settings.hologramMaxScaleByTier.max * Settings.get.hologramRenderDistance * Settings.get.hologramRenderDistance + override def getRenderBoundingBox = AxisAlignedBB.getAABBPool.getAABB(xCoord + 0.5 - 1.5 * scale, yCoord, zCoord - scale, xCoord + 0.5 + 1.5 * scale, yCoord + 0.25 + 2 * scale, zCoord + 0.5 + 1.5 * scale) // ----------------------------------------------------------------------- // diff --git a/src/main/scala/li/cil/oc/common/tileentity/traits/AbstractBusAware.scala b/src/main/scala/li/cil/oc/common/tileentity/traits/AbstractBusAware.scala index 4af11f93e..01b952df6 100644 --- a/src/main/scala/li/cil/oc/common/tileentity/traits/AbstractBusAware.scala +++ b/src/main/scala/li/cil/oc/common/tileentity/traits/AbstractBusAware.scala @@ -9,7 +9,6 @@ import li.cil.oc.util.mods.{Mods, StargateTech2} import net.minecraft.nbt.NBTTagCompound import stargatetech2.api.bus.{IBusInterface, IBusDevice} import stargatetech2.api.StargateTechAPI -import li.cil.oc.server.component.AbstractBus // IMPORTANT: for some reason that is beyond me we cannot implement the // IBusDevice here directly, since we'll get an error if the interface is not @@ -32,7 +31,7 @@ trait AbstractBusAware extends TileEntity with network.Environment { if (isAbstractBusAvailable) { if (isServer) { installedComponents.collect { - case abstractBus: AbstractBus => abstractBus.busInterface + case abstractBus: component.AbstractBusCard => abstractBus.busInterface }.toArray } else fakeInterface.map(_.asInstanceOf[IBusInterface]) diff --git a/src/main/scala/li/cil/oc/server/component/AbstractBus.scala b/src/main/scala/li/cil/oc/server/component/AbstractBusCard.scala similarity index 96% rename from src/main/scala/li/cil/oc/server/component/AbstractBus.scala rename to src/main/scala/li/cil/oc/server/component/AbstractBusCard.scala index dcaf8f36f..da6845d0f 100644 --- a/src/main/scala/li/cil/oc/server/component/AbstractBus.scala +++ b/src/main/scala/li/cil/oc/server/component/AbstractBusCard.scala @@ -9,7 +9,7 @@ import scala.collection.convert.WrapAsScala._ import stargatetech2.api.StargateTechAPI import stargatetech2.api.bus._ -class AbstractBus(val device: IBusDevice) extends component.ManagedComponent with IBusDriver { +class AbstractBusCard(val device: IBusDevice) extends component.ManagedComponent with IBusDriver { val node = Network.newNode(this, Visibility.Neighbors). withComponent("abstract_bus"). withConnector(). @@ -70,7 +70,7 @@ class AbstractBus(val device: IBusDevice) extends component.ManagedComponent wit result(address) } - @Callback(doc = """function(address:number, data:table):boolean -- Sends data across the abstract bus.""") + @Callback(doc = """function(address:number, data:table):table -- Sends data across the abstract bus.""") def send(context: Context, args: Arguments): Array[AnyRef] = this.synchronized { val target = args.checkInteger(0) & 0xFFFF val data = args.checkTable(1) diff --git a/src/main/scala/li/cil/oc/server/component/GraphicsCard.scala b/src/main/scala/li/cil/oc/server/component/GraphicsCard.scala index 0f2da3c87..cee2c7dba 100644 --- a/src/main/scala/li/cil/oc/server/component/GraphicsCard.scala +++ b/src/main/scala/li/cil/oc/server/component/GraphicsCard.scala @@ -103,7 +103,9 @@ abstract class GraphicsCard extends component.ManagedComponent { @Callback(direct = true) def getPaletteColor(context: Context, args: Arguments): Array[AnyRef] = { val index = args.checkInteger(0) - screen(s => result(s.getPaletteColor(index))) + screen(s => try result(s.getPaletteColor(index)) catch { + case _: ArrayIndexOutOfBoundsException => throw new IllegalArgumentException("invalid palette index") + }) } @Callback @@ -111,10 +113,13 @@ abstract class GraphicsCard extends component.ManagedComponent { val index = args.checkInteger(0) val color = args.checkInteger(1) context.pause(0.1) - screen(s => { + screen(s => try { val oldColor = s.getPaletteColor(index) s.setPaletteColor(index, color) result(oldColor) + } + catch { + case _: ArrayIndexOutOfBoundsException => throw new IllegalArgumentException("invalid palette index") }) } diff --git a/src/main/scala/li/cil/oc/server/component/machine/NativeLuaArchitecture.scala b/src/main/scala/li/cil/oc/server/component/machine/NativeLuaArchitecture.scala index e3daf56d9..0f86a3ef3 100644 --- a/src/main/scala/li/cil/oc/server/component/machine/NativeLuaArchitecture.scala +++ b/src/main/scala/li/cil/oc/server/component/machine/NativeLuaArchitecture.scala @@ -28,10 +28,11 @@ class NativeLuaArchitecture(val machine: api.machine.Machine) extends Architectu new ComponentAPI(this), new ComputerAPI(this), new OSAPI(this), - persistence, new SystemAPI(this), new UnicodeAPI(this), - new UserdataAPI(this)) + new UserdataAPI(this), + // Persistence has to go last to ensure all other APIs can go into the permanent value table. + persistence) private[machine] def invoke(f: () => Array[AnyRef]): Int = try { f() match { @@ -125,7 +126,7 @@ class NativeLuaArchitecture(val machine: api.machine.Machine) extends Architectu override def isInitialized = kernelMemory > 0 override def recomputeMemory() = Option(lua) match { - case Some(l) => + case Some(l) if Settings.get.limitMemory => l.setTotalMemory(Int.MaxValue) l.gc(LuaState.GcAction.COLLECT, 0) if (kernelMemory > 0) { @@ -241,7 +242,9 @@ class NativeLuaArchitecture(val machine: api.machine.Machine) extends Architectu new ExecutionResult.Shutdown(false) } else { - lua.setTotalMemory(Int.MaxValue) + if (Settings.get.limitMemory) { + lua.setTotalMemory(Int.MaxValue) + } val error = if (lua.isJavaObjectRaw(3)) lua.toJavaObjectRaw(3).toString else lua.toString(3) @@ -291,7 +294,9 @@ class NativeLuaArchitecture(val machine: api.machine.Machine) extends Architectu override def close() { if (lua != null) { - lua.setTotalMemory(Integer.MAX_VALUE) + if (Settings.get.limitMemory) { + lua.setTotalMemory(Integer.MAX_VALUE) + } lua.close() } lua = null @@ -310,7 +315,9 @@ class NativeLuaArchitecture(val machine: api.machine.Machine) extends Architectu bootAddress = nbt.getString("bootAddress") // Unlimit memory use while unpersisting. - lua.setTotalMemory(Integer.MAX_VALUE) + if (Settings.get.limitMemory) { + lua.setTotalMemory(Integer.MAX_VALUE) + } try { // Try unpersisting Lua, because that's what all of the rest depends @@ -333,7 +340,7 @@ class NativeLuaArchitecture(val machine: api.machine.Machine) extends Architectu if (!lua.isThread(1)) { // This shouldn't really happen, but there's a chance it does if // the save was corrupt (maybe someone modified the Lua files). - throw new IllegalArgumentException("Invalid kernel.") + throw new LuaRuntimeException("Invalid kernel.") } if (state.contains(Machine.State.SynchronizedCall) || state.contains(Machine.State.SynchronizedReturn)) { val stack = @@ -343,15 +350,16 @@ class NativeLuaArchitecture(val machine: api.machine.Machine) extends Architectu if (!(if (state.contains(Machine.State.SynchronizedCall)) lua.isFunction(2) else lua.isTable(2))) { // Same as with the above, should not really happen normally, but // could for the same reasons. - throw new IllegalArgumentException("Invalid stack.") + throw new LuaRuntimeException("Invalid stack.") } } kernelMemory = (nbt.getInteger("kernelMemory") * ramScale).toInt } catch { case e: LuaRuntimeException => - OpenComputers.log.warning("Could not unpersist computer.\n" + e.toString + "\tat " + e.getLuaStackTrace.mkString("\n\tat ")) + OpenComputers.log.warning("Could not unpersist computer.\n" + e.toString + (if (e.getLuaStackTrace.isEmpty) "" else "\tat " + e.getLuaStackTrace.mkString("\n\tat "))) machine.stop() + machine.start() } // Limit memory again. @@ -364,7 +372,9 @@ class NativeLuaArchitecture(val machine: api.machine.Machine) extends Architectu } // Unlimit memory while persisting. - lua.setTotalMemory(Integer.MAX_VALUE) + if (Settings.get.limitMemory) { + lua.setTotalMemory(Integer.MAX_VALUE) + } try { // Try persisting Lua, because that's what all of the rest depends on. diff --git a/src/main/scala/li/cil/oc/server/component/machine/luac/PersistenceAPI.scala b/src/main/scala/li/cil/oc/server/component/machine/luac/PersistenceAPI.scala index 63987071d..f2440a85f 100644 --- a/src/main/scala/li/cil/oc/server/component/machine/luac/PersistenceAPI.scala +++ b/src/main/scala/li/cil/oc/server/component/machine/luac/PersistenceAPI.scala @@ -99,43 +99,47 @@ class PersistenceAPI(owner: NativeLuaArchitecture) extends NativeLuaAPI(owner) { } def persist(index: Int): Array[Byte] = { - configure() - lua.getGlobal("eris") // ... eris - lua.getField(-1, "persist") // ... eris persist - if (lua.isFunction(-1)) { - lua.getField(LuaState.REGISTRYINDEX, "perms") // ... eris persist perms - lua.pushValue(index) // ... eris persist perms obj - try { - lua.call(2, 1) // ... eris str? - } catch { - case e: Throwable => - lua.pop(1) - throw e - } - if (lua.isString(-1)) { - // ... eris str - val result = lua.toByteArray(-1) - lua.pop(2) // ... - return result + if (Settings.get.allowPersistence) { + configure() + lua.getGlobal("eris") // ... eris + lua.getField(-1, "persist") // ... eris persist + if (lua.isFunction(-1)) { + lua.getField(LuaState.REGISTRYINDEX, "perms") // ... eris persist perms + lua.pushValue(index) // ... eris persist perms obj + try { + lua.call(2, 1) // ... eris str? + } catch { + case e: Throwable => + lua.pop(1) + throw e + } + if (lua.isString(-1)) { + // ... eris str + val result = lua.toByteArray(-1) + lua.pop(2) // ... + return result + } // ... eris :( } // ... eris :( - } // ... eris :( - lua.pop(2) // ... + lua.pop(2) // ... + } Array[Byte]() } def unpersist(value: Array[Byte]): Boolean = { - configure() - lua.getGlobal("eris") // ... eris - lua.getField(-1, "unpersist") // ... eris unpersist - if (lua.isFunction(-1)) { - lua.getField(LuaState.REGISTRYINDEX, "uperms") // ... eris persist uperms - lua.pushByteArray(value) // ... eris unpersist uperms str - lua.call(2, 1) // ... eris obj - lua.insert(-2) // ... obj eris + if (Settings.get.allowPersistence) { + configure() + lua.getGlobal("eris") // ... eris + lua.getField(-1, "unpersist") // ... eris unpersist + if (lua.isFunction(-1)) { + lua.getField(LuaState.REGISTRYINDEX, "uperms") // ... eris persist uperms + lua.pushByteArray(value) // ... eris unpersist uperms str + lua.call(2, 1) // ... eris obj + lua.insert(-2) // ... obj eris + lua.pop(1) + return true + } // ... :( lua.pop(1) - return true - } // ... :( - lua.pop(1) + } false } } diff --git a/src/main/scala/li/cil/oc/server/driver/item/AbstractBusCard.scala b/src/main/scala/li/cil/oc/server/driver/item/AbstractBusCard.scala index 8cce7af5b..efdd7c849 100644 --- a/src/main/scala/li/cil/oc/server/driver/item/AbstractBusCard.scala +++ b/src/main/scala/li/cil/oc/server/driver/item/AbstractBusCard.scala @@ -11,7 +11,7 @@ object AbstractBusCard extends Item { override def worksWith(stack: ItemStack) = isOneOf(stack, api.Items.get("abstractBusCard")) override def createEnvironment(stack: ItemStack, container: Container) = if (Mods.StargateTech2.isAvailable) container match { - case device: IBusDevice => new component.AbstractBus(device) + case device: IBusDevice => new component.AbstractBusCard(device) case _ => null } else null diff --git a/src/main/scala/li/cil/oc/util/LuaStateFactory.scala b/src/main/scala/li/cil/oc/util/LuaStateFactory.scala index 323c29454..8376d6a1d 100644 --- a/src/main/scala/li/cil/oc/util/LuaStateFactory.scala +++ b/src/main/scala/li/cil/oc/util/LuaStateFactory.scala @@ -158,7 +158,9 @@ object LuaStateFactory { if (!haveNativeLibrary) return None try { - val state = new jnlua.LuaState(Int.MaxValue) + val state = + if (Settings.get.limitMemory) new jnlua.LuaState(Int.MaxValue) + else new jnlua.LuaState() try { // Load all libraries. state.openLib(jnlua.LuaState.Library.BASE)