diff --git a/assets/opencomputers/lua/boot.lua b/assets/opencomputers/lua/boot.lua index 7d3350dd7..7c55fead1 100644 --- a/assets/opencomputers/lua/boot.lua +++ b/assets/opencomputers/lua/boot.lua @@ -111,9 +111,4 @@ end sendToNode = wrap(sendToNode) sendToAll = wrap(sendToAll) -nodeName = wrap(nodeName) - -driver = {} -function driver.componentType(id) - return nodeName(id) -end \ No newline at end of file +nodeName = wrap(nodeName) \ No newline at end of file diff --git a/assets/opencomputers/lua/buffer.lua b/assets/opencomputers/lua/buffer.lua deleted file mode 100644 index a6ca2abb7..000000000 --- a/assets/opencomputers/lua/buffer.lua +++ /dev/null @@ -1,137 +0,0 @@ ---[[ - This is the terminal implementation. - - It provides means to interact with a screen that is provided by the actual - game in some way. -]] - -function write(term, ...) - local w, h = term.getSize() - local x, y = term.getCursorPosition() - local foreground = term.getForeground() - local background = term.getBackground() - for _, value in ipairs({...}) do - local value = tostring(value) - for i in 1, value:len() do - term.setCharAt(x, y) = value.sub(i, i) - end - end -end - ---[[ Creates a new terminal with an internal display buffer. ]] -function new(width, height) - -- This is the current display buffer of the terminal: an array of lines, which - -- are in turn arrays of the displayed characters on that line, as well as - -- foreground and background colors (which may or may not be ignored in the - -- actual presentation layer). - local buffer = {} - - -- This is the size of the buffer, i.e. its width and height. - local size = {x = 0, y = 0} - - -- Cursor position and whether to blink or not. - local cursor = {x = 1, y = 1, blink = false} - - -- The current foreground and background color, applied when writing. - local colors = {foreground = 0xFFFFFF, background = 0x000000} - - --[[ Resizes the internal buffer and trims / expands it as necessary. ]] - local function resize(w, h) - -- Trim vertical area. - for line = size.y, h + 1, -1 do - buffer[line] = nil - end - -- Trim horizontal area. - for line = 1, h do - local buffer = buffer[line] - for column = size.x, w + 1, -1 do - buffer[column] = nil - end - end - -- Expand vertical area. - for line = size.y + 1, h do - buffer[line] = {} - end - -- Expand horizontal area. - for line = 1, h do - local buffer = buffer[line] - for column = line <= h and size.x + 1 or 1, w do - buffer[column] = {" ", 0xFFFFFF, 0x000000} - end - end - -- Store new sizes. - size.x = x - size.y = y - end - - -- Pre-fill the buffer. - resize(80, 24) - - -- Checked buffer access for abstraction layer below. - local function get(x, y) - if x < 1 or x > size.x then - error("column out of bounds") - elseif y < 1 or y > size.y then - error("line out of bounds") - else - return buffer[x][y] - end - end - - -- Abstraction layer to avoid direct access to internals while re-using - -- function instances. - local term = { - getSize = function() return size.x, size.y end, - getCharAt = function(x, y) return get(x, y)[1] end, - setCharAt = function(x, y, char) - assert(type(char) == "string" and char:len() == 1, - "'char' must be a single character") - get(x, y)[1] = char - end, - getForegroundAt = function(x, y) return get(x, y)[2] end, - setForegroundAt = function(x, y, color) - assert(type(color) == "number" and color == math.floor(color), - "'color' must be an integral number") - get(x, y)[2] = color - end, - getBackgroundAt = function(x, y) return get(x, y)[3] end, - setBackgroundAt = function(x, y, color) - assert(type(color) == "number" and color == math.floor(color), - "'color' must be an integral number") - get(x, y)[3] = color - end, - getCursorPosition = function() return cursor.x, cursor.y end, - setCursorPosition = function(x, y) - assert(type(x) == "number" and x == math.floor(x), - "'x' must be an integral number") - assert(x >= 1 and x <= size.x, "column out of bounds") - assert(type(y) == "number" and y == math.floor(y), - "'y' must be an integral number") - assert(y >= 1 and y <= size.y, "line out of bounds") - cursor.x = x - cursor.y = y - end, - getCursorBlink = function() return cursor.blink end, - setCursorBlink = function(blink) - assert(type(blink) == "boolean", "'blink' must be a boolean") - cursor.blink = blink - end, - getForeground = function() return colors.foreground end, - setForeground = function(color) - assert(type(color) == "number" and color == math.floor(color), - "'color' must be an integral number") - colors.foreground = color - end, - getBackground = function() return colors.background end, - setBackground = function(color) - assert(type(color) == "number" and color == math.floor(color), - "'color' must be an integral number") - colors.background = color - end - } - return setmetatable({}, {__index = term}) -end - - -local function clear() -end \ No newline at end of file diff --git a/assets/opencomputers/lua/disk.lua b/assets/opencomputers/lua/disk.lua index 3a085bb19..2b1843483 100644 --- a/assets/opencomputers/lua/disk.lua +++ b/assets/opencomputers/lua/disk.lua @@ -2,17 +2,57 @@ The Disk API, provided by disk components. ]] -mount = function() end -umount = function() end +driver.disk = {} -listdir = function(dirname) end -remove = _G.os.remove -rename = _G.os.rename -tmpname = function() end +-- We track mounted disks in this table. +local fstab = {} -open = function(filename, mode) end -read = function() end -write = function(value) end -flush = function(file) end -close = function(file) end -type = function(file) end \ No newline at end of file +function driver.disk.mount() +end + +function driver.disk.umount() +end + +function driver.disk.listdir(dirname) +end + +function driver.disk.remove(path) +end + +function driver.disk.rename(path) +end + +function driver.disk.tmpname() +end + +function driver.disk.open(filename, mode) +end + +function driver.disk.read() +end + +function driver.disk.write(value) +end + +function driver.disk.flush(file) +end + +function driver.disk.close(file) +end + +function driver.disk.type(file) +end + +-- Aliases for vanilla Lua. +os.remove = driver.disk.remove +os.rename = driver.disk.rename +os.tmpname = driver.disk.tmpname + +io = {} +io.flush = function() end -- does nothing +-- TODO io.lines = function(filename) end +io.open = driver.disk.open +-- TODO io.popen = function(prog, mode) end +io.read = driver.disk.read +-- TODO io.tmpfile = function() end +io.type = driver.disk.type diff --git a/assets/opencomputers/lua/gpu.lua b/assets/opencomputers/lua/gpu.lua index 1ac469e6b..5215543d7 100644 --- a/assets/opencomputers/lua/gpu.lua +++ b/assets/opencomputers/lua/gpu.lua @@ -25,27 +25,25 @@ function driver.gpu.copy(gpu, screen, col, row, w, h, tx, ty) sendToNode(gpu, "gpu.copy", screen, col, row, w, h, tx, ty) end -function driver.gpu.bind(gpuAccess, screenAccess) - local gpu = type(gpuAccess) == "function" and gpuAccess or function() return gpuAccess end - local screen = type(screenAccess) == "function" and screenAccess or function() return screenAccess end +function driver.gpu.bind(gpuId, screenId) return { setResolution = function(w, h) - driver.gpu.setResolution(gpu(), screen(), w, h) + driver.gpu.setResolution(component.address(gpuId), component.address(screenId), w, h) end, getResolution = function() - return driver.gpu.getResolution(gpu(), screen()) + return driver.gpu.getResolution(component.address(gpuId), component.address(screenId)) end, getResolutions = function() - return driver.gpu.getResolutions(gpu(), screen()) + return driver.gpu.getResolutions(component.address(gpuId), component.address(screenId)) end, set = function(col, row, value) - driver.gpu.set(gpu(), screen(), col, row, value) + driver.gpu.set(component.address(gpuId), component.address(screenId), col, row, value) end, fill = function(col, ro, w, h, value) - driver.gpu.fill(gpu(), screen(), col, ro, w, h, value) + driver.gpu.fill(component.address(gpuId), component.address(screenId), col, ro, w, h, value) end, copy = function(col, row, w, h, tx, ty) - driver.gpu.copy(gpu(), screen(), col, row, w, h, tx, ty) + driver.gpu.copy(component.address(gpuId), component.address(screenId), col, row, w, h, tx, ty) end } end \ No newline at end of file diff --git a/assets/opencomputers/lua/init.lua b/assets/opencomputers/lua/init.lua index 94ec2c5f9..f94d8e096 100644 --- a/assets/opencomputers/lua/init.lua +++ b/assets/opencomputers/lua/init.lua @@ -235,9 +235,7 @@ end) local function bindIfPossible() if gpuId > 0 and screenId > 0 then if not boundGpu then - local function gpu() return component.address(gpuId) end - local function screen() return component.address(screenId) end - boundGpu = driver.gpu.bind(gpu, screen) + boundGpu = driver.gpu.bind(gpuId, screenId) screenWidth, screenHeight = boundGpu.getResolution() event.fire("term_available") end @@ -340,12 +338,12 @@ end write = function(...) local args = {...} local first = true - for _, value in ipairs(args) do + for i = 1, #args do if not first then term.write(", ") end first = false - term.write(value, true) + term.write(args[i], true) end end diff --git a/assets/opencomputers/lua/kernel.lua b/assets/opencomputers/lua/kernel.lua index 6e72fe7d7..34a015d79 100644 --- a/assets/opencomputers/lua/kernel.lua +++ b/assets/opencomputers/lua/kernel.lua @@ -1,20 +1,6 @@ ---[[ Basic functionality, drives userland and enforces timeouts. - - This is called as the main coroutine by the computer. If this throws, the - computer crashes. If this returns, the computer is considered powered off. ---]] - ---[[ Will be adjusted by the kernel when running, represents how long we can - continue running without yielding. Used in the debug hook that enforces - this timeout by throwing an error if it's exceeded. ]] -local deadline = 0 - ---[[ The hook installed for process coroutines enforcing the timeout. ]] -local function timeoutHook() - if os.realTime() > deadline then - error({timeout = debug.traceback(2)}, 0) - end -end +--[[ This is called as the main coroutine by the host. If this returns the + computer crashes. It should never ever return "normally", only when an + error occurred. Shutdown / reboot are signalled via special yields. ]] --[[ Set up the global environment we make available to userland programs. ]] local sandbox = { @@ -39,17 +25,9 @@ local sandbox = { tonumber = tonumber, tostring = tostring, - -- We don't care what users do with metatables. The only raised concern was - -- about breaking an environment, which is pretty much void in Lua 5.2. getmetatable = getmetatable, setmetatable = setmetatable, - write = function() end, - - checkArg = checkArg, - component = component, - driver = driver, - bit32 = { arshift = bit32.arshift, band = bit32.band, @@ -142,114 +120,130 @@ local sandbox = { unpack = table.unpack } } +sandbox.checkArg = checkArg +sandbox._G = sandbox + +-- Note: 'write' will be replaced by init script. +function sandbox.write(...) end function sandbox.print(...) sandbox.write(...) sandbox.write("\n") end --- Make the sandbox its own globals table. -sandbox._G = sandbox - --- Allow sandboxes to load code, but only in text form, and in the sandbox. --- Note that we allow passing a custom environment, because if this is called --- from inside the sandbox, env must already be in the sandbox. function sandbox.load(code, source, env) return load(code, source, "t", env or sandbox) end --[[ Install wrappers for coroutine management that reserves the first value - returned by yields for internal stuff. For now this is used for driver - calls, in which case the first function is the function performing the - actual driver call, but is called from the server thread, and for sleep - notifications, i.e. letting the host know we're in no hurry to be - called again any time soon. See os.sleep for more on the second. + returned by yields for internal stuff. Used for sleeping and message + calls (sendToNode and its ilk) that happen synchronized (Server thread). --]] -function sandbox.coroutine.yield(...) - return coroutine.yield(nil, ...) -end -function sandbox.coroutine.resume(co, ...) - local function checkDeadline() - if os.realTime() > deadline then - error("too long without yielding", 0) - end +local deadline = 0 + +local function checkDeadline() + if os.realTime() > deadline then + error("too long without yielding", 0) end - local args = {...} +end + +local function main(co) + local args = {} while true do - -- Don't reset counter when resuming, to avoid circumventing the timeout. + deadline = os.realTime() + 3 if not debug.gethook(co) then debug.sethook(co, checkDeadline, "", 10000) end - - -- Run the coroutine. local result = {coroutine.resume(co, table.unpack(args))} - - -- Check if we're over the deadline since I'm pretty sure the counter of - -- coroutines is separate. - checkDeadline() - - -- See what kind of yield we have. if result[1] then - -- Coroutine returned normally, if we yielded it may be a system yield. - if coroutine.status(co) ~= "dead" and result[2] then - -- Propagate system yields and repeat the retry. - args = coroutine.yield(table.unpack(result, 2)) - else - -- Normal yield or coroutine returned, return result. - return result[1], table.unpack(result, 3) - end + args = {coroutine.yield(result[2])} -- system yielded value else - -- Error while executing coroutine. + error(result[2]) + end + end +end + +function sandbox.coroutine.resume(co, ...) + local args = {...} + while true do + if not debug.gethook(co) then -- don't reset counter + debug.sethook(co, checkDeadline, "", 10000) + end + local result = {coroutine.resume(co, table.unpack(args))} + checkDeadline() + if result[1] then + local isSystemYield = coroutine.status(co) ~= "dead" and result[2] ~= nil + if isSystemYield then + args = coroutine.yield(result[2]) + else + return true, table.unpack(result, 3) + end + else -- error: result = (bool, string) return table.unpack(result) end end end ---[[ Pull a signal with an optional timeout. ]] +function sandbox.coroutine.yield(...) + return coroutine.yield(nil, ...) +end + function sandbox.os.signal(name, timeout) checkArg(1, name, "string", "nil") checkArg(2, timeout, "number", "nil") - local deadline = os.clock() + (timeout or math.huge) - while os.clock() < deadline do - local signal = {coroutine.yield(deadline - os.clock())} - if signal and (signal[1] == name or name == nil) then + local waitUntil = os.clock() + (timeout or math.huge) + while os.clock() < waitUntil do + local signal = {coroutine.yield(waitUntil - os.clock())} + if signal and (name == signal[1] or name == nil) then return table.unpack(signal) end end end ---[[ Shutdown the computer. ]] function sandbox.os.shutdown() coroutine.yield(false) end ---[[ Reboot the computer. ]] function sandbox.os.reboot() coroutine.yield(true) end +sandbox.driver = {} + +function sandbox.driver.componentType(id) + return nodeName(id) +end + +do + local env = setmetatable({ + sendToNode = sendToNode, + sendToAll = sendToAll + }, { __index = sandbox }) + for name, code in pairs(drivers()) do + local driver, reason = load(code, "=" .. name, "t", env) + if not driver then + print("Failed loading driver '" .. name .. "': " .. reason) + else + local result, reason = pcall(driver) + if not result then + print("Failed initializing driver '" .. name .. "': " .. reason) + end + end + end +end + +-- Load init script in sandboxed environment. +local coinit +do + local result, reason = load(init(), "=init", "t", sandbox) + if not result then + error(reason) + end + coinit = coroutine.create(result) +end + +-- Yield once to allow initializing up to here to get a memory baseline. +coroutine.yield() + -- JNLua converts the coroutine to a string immediately, so we can't get the -- traceback later. Because of that we have to do the error handling here. -return pcall(function() - -- Replace init script code with loaded, sandboxed and threaded script. - local init = (function() - local result, reason = load(init(), "=init", "t", sandbox) - if not result then error(reason, 0) end - return coroutine.create(result) - end)() - - -- Main kernel loop. - local data = {} - while true do - deadline = os.realTime() + 3 - local result = {coroutine.resume(init, table.unpack(data))} - if result[1] then - -- Init should never return, so we have a yield. The first yielded - -- value can only be set by system yields. - result = result[2] - else - -- Some other error, go kill ourselves. - error(result[2]) - end - data = {coroutine.yield(result)} - end -end) \ No newline at end of file +return pcall(main, coinit) \ No newline at end of file diff --git a/assets/opencomputers/lua/redstone.lua b/assets/opencomputers/lua/redstone.lua index 1a56f7176..25f458282 100644 --- a/assets/opencomputers/lua/redstone.lua +++ b/assets/opencomputers/lua/redstone.lua @@ -5,34 +5,34 @@ driver.redstone = {} driver.redstone.sides = {"top", "bottom", "left", "right", "front", "back"} -- Add inverse mapping and aliases. -for k, v in pairs(sides) do sides[v] = k end -sides.up = sides.top -sides.down = sides.bottom +for k, v in pairs(driver.redstone.sides) do + driver.redstone.sides[v] = k +end +driver.redstone.sides.up = driver.redstone.sides.top +driver.redstone.sides.down = driver.redstone.sides.bottom -function driver.redstone.getAnalogInput(card, side) - checkArg(1, side, "number") - sendToNode(card, os.address(), "redstone.input", side) +local safeOsAddress = os.address + +function driver.redstone.analogInput(card, side) + sendToNode(card, safeOsAddress(), "redstone.input", side) end -function driver.redstone.getAnalogOutput(card, side) - checkArg(1, side, "number") - sendToNode(card, os.address(), "redstone.output", side) +function driver.redstone.analogOutput(card, side, value) + if value then + sendToNode(card, safeOsAddress(), "redstone.output=", side, value) + else + return sendToNode(card, safeOsAddress(), "redstone.output", side) + end end -function driver.redstone.setAnalogOutput(card, side, value) - checkArg(1, side, "number") - checkArg(2, side, "number") - sendToNode(card, os.address(), "redstone.output=", side, value) +function driver.redstone.input(card, side) + return driver.redstone.analogInput(card, side) > 0 end -function getInput(side) - return getAnalogInput(side) > 0 -end - -function getOutput(side) - return getAnalogOutput(side) > 0 -end - -function setOutput(side, value) - rs.setAnalogOutput(side, value and 15 or 0) +function output(card, side, value) + if value then + driver.redstone.analogOutput(side, value and 15 or 0) + else + return driver.redstone.analogOutput(card, side) > 0 + end end diff --git a/li/cil/oc/api/Driver.scala b/li/cil/oc/api/Driver.scala new file mode 100644 index 000000000..c455787fc --- /dev/null +++ b/li/cil/oc/api/Driver.scala @@ -0,0 +1,56 @@ +package li.cil.oc.api + +import java.io.InputStream +import li.cil.oc.api.driver.{Item, Block} + +/** + * This interface specifies the structure of a driver for a component. + *
+ * A driver is essentially the glue code that allows arbitrary objects to be + * used as computer components. They specify an API that is injected into the + * Lua state when the driver is installed, and provide general information used + * by the computer. + * + * Do not implement this interface directly; use the `ItemDriver` and + * `BlockDriver` interfaces for the respective component types. + */ +trait Driver { + /** + * The code that is run when the driver is installed. + * + * The code will be run in a 'privileged' sandbox, i.e the basic environment + * is already the sandboxed environment (thus any changes made to the globals + * table will be noticeable - keep copies of security relevant functions). In + * addition to the sandbox it will also have access to two more functions, + * `sendToNode` and `sendToAll`, which will send a (synchronized) message to + * the component network the computer is attached to. + * + * Drivers must take all precautions to avoid these two functions "leaking" + * into user-space; at that point the user is pretty much in "creative mode", + * relatively speaking (i.e. the user could send arbitrary messages into the + * network, which is really not a good idea). + * + * This can be `None` to do nothing. Otherwise this is expected to be valid + * Lua code (it is loaded viaload()
and then run).
+ *
+ * The stream has to be recreated each time this is called. Normally you will
+ * return something along the lines of
+ * `Mod.class.getResourceAsStream("/assets/yourmod/ocapi.lua")`
+ * from this method. If you wish to hard-code the returned script, you can
+ * use `new ByteArrayInputStream(yourScript.getBytes())` instead.
+ *
+ * IMPORTANT: Note that the stream will automatically closed.
+ *
+ * @return the Lua code to run when a computer is started up.
+ */
+ def api: Option[InputStream] = None
+}
+
+object Driver {
+ /** Initialized in pre-init. */
+ private[oc] var registry: Option[{def add(driver: Block); def add(driver: Item)}] = None
+
+ def add(driver: Block) = registry.foreach(_.add(driver))
+
+ def add(driver: Item) = registry.foreach(_.add(driver))
+}
\ No newline at end of file
diff --git a/li/cil/oc/api/network/Network.scala b/li/cil/oc/api/Network.scala
similarity index 89%
rename from li/cil/oc/api/network/Network.scala
rename to li/cil/oc/api/Network.scala
index 49680372c..3979bab29 100644
--- a/li/cil/oc/api/network/Network.scala
+++ b/li/cil/oc/api/Network.scala
@@ -1,4 +1,7 @@
-package li.cil.oc.api.network
+package li.cil.oc.api
+
+import li.cil.oc.api.network.Node
+import net.minecraft.world.IBlockAccess
/**
* Interface for interacting with component networks.
@@ -12,15 +15,15 @@ package li.cil.oc.api.network
* neighbors to see if a network already exists. If so, it should join that
* network. If multiple different networks are adjacent it should join one and
* then merge it with the other(s). If no networks exist, it should create a new
- * one. All this logic is provided by `NetworkAPI#joinOrCreateNetwork`.
+ * one. All this logic is provided by `Network.joinOrCreateNetwork`.
*
* Note that for network nodes implemented in TileEntities adding and
* removal is automatically provided on chunk load and unload. When a block is
* placed or broken you will have to implement this logic yourself (i.e. call
- * NetworkAPI.joinOrCreateNetwork in onBlockAdded and
- * getNetwork.remove in breakBlock.
+ * Network.joinOrCreateNetwork in onBlockAdded and
+ * Network.remove in breakBlock.
*
- * All other kinds of nodes have to be managed manually. See `INetworkNode`.
+ * All other kinds of nodes have to be managed manually. See `Node`.
*
* There are a couple of system messages to be aware of. These are all sent by
* the network manager itself:
@@ -38,7 +41,7 @@ package li.cil.oc.api.network
* instances of your own network implementation; this will lead to
* incompatibilities with the built-in network implementation (which can only
* merge with other networks of its own type). Always use the methods provided
- * in NetworkAPI to create and join networks.
+ * in Network to create and join networks.
*/
trait Network {
/**
@@ -59,10 +62,6 @@ trait Network {
* @return true if a new connection between the two nodes was added; false if
* the connection already existed.
* @throws IllegalArgumentException if neither node is in this network.
- * @throws IllegalStateException if this is called while the network is
- * already updating, for example from
- * `INetworkNode#receive` (which may be
- * called during the update).
*/
def connect(nodeA: Node, nodeB: Node): Boolean
@@ -215,4 +214,20 @@ trait Network {
* @param data the message to send.
*/
def sendToAll(source: Node, name: String, data: Any*)
+}
+
+object Network {
+ /** Initialized in pre-init. */
+ private[oc] var network: Option[{def joinOrCreateNetwork(world: IBlockAccess, x: Int, y: Int, z: Int)}] = None
+
+ /**
+ * Tries to add a tile entity network node at the specified coordinates to adjacent networks.
+ *
+ * @param world the world the tile entity lives in.
+ * @param x the X coordinate of the tile entity.
+ * @param y the Y coordinate of the tile entity.
+ * @param z the Z coordinate of the tile entity.
+ */
+ def joinOrCreateNetwork(world: IBlockAccess, x: Int, y: Int, z: Int) =
+ network.foreach(_.joinOrCreateNetwork(world, x, y, z))
}
\ No newline at end of file
diff --git a/li/cil/oc/api/driver/API.scala b/li/cil/oc/api/driver/API.scala
deleted file mode 100644
index f8ce1b8fd..000000000
--- a/li/cil/oc/api/driver/API.scala
+++ /dev/null
@@ -1,13 +0,0 @@
-package li.cil.oc.api.driver
-
-import li.cil.oc.server.computer.Drivers
-
-object API {
- def addDriver(driver: Block) {
- Drivers.add(driver)
- }
-
- def addDriver(driver: Item) {
- Drivers.add(driver)
- }
-}
\ No newline at end of file
diff --git a/li/cil/oc/api/driver/Block.scala b/li/cil/oc/api/driver/Block.scala
index fcaf16c80..e689a55c7 100644
--- a/li/cil/oc/api/driver/Block.scala
+++ b/li/cil/oc/api/driver/Block.scala
@@ -2,13 +2,14 @@ package li.cil.oc.api.driver
import li.cil.oc.api.network.Node
import net.minecraft.world.World
+import li.cil.oc.api.Driver
/**
* Interface for block component drivers.
*
* This driver type is used for components that are blocks, i.e. that can be
* placed in the world, but cannot be modified to or don't want to have their
- * `TileEntities` implement `INetworkNode`.
+ * `TileEntities` implement `network.Node`.
*
* A block driver is used by proxy blocks to check its neighbors and whether
* those neighbors should be treated as components or not.
diff --git a/li/cil/oc/api/driver/Driver.scala b/li/cil/oc/api/driver/Driver.scala
deleted file mode 100644
index c2a10d3dc..000000000
--- a/li/cil/oc/api/driver/Driver.scala
+++ /dev/null
@@ -1,45 +0,0 @@
-package li.cil.oc.api.driver
-
-import java.io.InputStream
-
-/**
- * This interface specifies the structure of a driver for a component.
- *
- * A driver is essentially the glue code that allows arbitrary objects to be
- * used as computer components. They specify an API that is injected into the
- * Lua state when the driver is installed, and provide general information used
- * by the computer.
- *
- * Do not implement this interface directly; use the `IItemDriver` and
- * `IBlockDriver` interfaces for the respective component types.
- */
-trait Driver {
- /**
- * Some initialization code that is run when the driver is installed.
- *
- * These will usually be some functions that generate network messages of
- * the particular signature the node of the driver handles, but may contain
- * arbitrary other functions. However, whatever you do, keep in mind that
- * only certain parts of the global namespace will be made available to the
- * computer at runtime, so it's best to keep all you declare in the driver
- * table (global variable `driver`).
- *
- * This is loaded into the Lua state and run in the global, un-sandboxed
- * environment. This means your scripts can mess things up bad, so make sure
- * you know what you're doing and exposing.
- *
- * This can be `None` to do nothing. Otherwise this is expected to be valid
- * Lua code (it is simply loaded via load()
and then executed).
- *
- * The stream has to be recreated each time this is called. Normally you will
- * return something along the lines of
- * `Mod.class.getResourceAsStream("/assets/yourmod/lua/ocapi.lua")`
- * from this method. If you wish to hard-code the returned script, you can use
- * `new ByteArrayInputStream(yourScript.getBytes())` instead.
- *
- * IMPORTANT: Note that the stream will automatically closed.
- *
- * @return the Lua code to run when a computer is started up.
- */
- def api: Option[InputStream] = None
-}
\ No newline at end of file
diff --git a/li/cil/oc/api/driver/Item.scala b/li/cil/oc/api/driver/Item.scala
index 9c1a43f26..fefa8ffd3 100644
--- a/li/cil/oc/api/driver/Item.scala
+++ b/li/cil/oc/api/driver/Item.scala
@@ -2,6 +2,7 @@ package li.cil.oc.api.driver
import li.cil.oc.api.network.Node
import net.minecraft.item.ItemStack
+import li.cil.oc.api.Driver
/**
* Interface for item component drivers.
@@ -13,9 +14,9 @@ import net.minecraft.item.ItemStack
* When trying to add an item to a computer the list of registered drivers is
* queried using the drivers' `worksWith` functions. The first driver that
* replies positively and whose check against the slot type is successful, i.e.
- * for which the `componentType` matches the slot, will be used as the
- * component's driver and the component will be added. If no driver is found
- * the item will be rejected and cannot be installed.
+ * for which the `slot` matches the slot it should be inserted into, will be
+ * used as the component's driver and the component will be added. If no driver
+ * is found the item will be rejected and cannot be installed.
*
* Note that it is possible to write one driver that supports as many different
* items as you wish. I'd recommend writing one per device (type), though, to
diff --git a/li/cil/oc/api/network/API.scala b/li/cil/oc/api/network/API.scala
deleted file mode 100644
index eb5656f00..000000000
--- a/li/cil/oc/api/network/API.scala
+++ /dev/null
@@ -1,21 +0,0 @@
-package li.cil.oc.api.network
-
-import li.cil.oc.server
-import net.minecraft.world.IBlockAccess
-
-/**
- * Provides convenience methods for interacting with component networks.
- */
-object API {
- /**
- * Tries to add a tile entity network node at the specified coordinates to adjacent networks.
- *
- * @param world the world the tile entity lives in.
- * @param x the X coordinate of the tile entity.
- * @param y the Y coordinate of the tile entity.
- * @param z the Z coordinate of the tile entity.
- */
- def joinOrCreateNetwork(world: IBlockAccess, x: Int, y: Int, z: Int) =
- // TODO reflection
- server.network.Network.joinOrCreateNetwork(world, x, y, z)
-}
\ No newline at end of file
diff --git a/li/cil/oc/api/network/Node.scala b/li/cil/oc/api/network/Node.scala
index 6a282fdbd..bfeea4189 100644
--- a/li/cil/oc/api/network/Node.scala
+++ b/li/cil/oc/api/network/Node.scala
@@ -1,6 +1,7 @@
package li.cil.oc.api.network
import net.minecraft.nbt.NBTTagCompound
+import li.cil.oc.api.Network
/**
* A single node in a `INetwork`.
@@ -83,7 +84,7 @@ trait Node {
*
* @return the network the node is in.
*/
- var network: Network = null
+ var network: Option[Network] = None
/**
* Makes the node handle a message.
diff --git a/li/cil/oc/common/Proxy.scala b/li/cil/oc/common/Proxy.scala
index ce2ac4903..26f218971 100644
--- a/li/cil/oc/common/Proxy.scala
+++ b/li/cil/oc/common/Proxy.scala
@@ -4,10 +4,12 @@ import cpw.mods.fml.common.event._
import cpw.mods.fml.common.network.NetworkRegistry
import cpw.mods.fml.common.registry.LanguageRegistry
import li.cil.oc._
-import li.cil.oc.api.driver.API
-import li.cil.oc.server.computer.{Computer, Drivers}
+import li.cil.oc.api.Driver
+import li.cil.oc.api.Network
+import li.cil.oc.server.computer.Computer
import li.cil.oc.server.driver
-import li.cil.oc.server.network.Network
+import li.cil.oc.server.driver.Registry
+import li.cil.oc.server.network
import net.minecraftforge.common.MinecraftForge
class Proxy {
@@ -16,6 +18,9 @@ class Proxy {
LanguageRegistry.instance.loadLocalization(
"/assets/opencomputers/lang/en_US.lang", "en_US", false)
+
+ Driver.registry = Some(driver.Registry)
+ Network.network = Some(network.Network)
}
def init(e: FMLInitializationEvent): Unit = {
@@ -24,17 +29,17 @@ class Proxy {
NetworkRegistry.instance.registerGuiHandler(OpenComputers, GuiHandler)
- API.addDriver(driver.GraphicsCard)
- API.addDriver(driver.Keyboard)
+ Driver.add(driver.GraphicsCard)
+ Driver.add(driver.Keyboard)
MinecraftForge.EVENT_BUS.register(Computer)
- MinecraftForge.EVENT_BUS.register(Network)
+ MinecraftForge.EVENT_BUS.register(network.Network)
}
def postInit(e: FMLPostInitializationEvent): Unit = {
// Lock the driver registry to avoid drivers being added after computers
// may have already started up. This makes sure the driver API won't change
// over the course of a game, since that could lead to weird effects.
- Drivers.locked = true
+ Registry.locked = true
}
}
\ No newline at end of file
diff --git a/li/cil/oc/common/block/Multi.scala b/li/cil/oc/common/block/Multi.scala
index 12e065196..b667457f6 100644
--- a/li/cil/oc/common/block/Multi.scala
+++ b/li/cil/oc/common/block/Multi.scala
@@ -3,7 +3,8 @@ package li.cil.oc.common.block
import cpw.mods.fml.common.registry.GameRegistry
import li.cil.oc.Config
import li.cil.oc.CreativeTab
-import li.cil.oc.api.network.{Node, API}
+import li.cil.oc.api.Network
+import li.cil.oc.api.network.Node
import li.cil.oc.common.tileentity.TileEntityRotatable
import net.minecraft.block.Block
import net.minecraft.block.material.Material
@@ -102,7 +103,7 @@ class Multi(id: Int) extends Block(id, Material.iron) {
case None => // Invalid but avoid match error.
case Some(subBlock) => {
world.getBlockTileEntity(x, y, z) match {
- case node: Node => node.network.remove(node)
+ case node: Node => node.network.foreach(_.remove(node))
}
subBlock.breakBlock(world, x, y, z, blockId, metadata)
}
@@ -266,7 +267,7 @@ class Multi(id: Int) extends Block(id, Material.iron) {
case None => // Invalid but avoid match error.
case Some(subBlock) => {
world.getBlockTileEntity(x, y, z) match {
- case _: Node => API.joinOrCreateNetwork(world, x, y, z)
+ case _: Node => Network.joinOrCreateNetwork(world, x, y, z)
}
subBlock.onBlockAdded(world, x, y, z)
}
diff --git a/li/cil/oc/common/component/ScreenEnvironment.scala b/li/cil/oc/common/component/ScreenEnvironment.scala
index 53bf0b224..082ef94fa 100644
--- a/li/cil/oc/common/component/ScreenEnvironment.scala
+++ b/li/cil/oc/common/component/ScreenEnvironment.scala
@@ -52,9 +52,8 @@ trait ScreenEnvironment extends Node {
}
- def onScreenResolutionChange(w: Int, h: Int) = if (network != null) {
- network.sendToAll(this, "computer.signal", "screen_resized", w, h)
- }
+ def onScreenResolutionChange(w: Int, h: Int) =
+ network.foreach(_.sendToAll(this, "computer.signal", "screen_resized", w, h))
def onScreenSet(col: Int, row: Int, s: String) {}
diff --git a/li/cil/oc/common/tileentity/ItemComponentProxy.scala b/li/cil/oc/common/tileentity/ItemComponentProxy.scala
index fd30ecfbd..354d44702 100644
--- a/li/cil/oc/common/tileentity/ItemComponentProxy.scala
+++ b/li/cil/oc/common/tileentity/ItemComponentProxy.scala
@@ -1,7 +1,6 @@
package li.cil.oc.common.tileentity
import li.cil.oc.api.network.Node
-import li.cil.oc.server.computer.Drivers
import net.minecraft.inventory.IInventory
import net.minecraft.item.ItemStack
import net.minecraft.nbt.NBTTagCompound
@@ -9,6 +8,7 @@ import net.minecraft.nbt.NBTTagList
import net.minecraft.world.World
import li.cil.oc.api.driver.Slot
import li.cil.oc.common.component.Computer
+import li.cil.oc.server.driver.Registry
trait ItemComponentProxy extends IInventory with Node {
protected val inventory = new Array[ItemStack](8)
@@ -52,29 +52,29 @@ trait ItemComponentProxy extends IInventory with Node {
// NetworkNode
// ----------------------------------------------------------------------- //
- override protected def onConnect() = {
+ override protected def onConnect() {
super.onConnect()
for (slot <- 0 until inventory.length) {
itemNode(slot) match {
case None => // Ignore.
case Some(node) =>
- network.connect(this, node)
+ network.foreach(_.connect(this, node))
}
}
}
- override protected def onDisconnect() = {
+ override protected def onDisconnect() {
super.onDisconnect()
for (slot <- 0 until inventory.length) {
itemNode(slot) match {
case None => // Ignore.
case Some(node) =>
- node.network.remove(node)
+ node.network.foreach(_.remove(node))
}
}
}
- private def itemNode(slot: Int) = Drivers.driverFor(inventory(slot)) match {
+ private def itemNode(slot: Int) = Registry.driverFor(inventory(slot)) match {
case None => None
case Some(driver) => driver.node(inventory(slot))
}
@@ -115,7 +115,7 @@ trait ItemComponentProxy extends IInventory with Node {
if (!world.isRemote) itemNode(slot) match {
case None => // Nothing to do.
case Some(node) =>
- node.network.remove(node)
+ node.network.foreach(_.remove(node))
}
inventory(slot) = item
@@ -125,11 +125,11 @@ trait ItemComponentProxy extends IInventory with Node {
if (!world.isRemote) itemNode(slot) match {
case None => // Nothing to do.
case Some(node) =>
- network.connect(this, node)
+ network.foreach(_.connect(this, node))
}
}
- def isItemValidForSlot(slot: Int, item: ItemStack) = (slot, Drivers.driverFor(item)) match {
+ def isItemValidForSlot(slot: Int, item: ItemStack) = (slot, Registry.driverFor(item)) match {
case (_, None) => false // Invalid item.
case (0, Some(driver)) => driver.slot(item) == Slot.PSU
case (1 | 2 | 3, Some(driver)) => driver.slot(item) == Slot.PCI
diff --git a/li/cil/oc/common/tileentity/TileEntityComputer.scala b/li/cil/oc/common/tileentity/TileEntityComputer.scala
index 04064e2cf..96da718f4 100644
--- a/li/cil/oc/common/tileentity/TileEntityComputer.scala
+++ b/li/cil/oc/common/tileentity/TileEntityComputer.scala
@@ -96,11 +96,10 @@ class TileEntityComputer(isClient: Boolean) extends TileEntityRotatable with com
}
if (isRunning != computer.isRunning) {
isRunning = computer.isRunning
- if (network != null)
- if (isRunning)
- network.sendToAll(this, "computer.started")
- else
- network.sendToAll(this, "computer.stopped")
+ if (isRunning)
+ network.foreach(_.sendToAll(this, "computer.started"))
+ else
+ network.foreach(_.sendToAll(this, "computer.stopped"))
ServerPacketSender.sendComputerState(this, isRunning)
}
}
diff --git a/li/cil/oc/common/tileentity/TileEntityKeyboard.scala b/li/cil/oc/common/tileentity/TileEntityKeyboard.scala
index 3c81d1f4e..38a5b24e1 100644
--- a/li/cil/oc/common/tileentity/TileEntityKeyboard.scala
+++ b/li/cil/oc/common/tileentity/TileEntityKeyboard.scala
@@ -15,13 +15,13 @@ class TileEntityKeyboard extends TileEntityRotatable with Node {
message.data match {
case Array(p: Player, char: Char, code: Int) if message.name == "keyboard.keyDown" =>
if (isUseableByPlayer(p))
- network.sendToAll(this, "computer.signal", "key_down", char, code)
+ network.foreach(_.sendToAll(this, "computer.signal", "key_down", char, code))
case Array(p: Player, char: Char, code: Int) if message.name == "keyboard.keyUp" =>
if (isUseableByPlayer(p))
- network.sendToAll(this, "computer.signal", "key_up", char, code)
+ network.foreach(_.sendToAll(this, "computer.signal", "key_up", char, code))
case Array(p: Player, value: String) if message.name == "keyboard.clipboard" =>
if (isUseableByPlayer(p))
- network.sendToAll(this, "computer.signal", "clipboard", value)
+ network.foreach(_.sendToAll(this, "computer.signal", "clipboard", value))
case _ => // Ignore.
}
None
diff --git a/li/cil/oc/server/PacketHandler.scala b/li/cil/oc/server/PacketHandler.scala
index 50035bedd..1a8e4f001 100644
--- a/li/cil/oc/server/PacketHandler.scala
+++ b/li/cil/oc/server/PacketHandler.scala
@@ -68,18 +68,18 @@ class PacketHandler extends CommonPacketHandler {
def onKeyDown(p: PacketParser) =
p.readTileEntity[Node]() match {
case None => // Invalid packet.
- case Some(n) => n.network.sendToNeighbors(n, "keyboard.keyDown", p.player, p.readChar(), p.readInt())
+ case Some(n) => n.network.foreach(_.sendToNeighbors(n, "keyboard.keyDown", p.player, p.readChar(), p.readInt()))
}
def onKeyUp(p: PacketParser) =
p.readTileEntity[Node]() match {
case None => // Invalid packet.
- case Some(n) => n.network.sendToNeighbors(n, "keyboard.keyUp", p.player, p.readChar(), p.readInt())
+ case Some(n) => n.network.foreach(_.sendToNeighbors(n, "keyboard.keyUp", p.player, p.readChar(), p.readInt()))
}
def onClipboard(p: PacketParser) =
p.readTileEntity[Node]() match {
case None => // Invalid packet.
- case Some(n) => n.network.sendToNeighbors(n, "keyboard.clipboard", p.player, p.readUTF())
+ case Some(n) => n.network.foreach(_.sendToNeighbors(n, "keyboard.clipboard", p.player, p.readUTF()))
}
}
\ No newline at end of file
diff --git a/li/cil/oc/server/component/GraphicsCard.scala b/li/cil/oc/server/component/GraphicsCard.scala
index 47479b022..0a12d0d7d 100644
--- a/li/cil/oc/server/component/GraphicsCard.scala
+++ b/li/cil/oc/server/component/GraphicsCard.scala
@@ -1,5 +1,6 @@
package li.cil.oc.server.component
+import li.cil.oc.api.Network
import li.cil.oc.api.network.Message
import net.minecraft.nbt.NBTTagCompound
@@ -10,27 +11,28 @@ class GraphicsCard(nbt: NBTTagCompound) extends ItemComponent(nbt) {
override def receive(message: Message) = {
super.receive(message)
+ def trySend(f: Network => Option[Array[Any]]) = network.fold(None: Option[Array[Any]])(f)
message.data match {
case Array(screen: Double, w: Double, h: Double) if message.name == "gpu.resolution=" =>
if (supportedResolutions.contains((w.toInt, h.toInt)))
- network.sendToAddress(this, screen.toInt, "screen.resolution=", w.toInt, h.toInt)
+ trySend(_.sendToAddress(this, screen.toInt, "screen.resolution=", w.toInt, h.toInt))
else Some(Array(None, "unsupported resolution"))
case Array(screen: Double) if message.name == "gpu.resolution" =>
- network.sendToAddress(this, screen.toInt, "screen.resolution")
+ trySend(_.sendToAddress(this, screen.toInt, "screen.resolution"))
case Array(screen: Double) if message.name == "gpu.resolutions" =>
- network.sendToAddress(this, screen.toInt, "screen.resolutions") match {
+ trySend(_.sendToAddress(this, screen.toInt, "screen.resolutions")) match {
case Some(Array(resolutions@_*)) =>
Some(Array(supportedResolutions.intersect(resolutions): _*))
case _ => None
}
case Array(screen: Double, x: Double, y: Double, value: String) if message.name == "gpu.set" =>
- network.sendToAddress(this, screen.toInt, "screen.set", x.toInt - 1, y.toInt - 1, value)
+ trySend(_.sendToAddress(this, screen.toInt, "screen.set", x.toInt - 1, y.toInt - 1, value))
case Array(screen: Double, x: Double, y: Double, w: Double, h: Double, value: String) if message.name == "gpu.fill" =>
if (value != null && value.length == 1)
- network.sendToAddress(this, screen.toInt, "screen.fill", x.toInt - 1, y.toInt - 1, w.toInt, h.toInt, value.charAt(0))
+ trySend(_.sendToAddress(this, screen.toInt, "screen.fill", x.toInt - 1, y.toInt - 1, w.toInt, h.toInt, value.charAt(0)))
else Some(Array(None, "invalid fill value"))
case Array(screen: Double, x: Double, y: Double, w: Double, h: Double, tx: Double, ty: Double) if message.name == "gpu.copy" =>
- network.sendToAddress(this, screen.toInt, "screen.copy", x.toInt - 1, y.toInt - 1, w.toInt, h.toInt, tx.toInt, ty.toInt)
+ trySend(_.sendToAddress(this, screen.toInt, "screen.copy", x.toInt - 1, y.toInt - 1, w.toInt, h.toInt, tx.toInt, ty.toInt))
case _ => None
}
}
diff --git a/li/cil/oc/server/component/RedstoneCard.scala b/li/cil/oc/server/component/RedstoneCard.scala
index 23f455173..dfd5190a4 100644
--- a/li/cil/oc/server/component/RedstoneCard.scala
+++ b/li/cil/oc/server/component/RedstoneCard.scala
@@ -1,6 +1,7 @@
package li.cil.oc.server.component
import li.cil.oc.api.network.Message
+import li.cil.oc.api.network.Node
import net.minecraft.nbt.NBTTagCompound
import net.minecraft.tileentity.TileEntity
import net.minecraftforge.common.ForgeDirection
@@ -21,7 +22,9 @@ class RedstoneCard(nbt: NBTTagCompound) extends ItemComponent(nbt) {
}
}
- private def input(target: Int, side: Int) = if (side >= 0 && side < 6) network.node(target) match {
+ private def tryGet(target: Int) = network.fold(None: Option[Node])(_.node(target))
+
+ private def input(target: Int, side: Int) = if (side >= 0 && side < 6) tryGet(target) match {
case Some(r: RedstoneEnabled) => Some(Array(r.input(ForgeDirection.getOrientation(side)).asInstanceOf[Any]))
case Some(t: TileEntity) =>
val face = ForgeDirection.getOrientation(side.toInt)
@@ -31,7 +34,7 @@ class RedstoneCard(nbt: NBTTagCompound) extends ItemComponent(nbt) {
case _ => None // Can't work with this node.
} else None
- private def output(target: Int, side: Int) = if (side >= 0 && side < 6) network.node(target) match {
+ private def output(target: Int, side: Int) = if (side >= 0 && side < 6) tryGet(target) match {
case Some(r: RedstoneEnabled) => Some(Array(r.output(ForgeDirection.getOrientation(side)).asInstanceOf[Any]))
case Some(t: TileEntity) =>
val power = t.worldObj.isBlockProvidingPowerTo(t.xCoord, t.yCoord, t.zCoord, side.toInt)
@@ -39,7 +42,7 @@ class RedstoneCard(nbt: NBTTagCompound) extends ItemComponent(nbt) {
case _ => None // Can't work with this node.
} else None
- private def output(target: Int, side: Int, value: Int) = if (side >= 0 && side < 6) network.node(target) match {
+ private def output(target: Int, side: Int, value: Int) = if (side >= 0 && side < 6) tryGet(target) match {
case Some(r: RedstoneEnabled) => r.output(ForgeDirection.getOrientation(side)) = value
case _ => // Can't work with this node.
}
diff --git a/li/cil/oc/server/computer/Computer.scala b/li/cil/oc/server/computer/Computer.scala
index e76bd47bd..702a0ba27 100644
--- a/li/cil/oc/server/computer/Computer.scala
+++ b/li/cil/oc/server/computer/Computer.scala
@@ -5,8 +5,10 @@ import java.lang.Thread.UncaughtExceptionHandler
import java.util.concurrent._
import java.util.concurrent.atomic.AtomicInteger
import java.util.logging.Level
+import li.cil.oc.api.network.Node
import li.cil.oc.common.component
import li.cil.oc.common.tileentity.TileEntityComputer
+import li.cil.oc.server.driver
import li.cil.oc.{OpenComputers, Config}
import net.minecraft.nbt._
import net.minecraft.tileentity.TileEntity
@@ -47,7 +49,7 @@ class Computer(val owner: Environment) extends component.Computer with Runnable
private var state = Computer.State.Stopped
/** The internal Lua state. Only set while the computer is running. */
- private[computer] var lua: LuaState = null
+ private var lua: LuaState = null
/**
* The base memory consumption of the kernel. Used to permit a fixed base
@@ -151,7 +153,7 @@ class Computer(val owner: Environment) extends component.Computer with Runnable
signal("")
// Inject component added signals for all nodes in the network.
- owner.network.nodes(owner).foreach(node => signal("component_added", node.address))
+ owner.network.foreach(_.nodes(owner).foreach(node => signal("component_added", node.address)))
// All green, computer started successfully.
true
@@ -220,27 +222,27 @@ class Computer(val owner: Environment) extends component.Computer with Runnable
// to the coroutine.yield() that triggered the call.
lua.call(0, 1)
lua.checkType(2, LuaType.TABLE)
+ // Nothing should have been able to trigger a future.
+ assert(future.isEmpty)
+ // If the call lead to stop() being called we stop right now,
+ // otherwise we return the result to our executor.
+ if (state == Computer.State.Stopping)
+ close()
+ else
+ execute(Computer.State.SynchronizedReturn)
} catch {
case _: LuaMemoryAllocationException =>
// This can happen if we run out of memory while converting a Java
// exception to a string (which we have to do to avoid keeping
// userdata on the stack, which cannot be persisted).
OpenComputers.log.warning("Out of memory!") // TODO remove this when we have a component that can display crash messages
- owner.network.sendToAll(owner, "computer.crashed", "not enough memory")
+ owner.network.foreach(_.sendToAll(owner, "computer.crashed", "not enough memory"))
close()
case e: Throwable => {
OpenComputers.log.log(Level.WARNING, "Faulty Lua implementation for synchronized calls.", e)
close()
}
}
- // Nothing should have been able to trigger a future.
- assert(future.isEmpty)
- // If the call lead to stop() being called we stop right now,
- // otherwise we return the result to our executor.
- if (state == Computer.State.Stopping)
- close()
- else
- execute(Computer.State.SynchronizedReturn)
}
case _ => // Nothing special to do, just avoid match errors.
})
@@ -256,7 +258,6 @@ class Computer(val owner: Environment) extends component.Computer with Runnable
if (state != Computer.State.Stopped && init()) {
// Unlimit memory use while unpersisting.
- val memory = lua.getTotalMemory
lua.setTotalMemory(Integer.MAX_VALUE)
try {
// Try unpersisting Lua, because that's what all of the rest depends
@@ -295,11 +296,12 @@ class Computer(val owner: Environment) extends component.Computer with Runnable
}.toArray)
}).asJava)
+ kernelMemory = nbt.getInteger("kernelMemory")
timeStarted = nbt.getLong("timeStarted")
// Clean up some after we're done and limit memory again.
lua.gc(LuaState.GcAction.COLLECT, 0)
- lua.setTotalMemory(memory)
+ lua.setTotalMemory(kernelMemory + 16 * 1024)
// Start running our worker thread if we have to (for cases where it
// would not be re-started automatically in update()). We start with a
@@ -363,6 +365,7 @@ class Computer(val owner: Environment) extends component.Computer with Runnable
}
nbt.setTag("signals", list)
+ nbt.setInteger("kernelMemory", kernelMemory)
nbt.setLong("timeStarted", timeStarted)
}
catch {
@@ -523,7 +526,8 @@ class Computer(val owner: Environment) extends component.Computer with Runnable
}
lua.pushJavaFunction(ScalaFunction(lua =>
- owner.network.sendToAddress(owner, lua.checkInteger(1), lua.checkString(2), parseArguments(lua, 3): _*) match {
+ owner.network.fold(None: Option[Array[Any]])(_.
+ sendToAddress(owner, lua.checkInteger(1), lua.checkString(2), parseArguments(lua, 3): _*)) match {
case Some(Array(results@_*)) =>
results.foreach(pushResult(lua, _))
results.length
@@ -532,19 +536,44 @@ class Computer(val owner: Environment) extends component.Computer with Runnable
lua.setGlobal("sendToNode")
lua.pushJavaFunction(ScalaFunction(lua => {
- owner.network.sendToAll(owner, lua.checkString(1), parseArguments(lua, 2): _*)
+ owner.network.foreach(_.sendToAll(owner, lua.checkString(1), parseArguments(lua, 2): _*))
0
}))
lua.setGlobal("sendToAll")
lua.pushJavaFunction(ScalaFunction(lua => {
- owner.network.node(lua.checkInteger(1)) match {
+ owner.network.fold(None: Option[Node])(_.node(lua.checkInteger(1))) match {
case None => 0
case Some(node) => lua.pushString(node.name); 1
}
}))
lua.setGlobal("nodeName")
+ // Provide driver API code.
+ lua.pushJavaFunction(ScalaFunction(lua => {
+ val apis = driver.Registry.apis
+ lua.newTable(apis.length, 0)
+ for ((name, code) <- apis) {
+ lua.pushString(Source.fromInputStream(code).mkString)
+ code.close()
+ lua.setField(-2, name)
+ }
+ 1
+ }))
+ lua.setGlobal("drivers")
+
+ // Loads the init script. This is loaded and then run by the kernel as a
+ // separate coroutine to sandbox it and enforce timeouts and sandbox user
+ // scripts.
+ lua.pushJavaFunction(new JavaFunction() {
+ def invoke(lua: LuaState): Int = {
+ lua.pushString(Source.fromInputStream(classOf[Computer].
+ getResourceAsStream("/assets/opencomputers/lua/init.lua")).mkString)
+ 1
+ }
+ })
+ lua.setGlobal("init")
+
// Run the boot script. This sets up the permanent value tables as
// well as making the functions used for persisting/unpersisting
// available as globals. It also wraps the message sending functions
@@ -554,30 +583,15 @@ class Computer(val owner: Environment) extends component.Computer with Runnable
"/assets/opencomputers/lua/boot.lua"), "=boot", "t")
lua.call(0, 0)
- // Install all driver callbacks into the state. This is done once in
- // the beginning so that we can take the memory the callbacks use into
- // account when computing the kernel's memory use.
- Drivers.installOn(this)
-
- // Loads the init script. This is run by the kernel as a separate
- // coroutine to enforce timeouts and sandbox user scripts.
- lua.pushJavaFunction(new JavaFunction() {
- def invoke(lua: LuaState): Int = {
- lua.pushString(Source.fromInputStream(classOf[Computer].
- getResourceAsStream("/assets/opencomputers/lua/init.lua")).mkString)
- 1
- }
- })
- lua.setGlobal("init")
-
- // Load the basic kernel which takes care of handling signals by managing
- // the list of active processes. Whatever functionality we can we
- // implement in Lua, so we also implement most of the kernel's
- // functionality in Lua. Why? Because like this it's automatically
- // persisted for us without having to write more additional NBT stuff.
+ // Load the basic kernel which sets up the sandbox, loads the init script
+ // and then runs it in a coroutine with a debug hook checking for
+ // timeouts.
lua.load(classOf[Computer].getResourceAsStream(
"/assets/opencomputers/lua/kernel.lua"), "=kernel", "t")
- lua.newThread() // Leave it as the first value on the stack.
+ lua.newThread() // Left as the first value on the stack.
+ // Run to the first yield in kernel, to get a good idea of how much
+ // memory all the basic functionality we provide needs.
+ lua.pop(lua.resume(1, 0))
// Run the garbage collector to get rid of stuff left behind after the
// initialization phase to get a good estimate of the base memory usage
@@ -586,7 +600,7 @@ class Computer(val owner: Environment) extends component.Computer with Runnable
// underlying system (which may change across releases).
lua.gc(LuaState.GcAction.COLLECT, 0)
kernelMemory = lua.getTotalMemory - lua.getFreeMemory
- lua.setTotalMemory(kernelMemory + 64 * 1024)
+ lua.setTotalMemory(kernelMemory + 16 * 1024)
// Clear any left-over signals from a previous run.
signals.clear()
@@ -731,6 +745,8 @@ class Computer(val owner: Environment) extends component.Computer with Runnable
OpenComputers.log.warning("Kernel stopped unexpectedly.")
}
else {
+ // This can trigger another out of memory error if the original
+ // error was an out of memory error.
OpenComputers.log.warning("Computer crashed.\n" + lua.toString(3)) // TODO remove this when we have a component that can display crash messages
// TODO get this to the world as a computer.crashed message. problem: synchronizing it.
//owner.network.sendToAll(owner, "computer.crashed", lua.toString(3))
@@ -742,12 +758,16 @@ class Computer(val owner: Environment) extends component.Computer with Runnable
case e: LuaRuntimeException =>
OpenComputers.log.warning("Kernel crashed. This is a bug!\n" + e.toString + "\tat " + e.getLuaStackTrace.mkString("\n\tat "))
close()
- case e: LuaMemoryAllocationException => {
+ case e: LuaMemoryAllocationException =>
+ OpenComputers.log.warning("Out of memory!") // TODO remove this when we have a component that can display crash messages
+ // TODO get this to the world as a computer.crashed message. problem: synchronizing it.
+ //owner.network.sendToAll(owner, "computer.crashed", "not enough memory")
+ close()
+ case e: java.lang.Error if e.getMessage == "not enough memory" =>
OpenComputers.log.warning("Out of memory!") // TODO remove this when we have a component that can display crash messages
// TODO get this to the world as a computer.crashed message. problem: synchronizing it.
//owner.network.sendToAll(owner, "computer.crashed", "not enough memory")
close()
- }
}
// State has inevitably changed, mark as changed to save again.
diff --git a/li/cil/oc/server/computer/LuaStateFactory.scala b/li/cil/oc/server/computer/LuaStateFactory.scala
index 7f604e0fd..ffb2d50ef 100644
--- a/li/cil/oc/server/computer/LuaStateFactory.scala
+++ b/li/cil/oc/server/computer/LuaStateFactory.scala
@@ -5,7 +5,7 @@ import com.naef.jnlua.{JavaFunction, LuaState, NativeSupport}
import java.io.File
import java.io.FileOutputStream
import java.nio.channels.Channels
-import java.util.{Locale, Calendar}
+import java.util.{Formatter, Locale, Calendar}
import scala.util.Random
case class ScalaFunction(f: (LuaState) => Int) extends JavaFunction {
@@ -217,13 +217,13 @@ private[computer] object LuaStateFactory {
}))
state.setField(-2, "char")
- // TODO find
+ // TODO find (probably not necessary?)
- // TODO format
+ // TODO format (probably not necessary?)
- // TODO gmatch
+ // TODO gmatch (probably not necessary?)
- // TODO gsub
+ // TODO gsub (probably not necessary?)
state.pushJavaFunction(ScalaFunction(lua => {
lua.pushInteger(lua.checkString(1).length)
@@ -237,7 +237,7 @@ private[computer] object LuaStateFactory {
}))
state.setField(-2, "lower")
- // TODO match
+ // TODO match (probably not necessary?)
state.pushJavaFunction(ScalaFunction(lua => {
lua.pushString(lua.checkString(1).reverse)
diff --git a/li/cil/oc/server/computer/Drivers.scala b/li/cil/oc/server/driver/Registry.scala
similarity index 72%
rename from li/cil/oc/server/computer/Drivers.scala
rename to li/cil/oc/server/driver/Registry.scala
index 08e304a8d..35c3179d8 100644
--- a/li/cil/oc/server/computer/Drivers.scala
+++ b/li/cil/oc/server/driver/Registry.scala
@@ -1,13 +1,10 @@
-package li.cil.oc.server.computer
+package li.cil.oc.server.driver
-import com.naef.jnlua.LuaRuntimeException
-import li.cil.oc.OpenComputers
import li.cil.oc.api.driver.{Block, Item}
import net.minecraft.item.ItemStack
import net.minecraft.world.World
import scala.Some
import scala.collection.mutable.ArrayBuffer
-import scala.compat.Platform._
/**
* This class keeps track of registered drivers and provides installation logic
@@ -23,7 +20,7 @@ import scala.compat.Platform._
* usually require a component of the type the driver wraps to be installed in
* the computer, but may also provide context-free functions.
*/
-private[oc] object Drivers {
+private[oc] object Registry {
/** The list of registered block drivers. */
private val blocks = ArrayBuffer.empty[Block]
@@ -90,30 +87,11 @@ private[oc] object Drivers {
else None
/**
- * Used by the computer to initialize its Lua state, injecting the APIs of
- * all known drivers.
+ * Gets a list of all driver APIs.
+ *
+ * @return the apis of all known drivers.
*/
- private[computer] def installOn(computer: Computer) =
- (blocks ++ items).foreach(driver => {
- driver.api match {
- case None => // Nothing to do.
- case Some(code) =>
- val name = driver.getClass.getName
- try {
- computer.lua.load(code, "=" + name, "t") // ... func
- code.close()
- computer.lua.call(0, 0) // ...
- }
- catch {
- case e: LuaRuntimeException =>
- OpenComputers.log.warning(String.format(
- "Initialization code of driver %s threw an error: %s",
- name, e.getLuaStackTrace.mkString("", EOL, EOL)))
- case e: Throwable =>
- OpenComputers.log.warning(String.format(
- "Initialization code of driver %s threw an error: %s",
- name, e.getStackTraceString))
- }
- }
- })
+ def apis = (blocks ++ items) map (driver => (driver.getClass.getSimpleName, driver.api)) collect {
+ case (name, Some(code)) => (name, code)
+ }
}
diff --git a/li/cil/oc/server/network/Network.scala b/li/cil/oc/server/network/Network.scala
index 60ed741f4..b11ab0353 100644
--- a/li/cil/oc/server/network/Network.scala
+++ b/li/cil/oc/server/network/Network.scala
@@ -7,7 +7,7 @@ import _root_.net.minecraftforge.common.ForgeDirection
import _root_.net.minecraftforge.event.ForgeSubscribe
import _root_.net.minecraftforge.event.world.ChunkEvent
import java.util.logging.Level
-import li.cil.oc.OpenComputers
+import li.cil.oc.{api, OpenComputers}
import li.cil.oc.api.network.Visibility
import li.cil.oc.api.{network => net}
import scala.beans.BeanProperty
@@ -26,7 +26,7 @@ import scala.collection.mutable.ArrayBuffer
* It keeps the list of nodes as a lookup table for fast id->node resolving.
* Note that it is possible for multiple nodes to have the same ID, though.
*/
-class Network private(private val nodeMap: mutable.Map[Int, ArrayBuffer[Network.Node]]) extends net.Network {
+class Network private(private val nodeMap: mutable.Map[Int, ArrayBuffer[Network.Node]]) extends api.Network {
def this(node: net.Node) = {
this(mutable.Map({
if (node.address < 1)
@@ -36,7 +36,7 @@ class Network private(private val nodeMap: mutable.Map[Int, ArrayBuffer[Network.
send(new Network.ConnectMessage(node), List(node))
}
- nodes.foreach(_.network = this)
+ nodes.foreach(_.network = Some(this))
def connect(nodeA: net.Node, nodeB: net.Node) = {
val containsA = nodeMap.get(nodeA.address).exists(_.exists(_.data == nodeA))
@@ -67,7 +67,7 @@ class Network private(private val nodeMap: mutable.Map[Int, ArrayBuffer[Network.
private def add(oldNode: Network.Node, addedNode: net.Node) = {
// Check if the other node is new or if we have to merge networks.
- val (newNode, sendQueue) = if (addedNode.network == null) {
+ val (newNode, sendQueue) = if (addedNode.network.isEmpty) {
val newNode = new Network.Node(addedNode)
if (nodeMap.contains(addedNode.address) || addedNode.address < 1)
addedNode.address = findId()
@@ -84,14 +84,14 @@ class Network private(private val nodeMap: mutable.Map[Int, ArrayBuffer[Network.
nodes.foreach(node => sendQueue += ((new Network.ConnectMessage(node), List(addedNode))))
}
nodeMap.getOrElseUpdate(address, new ArrayBuffer[Network.Node]) += newNode
- addedNode.network = this
+ addedNode.network = Some(this)
(newNode, sendQueue)
}
else {
// Queue any messages to avoid side effects from receivers.
val sendQueue = mutable.Buffer.empty[(Network.Message, Iterable[net.Node])]
val thisNodes = nodes.toBuffer
- val otherNetwork = addedNode.network.asInstanceOf[Network]
+ val otherNetwork = addedNode.network.get.asInstanceOf[Network]
val otherNodes = otherNetwork.nodes.toBuffer
otherNodes.foreach(node => sendQueue += ((new Network.ConnectMessage(node), thisNodes)))
thisNodes.foreach(node => sendQueue += ((new Network.ConnectMessage(node), otherNodes)))
@@ -114,7 +114,7 @@ class Network private(private val nodeMap: mutable.Map[Int, ArrayBuffer[Network.
// Add nodes from other network into this network, including invalid nodes.
otherNetwork.nodeMap.values.flatten.foreach(node => {
nodeMap.getOrElseUpdate(node.data.address, new ArrayBuffer[Network.Node]) += node
- node.data.network = this
+ node.data.network = Some(this)
})
// Return the node object of the newly connected node for the next step.
@@ -152,7 +152,7 @@ class Network private(private val nodeMap: mutable.Map[Int, ArrayBuffer[Network.
case Some(list) => list.find(_.data == node) match {
case None => false
case Some(entry) => {
- node.network = null
+ node.network = None
// Removing a node may result in a net split, leaving us with multiple
// networks. The remove function returns all resulting networks, one
@@ -306,7 +306,7 @@ object Network {
tileEntities.
filter(_.isInstanceOf[net.Node]).
map(_.asInstanceOf[net.Node]).
- foreach(t => t.network.remove(t))
+ foreach(t => t.network.foreach(_.remove(t)))
}
private def onLoad(w: World, tileEntities: Iterable[TileEntity]) = if (!w.isRemote) {
@@ -321,12 +321,12 @@ object Network {
getNetworkNode(world, x + side.offsetX, y + side.offsetY, z + side.offsetZ) match {
case None => // Ignore.
case Some(neighborNode) =>
- if (neighborNode != null && neighborNode.network != null) {
- neighborNode.network.connect(neighborNode, node)
+ if (neighborNode.network.isDefined) {
+ neighborNode.network.foreach(_.connect(neighborNode, node))
}
}
}
- if (node.network == null) new Network(node)
+ if (node.network.isEmpty) new Network(node)
}
}