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 via load() 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) } }