things are slowly beginning to work again... it's still all a big mess, though

This commit is contained in:
Florian Nücke 2013-10-27 05:33:33 +01:00
parent af286d17fa
commit a7ec5c0ec0
30 changed files with 816 additions and 1013 deletions

View File

@ -87,7 +87,7 @@ local function wrap(f)
-- resume, so we get it via the yield. Thus: result = pcall(f, ...) -- resume, so we get it via the yield. Thus: result = pcall(f, ...)
if result[1] then if result[1] then
-- API call was successful, return the results. -- API call was successful, return the results.
return select(2, table.unpack(result, 1, result.n)) return table.unpack(result, 2, result.n)
else else
-- API call failed, re-throw the error. -- API call failed, re-throw the error.
error(result[2], 2) error(result[2], 2)
@ -95,5 +95,5 @@ local function wrap(f)
end end
end end
sendToAddress = wrap(sendToAddress) componentMethodsSynchronized = wrap(componentMethods)
nodeName = wrap(nodeName) componentInvokeSynchronized = wrap(componentInvoke)

View File

@ -1,16 +0,0 @@
driver.command = {}
function driver.command.value(block, value)
checkArg(1, block, "string")
if value then
checkArg(2, value, "string")
return send(block, "command.value=", value)
else
return send(block, "command.value")
end
end
function driver.command.run(block)
checkArg(1, block, "string")
return send(block, "command.run")
end

View File

@ -1,365 +0,0 @@
local mtab = {children={}}
local function segments(path)
path = path:gsub("\\", "/")
repeat local n; path, n = path:gsub("//", "/") until n == 0
local parts = {}
for part in path:gmatch("[^/]+") do
table.insert(parts, part)
end
local i = 1
while i <= #parts do
if parts[i] == "." then
table.remove(parts, i)
elseif parts[i] == ".." then
table.remove(parts, i)
i = i - 1
if i > 0 then
table.remove(parts, i)
else
i = 1
end
else
i = i + 1
end
end
return parts
end
local function findNode(path, create)
checkArg(1, path, "string")
local parts = segments(path)
local node = mtab
for i = 1, #parts do
if not node.children[parts[i]] then
if create then
node.children[parts[i]] = {children={}, parent=node}
else
return node, table.concat(parts, "/", i)
end
end
node = node.children[parts[i]]
end
return node
end
local function removeEmptyNodes(node)
while node and node.parent and not node.fs and not next(node.children) do
for k, c in pairs(node.parent.children) do
if c == node then
node.parent.children[k] = nil
break
end
end
node = node.parent
end
end
-------------------------------------------------------------------------------
driver.filesystem = {}
function driver.filesystem.canonical(path)
local result = table.concat(segments(path), "/")
if path:usub(1, 1) == "/" then
return "/" .. result
else
return result
end
end
function driver.filesystem.concat(pathA, pathB)
return driver.filesystem.canonical(pathA .. "/" .. pathB)
end
function driver.filesystem.name(path)
local parts = segments(path)
return parts[#parts]
end
function driver.filesystem.mount(fs, path)
if fs and path then
checkArg(1, fs, "string")
local node = findNode(path, true)
if node.fs then
return nil, "another filesystem is already mounted here"
end
node.fs = fs
else
local function path(node)
local result = "/"
while node and node.parent do
for name, child in pairs(node.parent.children) do
if child == node then
result = "/" .. name .. result
break
end
end
node = node.parent
end
return result
end
local queue = {mtab}
return function()
if #queue == 0 then
return nil
else
while true do
local node = table.remove(queue)
for _, child in pairs(node.children) do
table.insert(queue, child)
end
if node.fs then
return node.fs, path(node)
end
end
end
end
end
end
function driver.filesystem.umount(fsOrPath)
local node, rest = findNode(fsOrPath)
if not rest and node.fs then
node.fs = nil
removeEmptyNodes(node)
return true
else
local queue = {mtab}
for fs, path in driver.filesystem.mount() do
if fs == fsOrPath then
local node = findNode(path)
node.fs = nil
removeEmptyNodes(node)
return true
end
end
end
end
-------------------------------------------------------------------------------
function driver.filesystem.label(fs, label)
if type(label) == "string" then
return send(fs, "fs.label=", label)
end
return send(fs, "fs.label")
end
-------------------------------------------------------------------------------
function driver.filesystem.spaceTotal(path)
local node, rest = findNode(path)
if node.fs then
return send(node.fs, "fs.spaceTotal")
else
return nil, "no such device"
end
end
function driver.filesystem.spaceUsed(path)
local node, rest = findNode(path)
if node.fs then
return send(node.fs, "fs.spaceUsed")
else
return nil, "no such device"
end
end
-------------------------------------------------------------------------------
function driver.filesystem.exists(path)
local node, rest = findNode(path)
if not rest then -- virtual directory
return true
end
if node.fs then
return send(node.fs, "fs.exists", rest)
end
end
function driver.filesystem.size(path)
local node, rest = findNode(path)
if node.fs and rest then
return send(node.fs, "fs.size", rest)
end
return 0 -- no such file or directory or it's a virtual directory
end
function driver.filesystem.isDirectory(path)
local node, rest = findNode(path)
if node.fs and rest then
return send(node.fs, "fs.isDirectory", rest)
else
return not rest or rest:ulen() == 0
end
end
function driver.filesystem.lastModified(path)
local node, rest = findNode(path)
if node.fs and rest then
return send(node.fs, "fs.lastModified", rest)
end
return 0 -- no such file or directory or it's a virtual directory
end
function driver.filesystem.dir(path)
local node, rest = findNode(path)
if not node.fs and rest then
return nil, "no such file or directory"
end
local result
if node.fs then
result = table.pack(send(node.fs, "fs.dir", rest or ""))
if not result[1] and result[2] then
return nil, result[2]
end
else
result = {}
end
if not rest then
for k, _ in pairs(node.children) do
table.insert(result, k .. "/")
end
end
table.sort(result)
local i = 0
return function()
i = i + 1
return result[i]
end
end
-------------------------------------------------------------------------------
function driver.filesystem.makeDirectory(path)
local node, rest = findNode(path)
if node.fs and rest then
return send(node.fs, "fs.makeDirectory", rest)
end
return nil, "cannot create a directory in a virtual directory"
end
function driver.filesystem.remove(path)
local node, rest = findNode(path)
if node.fs and rest then
return send(node.fs, "fs.remove", rest)
end
return nil, "no such non-virtual directory"
end
function driver.filesystem.rename(oldPath, newPath)
local oldNode, oldRest = findNode(oldPath)
local newNode, newRest = findNode(newPath)
if oldNode.fs and oldRest and newNode.fs and newRest then
if oldNode.fs == newNode.fs then
return send(oldNode.fs, "fs.rename", oldRest, newRest)
else
local result, reason = driver.filesystem.copy(oldPath, newPath)
if result then
return driver.filesystem.remove(oldPath)
else
return nil, reason
end
end
end
return nil, "trying to read from or write to virtual directory"
end
function driver.filesystem.copy(fromPath, toPath)
local input, reason = io.open(fromPath, "rb")
if not input then
error(reason)
end
local output, reason = io.open(toPath, "wb")
if not output then
input:close()
error(reason)
end
repeat
local buffer, reason = input:read(1024)
if not buffer and reason then
error(reason)
elseif buffer then
local result, reason = output:write(buffer)
if not result then
input:close()
output:close()
error(reason)
end
end
until not buffer
input:close()
output:close()
return true
end
-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
local fileStream = {}
function fileStream:close()
send(self.fs, "fs.close", self.handle)
self.handle = nil
end
function fileStream:read(n)
if not self.handle then
return nil, "file is closed"
end
return send(self.fs, "fs.read", self.handle, n)
end
function fileStream:seek(whence, offset)
if not self.handle then
return nil, "file is closed"
end
return send(self.fs, "fs.seek", self.handle, whence, offset)
end
function fileStream:write(str)
if not self.handle then
return nil, "file is closed"
end
return send(self.fs, "fs.write", self.handle, str)
end
-------------------------------------------------------------------------------
function driver.filesystem.open(path, mode)
mode = tostring(mode or "r")
checkArg(2, mode, "string")
assert(({r=true, rb=true, w=true, wb=true, a=true, ab=true})[mode],
"bad argument #2 (r[b], w[b] or a[b] expected, got " .. mode .. ")")
local node, rest = findNode(path)
if not node.fs or not rest then
return nil, "file not found"
end
local handle, reason = send(node.fs, "fs.open", rest, mode)
if not handle then
return nil, reason
end
local stream = {fs = node.fs, handle = handle}
-- stream:close does a syscall, which yields, and that's not possible in
-- the __gc metamethod. So we start a timer to do the yield/cleanup.
local function cleanup(self)
if not self.handle then return end
-- save non-gc'ed values as upvalues
local fs = self.fs
local handle = self.handle
local function close()
send(fs, "fs.close", handle)
end
event.timer(0, close)
end
local metatable = {__index = fileStream,
__gc = cleanup,
__metatable = "filestream"}
return setmetatable(stream, metatable)
end

View File

@ -1,52 +0,0 @@
driver.gpu = {}
function driver.gpu.bind(gpu, screen)
checkArg(1, gpu, "string")
checkArg(2, screen, "string")
return send(gpu, "gpu.bind", screen)
end
function driver.gpu.resolution(gpu, w, h)
checkArg(1, gpu, "string")
if w and h then
checkArg(2, w, "number")
checkArg(3, h, "number")
return send(gpu, "gpu.resolution=", w, h)
else
return send(gpu, "gpu.resolution")
end
end
function driver.gpu.maxResolution(gpu)
checkArg(1, gpu, "string")
return send(gpu, "gpu.maxResolution")
end
function driver.gpu.set(gpu, col, row, value)
checkArg(1, gpu, "string")
checkArg(2, col, "number")
checkArg(3, row, "number")
checkArg(4, value, "string")
return send(gpu, "gpu.set", col, row, value)
end
function driver.gpu.fill(gpu, col, row, w, h, value)
checkArg(1, gpu, "string")
checkArg(2, col, "number")
checkArg(3, row, "number")
checkArg(4, w, "number")
checkArg(5, h, "number")
checkArg(6, value, "string")
return send(gpu, "gpu.fill", col, row, w, h, value:usub(1, 1))
end
function driver.gpu.copy(gpu, col, row, w, h, tx, ty)
checkArg(1, gpu, "string")
checkArg(2, col, "number")
checkArg(3, row, "number")
checkArg(4, w, "number")
checkArg(5, h, "number")
checkArg(6, tx, "number")
checkArg(7, ty, "number")
return send(gpu, "gpu.copy", col, row, w, h, tx, ty)
end

View File

@ -1,140 +0,0 @@
driver.keyboard = {}
driver.keyboard.keys = {
["1"] = 0x02,
["2"] = 0x03,
["3"] = 0x04,
["4"] = 0x05,
["5"] = 0x06,
["6"] = 0x07,
["7"] = 0x08,
["8"] = 0x09,
["9"] = 0x0A,
["0"] = 0x0B,
a = 0x1E,
b = 0x30,
c = 0x2E,
d = 0x20,
e = 0x12,
f = 0x21,
g = 0x22,
h = 0x23,
i = 0x17,
j = 0x24,
k = 0x25,
l = 0x26,
m = 0x32,
n = 0x31,
o = 0x18,
p = 0x19,
q = 0x10,
r = 0x13,
s = 0x1F,
t = 0x14,
u = 0x16,
v = 0x2F,
w = 0x11,
x = 0x2D,
y = 0x15,
z = 0x2C,
apostrophe = 0x28,
at = 0x91,
back = 0x0E, -- backspace
backslash = 0x2B,
colon = 0x92,
comma = 0x33,
enter = 0x1C,
equals = 0x0D,
grave = 0x29, -- accent grave
lbracket = 0x1A,
lcontrol = 0x1D,
lmenu = 0x38, -- left Alt
lshift = 0x2A,
minus = 0x0C,
numlock = 0x45,
pause = 0xC5,
period = 0x34,
rbracket = 0x1B,
rcontrol = 0x9D,
rmenu = 0xB8, -- right Alt
rshift = 0x36,
scroll = 0x46, -- Scroll Lock
semicolon = 0x27,
slash = 0x35, -- / on main keyboard
space = 0x39,
stop = 0x95,
tab = 0x0F,
underline = 0x93,
-- Keypad (and numpad with numlock off)
up = 0xC8,
down = 0xD0,
left = 0xCB,
right = 0xCD,
home = 0xC7,
["end"] = 0xCF,
pageUp = 0xC9,
pageDown = 0xD1,
insert = 0xD2,
delete = 0xD3,
-- Function keys
f1 = 0x3B,
f2 = 0x3C,
f3 = 0x3D,
f4 = 0x3E,
f5 = 0x3F,
f6 = 0x40,
f7 = 0x41,
f8 = 0x42,
f9 = 0x43,
f10 = 0x44,
f11 = 0x57,
f12 = 0x58,
f13 = 0x64,
f14 = 0x65,
f15 = 0x66,
f16 = 0x67,
f17 = 0x68,
f18 = 0x69,
f19 = 0x71,
-- Japanese keyboards
kana = 0x70,
kanji = 0x94,
convert = 0x79,
noconvert = 0x7B,
yen = 0x7D,
circumflex = 0x90,
ax = 0x96,
-- Numpad
numpad0 = 0x52,
numpad1 = 0x4F,
numpad2 = 0x50,
numpad3 = 0x51,
numpad4 = 0x4B,
numpad5 = 0x4C,
numpad6 = 0x4D,
numpad7 = 0x47,
numpad8 = 0x48,
numpad9 = 0x49,
numpadmul = 0x37,
numpaddiv = 0xB5,
numpadsub = 0x4A,
numpadadd = 0x4E,
numpaddecimal = 0x53,
numpadcomma = 0xB3,
numpadenter = 0x9C,
numpadequals = 0x8D,
}
-- Create inverse mapping for name lookup.
for k, v in pairs(driver.keyboard.keys) do
driver.keyboard.keys[v] = k
end
function driver.keyboard.isControl(char)
return type(char) == "number" and (char < 0x20 or (char >= 0x7F and char <= 0x9F))
end

View File

@ -1,36 +0,0 @@
driver.network = {}
function driver.network.open(card, port)
checkArg(1, card, "string")
checkArg(2, port, "number")
return send(card, "network.open=", port)
end
function driver.network.isOpen(card, port)
checkArg(1, card, "string")
checkArg(2, port, "number")
return send(card, "network.open", port)
end
function driver.network.close(card, port)
checkArg(1, card, "string")
if port then
checkArg(2, port, "number")
return send(card, "network.close", port)
else
return send(card, "network.close")
end
end
function driver.network.send(card, target, port, ...)
checkArg(1, card, "string")
checkArg(2, target, "string")
checkArg(3, port, "number")
return send(card, "network.send", target, port, ...)
end
function driver.network.broadcast(card, port, ...)
checkArg(1, card, "string")
checkArg(2, port, "number")
return send(card, "network.broadcast", port, ...)
end

View File

@ -1,35 +0,0 @@
driver.redstone = {}
function driver.redstone.analogInput(card, side)
checkArg(1, card, "string")
checkArg(2, side, "number")
return send(card, "redstone.input", side)
end
function driver.redstone.analogOutput(card, side, value)
checkArg(1, card, "string")
checkArg(2, side, "number")
checkArg(3, value, "number", "nil")
if value then
return send(card, "redstone.output=", side, value)
else
return send(card, "redstone.output", side)
end
end
function driver.redstone.input(card, side)
checkArg(1, card, "string")
checkArg(2, side, "number")
return driver.redstone.analogInput(card, side) > 0
end
function driver.redstone.output(card, side, value)
checkArg(1, card, "string")
checkArg(2, side, "number")
checkArg(3, value, "boolean", "nil")
if value ~= nil then
return driver.redstone.analogOutput(card, side, value and 15 or 0)
else
return driver.redstone.analogOutput(card, side) > 0
end
end

View File

@ -236,92 +236,101 @@ end
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
sandbox.driver = {} sandbox.component = {}
function sandbox.driver.componentType(address) function sandbox.component.list(filter)
checkArg(1, address, "string") checkArg(1, filter, "string", "nil")
return nodeName(address) return pairs(componentList(filter))
end end
do function sandbox.component.type(address)
local function send(address, name, ...) checkArg(1, address, "string")
checkArg(1, address, "string") return componentType(address)
checkArg(2, name, "string") end
return sendToAddress(address, name, ...)
function sandbox.component.invoke(address, method, ...)
checkArg(1, address, "string")
checkArg(2, method, "string")
return componentInvokeSynchronized(address, method, ...)
end
function sandbox.component.proxy(address)
checkArg(1, address, "string")
local type, reason = componentType(address)
if not type then
return nil, reason
end end
local env = setmetatable({send = send}, local proxy = {address = address, type = type}
{ __index = sandbox, __newindex = sandbox }) local methods = componentMethodsSynchronized(address)
for name, code in pairs(drivers()) do if methods then
local driver, reason = load(code, "=" .. name, "t", env) for _, method in ipairs(methods) do
if not driver then proxy[method] = function(...)
print("Failed loading driver '" .. name .. "': " .. reason) return componentInvokeSynchronized(address, method, ...)
else
local result, reason = xpcall(driver, function(msg)
return debug.traceback(msg, 2)
end)
if not result then
print("Failed initializing driver '" .. name .. "': " ..
(reason or "unknown error"))
end end
end end
end end
return proxy
end end
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
local function main(args) local function main()
local function bootstrap() local function bootstrap()
-- Prepare low-level print logic to give boot progress feedback. -- Prepare low-level print logic to give boot progress feedback.
local gpus = gpus() -- local gpus = gpus()
for i = 1, #gpus do -- for i = 1, #gpus do
local gpu = gpus[i] -- local gpu = gpus[i]
gpus[i] = nil -- gpus[i] = nil
local w, h = sandbox.driver.gpu.resolution(gpu) -- local w, h = sandbox.driver.gpu.resolution(gpu)
if w then -- if w then
if sandbox.driver.gpu.fill(gpu, 1, 1, w, h, " ") then -- if sandbox.driver.gpu.fill(gpu, 1, 1, w, h, " ") then
gpus[i] = {gpu, w, h} -- gpus[i] = {gpu, w, h}
end -- end
end -- end
end -- end
local l = 0 -- local l = 0
local function print(s) -- local function print(s)
l = l + 1 -- l = l + 1
for _, gpu in pairs(gpus) do -- for _, gpu in pairs(gpus) do
if l > gpu[3] then -- if l > gpu[3] then
sandbox.driver.gpu.copy(gpu[1], 1, 1, gpu[2], gpu[3], 0, -1) -- sandbox.driver.gpu.copy(gpu[1], 1, 1, gpu[2], gpu[3], 0, -1)
sandbox.driver.gpu.fill(gpu[1], 1, gpu[3], gpu[2], 1, " ") -- sandbox.driver.gpu.fill(gpu[1], 1, gpu[3], gpu[2], 1, " ")
end -- end
sandbox.driver.gpu.set(gpu[1], 1, math.min(l, gpu[3]), s) -- sandbox.driver.gpu.set(gpu[1], 1, math.min(l, gpu[3]), s)
end -- end
end -- end
print("Booting...") print("Booting...")
print("Mounting ROM and temporary file system.") print("Mounting ROM and temporary file system.")
local fs = sandbox.driver.filesystem local function rom(method, ...)
fs.mount(os.romAddress(), "/") return componentInvoke(os.romAddress(), method, ...)
fs.mount(os.tmpAddress(), "/tmp") end
-- Custom dofile implementation since we don't have the baselib yet. -- Custom dofile implementation since we don't have the baselib yet.
local function dofile(file) local function dofile(file)
print(" " .. file) print(" " .. file)
local stream, reason = fs.open(file) local handle, reason = rom("open", file)
if not stream then if not handle then
error(reason) error(reason)
end end
if stream then if handle then
local buffer = "" local buffer = ""
repeat repeat
local data = stream:read(math.huge) local data = rom("read", handle, math.huge)
if data then if data then
buffer = buffer .. data buffer = buffer .. data
end end
until not data until not data
stream:close() rom("close", handle)
stream = nil local program, reason = load(buffer, "=" .. file, "t", sandbox)
local program, reason = sandbox.load(buffer, "=" .. file)
buffer = nil buffer = nil
if program then if program then
return program() local result = table.pack(pcall(program))
if result[1] then
return table.unpack(result, 2, result.n)
else
error("error initializing lib '" .. file .. "': " .. result[2])
end
else else
error("error loading lib '" .. file .. "': " .. reason) error("error loading lib '" .. file .. "': " .. reason)
end end
@ -330,9 +339,9 @@ local function main(args)
print("Loading libraries.") print("Loading libraries.")
local init = {} local init = {}
for api in fs.dir("lib") do for _, api in ipairs(rom("list", "lib")) do
local path = fs.concat("lib", api) local path = "lib/" .. api
if not fs.isDirectory(path) then if not rom("isDirectory", path) then
local install = dofile(path) local install = dofile(path)
if type(install) == "function" then if type(install) == "function" then
table.insert(init, install) table.insert(init, install)
@ -347,14 +356,25 @@ local function main(args)
init = nil init = nil
print("Starting shell.") print("Starting shell.")
return coroutine.create(function(...) return coroutine.create(load([[
sandbox.event.fire(...) -- handle the first signal fs.mount(os.romAddress(), "/")
while true do fs.mount(os.tmpAddress(), "/tmp")
sandbox.os.execute("/bin/sh") for c, t in component.list() do
event.fire("component_added", c, t)
end end
end) while true do
local result, reason = os.execute("/bin/sh")
if not result then
error(reason)
end
end]], "=init", "t", sandbox))
end end
local co = bootstrap() local co = bootstrap()
-- Yield once to allow initializing up to here to get a memory baseline.
assert(coroutine.yield() == "dummy")
local args = {n=0}
while true do while true do
deadline = os.realTime() + timeout -- timeout global is set by host deadline = os.realTime() + timeout -- timeout global is set by host
if not debug.gethook(co) then if not debug.gethook(co) then
@ -362,7 +382,7 @@ local function main(args)
end end
local result = table.pack(coroutine.resume(co, table.unpack(args, 1, args.n))) local result = table.pack(coroutine.resume(co, table.unpack(args, 1, args.n)))
if not result[1] then if not result[1] then
error(result[2] or "unknown error", 0) error(tostring(result[2]), 0)
elseif coroutine.status(co) == "dead" then elseif coroutine.status(co) == "dead" then
error("computer stopped unexpectedly", 0) error("computer stopped unexpectedly", 0)
else else
@ -373,5 +393,4 @@ end
-- JNLua converts the coroutine to a string immediately, so we can't get the -- 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. -- traceback later. Because of that we have to do the error handling here.
-- Also, yield once to allow initializing up to here to get a memory baseline. return pcall(main)
return pcall(main, table.pack(coroutine.yield()))

View File

@ -9,7 +9,7 @@ for i = 1, #dirs do
if i > 1 then print() end if i > 1 then print() end
print("/" .. path .. ":") print("/" .. path .. ":")
end end
local list, reason = fs.dir(path) local list, reason = fs.list(path)
if not list then if not list then
print(reason) print(reason)
else else

View File

@ -1,29 +1,18 @@
local components = {}
local removing = {} local removing = {}
local primaries = {} local primaries = {}
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
component = {}
function component.isAvailable(componentType) function component.isAvailable(componentType)
return primaries[componentType] ~= nil return primaries[componentType] ~= nil
end end
function component.list(filter)
local address, ctype = nil
return function()
repeat
address, ctype = next(components, address)
until not address or type(filter) ~= "string" or ctype:match(filter)
return address, ctype
end
end
function component.isPrimary(address) function component.isPrimary(address)
local componentType = component.type(address) local componentType = component.type(address)
if componentType then if componentType then
return primaries[componentType] == address if component.isAvailable(componentType) then
return primaries[componentType].address == address
end
end end
return false return false
end end
@ -44,10 +33,10 @@ function component.primary(componentType, ...)
assert(address, "no such component") assert(address, "no such component")
end end
local wasAvailable = component.isAvailable(componentType) local wasAvailable = component.isAvailable(componentType)
primaries[componentType] = address primaries[componentType] = component.proxy(address)
if not wasAvailable and component.isAvailable(componentType) then if component.isAvailable(componentType) then
event.fire("component_available", componentType) event.fire("component_available", componentType)
elseif wasAvailable and not component.isAvailable(componentType) then elseif wasAvailable then
event.fire("component_unavailable", componentType) event.fire("component_unavailable", componentType)
end end
else else
@ -57,41 +46,22 @@ function component.primary(componentType, ...)
end end
end end
function component.type(address)
return components[address]
end
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
local function onComponentAdded(_, address) local function onComponentAdded(_, address, componentType)
if components[address] then
return false -- cancel this event, it is invalid
end
local componentType = driver.componentType(address)
if not componentType then
return -- component was removed again before signal could be processed
end
components[address] = componentType
if not component.isAvailable(componentType) then if not component.isAvailable(componentType) then
component.primary(componentType, address) component.primary(componentType, address)
end end
end end
local function onComponentRemoved(_, address) local function onComponentRemoved(_, address, componentType)
if removing[address] then return end if component.isAvailable(componentType) and
if not components[address] then return false end component.primary(componentType).address == address
local componentType = component.type(address) then
components[address] = nil
-- Redispatch with component type, since we already removed.
removing[address] = true -- don't cancel this one!
event.fire("component_removed", address, componentType)
removing[address] = false
if primaries[componentType] == address then
component.primary(componentType, nil) component.primary(componentType, nil)
address = component.list(componentType)() address = component.list(componentType)()
component.primary(componentType, address) component.primary(componentType, address)
end end
return false -- cancel this one
end end
return function() return function()

View File

@ -45,13 +45,11 @@ function event.fire(name, ...)
-- Copy the listener lists because they may be changed by callbacks. -- Copy the listener lists because they may be changed by callbacks.
local listeners = copy(listenersFor(name, false), listenersFor(name, true)) local listeners = copy(listenersFor(name, false), listenersFor(name, true))
for _, callback in ipairs(listeners) do for _, callback in ipairs(listeners) do
local result, message = pcall(callback, name, ...) local result, message = xpcall(callback, debug.traceback, name, ...)
if not result then if not result then
if not (event.error and pcall(event.error, message)) then if not (event.error and pcall(event.error, message)) then
os.shutdown() os.shutdown()
end end
elseif result and message == false then
break
end end
end end
end end

View File

@ -1,18 +1,153 @@
local isAutorunEnabled = true local isAutorunEnabled = true
local mtab = {children={}}
local function segments(path)
path = path:gsub("\\", "/")
repeat local n; path, n = path:gsub("//", "/") until n == 0
local parts = {}
for part in path:gmatch("[^/]+") do
table.insert(parts, part)
end
local i = 1
while i <= #parts do
if parts[i] == "." then
table.remove(parts, i)
elseif parts[i] == ".." then
table.remove(parts, i)
i = i - 1
if i > 0 then
table.remove(parts, i)
else
i = 1
end
else
i = i + 1
end
end
return parts
end
local function findNode(path, create)
checkArg(1, path, "string")
local parts = segments(path)
local node = mtab
for i = 1, #parts do
if not node.children[parts[i]] then
if create then
node.children[parts[i]] = {children={}, parent=node}
else
return node, table.concat(parts, "/", i)
end
end
node = node.children[parts[i]]
end
return node
end
local function removeEmptyNodes(node)
while node and node.parent and not node.fs and not next(node.children) do
for k, c in pairs(node.parent.children) do
if c == node then
node.parent.children[k] = nil
break
end
end
node = node.parent
end
end
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
filesystem = setmetatable({}, {__index=driver.filesystem}) filesystem = {}
fs = filesystem
fs.delete = fs.remove function filesystem.canonical(path)
fs.isFolder = fs.isDirectory local result = table.concat(segments(path), "/")
fs.list = fs.dir if path:usub(1, 1) == "/" then
fs.mkdir = fs.makeDirectory return "/" .. result
else
return result
end
end
function filesystem.concat(pathA, pathB)
return filesystem.canonical(pathA .. "/" .. pathB)
end
function filesystem.name(path)
local parts = segments(path)
return parts[#parts]
end
function filesystem.mount(fs, path)
if type(fs) == "string" then
fs = component.proxy(fs)
end
assert(type(fs) == "table", "bad argument #1 (file system proxy or address expected)")
if fs and path then
local node = findNode(path, true)
if node.fs then
return nil, "another filesystem is already mounted here"
end
node.fs = fs
else
local function path(node)
local result = "/"
while node and node.parent do
for name, child in pairs(node.parent.children) do
if child == node then
result = "/" .. name .. result
break
end
end
node = node.parent
end
return result
end
local queue = {mtab}
return function()
if #queue == 0 then
return nil
else
while true do
local node = table.remove(queue)
for _, child in pairs(node.children) do
table.insert(queue, child)
end
if node.fs then
return node.fs, path(node)
end
end
end
end
end
end
function filesystem.umount(fsOrPath)
local node, rest = findNode(fsOrPath)
if not rest and node.fs then
node.fs = nil
removeEmptyNodes(node)
return true
else
local function unmount(address)
local queue = {mtab}
for fs, path in filesystem.mount() do
if fs.address == address then
local node = findNode(path)
node.fs = nil
removeEmptyNodes(node)
return true
end
end
end
local fs = type(fsOrPath) == "table" and fsOrPath.address or fsOrPath
while unmount(fs) do end
end
end
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
function fs.autorun(...) function filesystem.autorun(...)
local args = table.pack(...) local args = table.pack(...)
if args.n > 0 then if args.n > 0 then
checkArg(1, args[1], "boolean") checkArg(1, args[1], "boolean")
@ -23,6 +158,226 @@ end
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
function filesystem.spaceTotal(path)
local node, rest = findNode(path)
if node.fs then
return node.fs.spaceTotal()
else
return nil, "invalid path"
end
end
function filesystem.spaceUsed(path)
local node, rest = findNode(path)
if node.fs then
return node.fs.spaceUsed()
else
return nil, "invalid path"
end
end
-------------------------------------------------------------------------------
function filesystem.exists(path)
local node, rest = findNode(path)
if not rest then -- virtual directory
return true
end
if node.fs then
return node.fs.exists(rest)
end
end
function filesystem.size(path)
local node, rest = findNode(path)
if node.fs and rest then
return node.fs.size(rest)
end
return 0 -- no such file or directory or it's a virtual directory
end
function filesystem.isDirectory(path)
local node, rest = findNode(path)
if node.fs and rest then
return node.fs.isDirectory(rest)
else
return not rest or rest:ulen() == 0
end
end
function filesystem.lastModified(path)
local node, rest = findNode(path)
if node.fs and rest then
return node.fs.lastModified(rest)
end
return 0 -- no such file or directory or it's a virtual directory
end
function filesystem.list(path)
local node, rest = findNode(path)
if not node.fs and rest then
return nil, "no such file or directory"
end
local result, reason
if node.fs then
result, reason = node.fs.list(rest or "")
if not result then
return nil, reason
end
else
result = {}
end
if not rest then
for k, _ in pairs(node.children) do
table.insert(result, k .. "/")
end
end
table.sort(result)
local i = 0
return function()
i = i + 1
return result[i]
end
end
-------------------------------------------------------------------------------
function filesystem.makeDirectory(path)
local node, rest = findNode(path)
if node.fs and rest then
return node.fs.makeDirectory(rest)
end
return nil, "cannot create a directory in a virtual directory"
end
function filesystem.remove(path)
local node, rest = findNode(path)
if node.fs and rest then
return node.fs.remove(rest)
end
return nil, "no such non-virtual directory"
end
function filesystem.rename(oldPath, newPath)
local oldNode, oldRest = findNode(oldPath)
local newNode, newRest = findNode(newPath)
if oldNode.fs and oldRest and newNode.fs and newRest then
if oldNode.fs.address == newNode.fs.address then
return oldNode.fs.rename(oldRest, newRest)
else
local result, reason = filesystem.copy(oldPath, newPath)
if result then
return filesystem.remove(oldPath)
else
return nil, reason
end
end
end
return nil, "trying to read from or write to virtual directory"
end
function filesystem.copy(fromPath, toPath)
local input, reason = io.open(fromPath, "rb")
if not input then
error(reason)
end
local output, reason = io.open(toPath, "wb")
if not output then
input:close()
error(reason)
end
repeat
local buffer, reason = input:read(1024)
if not buffer and reason then
error(reason)
elseif buffer then
local result, reason = output:write(buffer)
if not result then
input:close()
output:close()
error(reason)
end
end
until not buffer
input:close()
output:close()
return true
end
-------------------------------------------------------------------------------
local fileStream = {}
function fileStream:close()
self.fs.close(self.handle)
self.handle = nil
end
function fileStream:read(n)
if not self.handle then
return nil, "file is closed"
end
return self.fs.read(self.handle, n)
end
function fileStream:seek(whence, offset)
if not self.handle then
return nil, "file is closed"
end
return self.fs.seek(self.handle, whence, offset)
end
function fileStream:write(str)
if not self.handle then
return nil, "file is closed"
end
return self.fs.write(self.handle, str)
end
-------------------------------------------------------------------------------
function filesystem.open(path, mode)
mode = tostring(mode or "r")
checkArg(2, mode, "string")
assert(({r=true, rb=true, w=true, wb=true, a=true, ab=true})[mode],
"bad argument #2 (r[b], w[b] or a[b] expected, got " .. mode .. ")")
local node, rest = findNode(path)
if not node.fs or not rest then
return nil, "file not found"
end
local handle, reason = node.fs.open(rest, mode)
if not handle then
return nil, reason
end
local stream = {fs = node.fs, handle = handle}
-- stream:close does a syscall, which yields, and that's not possible in
-- the __gc metamethod. So we start a timer to do the yield/cleanup.
local function cleanup(self)
if not self.handle then return end
-- save non-gc'ed values as upvalues
local fs = self.fs
local handle = self.handle
local function close()
fs.close(handle)
end
event.timer(0, close)
end
local metatable = {__index = fileStream,
__gc = cleanup,
__metatable = "filestream"}
return setmetatable(stream, metatable)
end
-------------------------------------------------------------------------------
fs = filesystem
-------------------------------------------------------------------------------
local function onComponentAdded(_, address) local function onComponentAdded(_, address)
local componentType = component.type(address) local componentType = component.type(address)
if (componentType == "filesystem" or componentType == "disk_drive") and if (componentType == "filesystem" or componentType == "disk_drive") and

View File

@ -1,65 +1,22 @@
local resolutionX, resolutionY = nil, nil
-------------------------------------------------------------------------------
gpu = {}
function gpu.bind(screen)
return driver.gpu.bind(component.primary("gpu"), screen)
end
function gpu.resolution(w, h)
if w and h then
return driver.gpu.resolution(component.primary("gpu"), w, h)
elseif not resolutionX or not resolutionY then
resolutionX, resolutionY = driver.gpu.resolution(component.primary("gpu"))
end
return resolutionX, resolutionY
end
function gpu.maxResolution()
return driver.gpu.maxResolution(component.primary("gpu"))
end
function gpu.set(col, row, value)
return driver.gpu.set(component.primary("gpu"), col, row, value)
end
function gpu.fill(col, row, w, h, value)
return driver.gpu.fill(component.primary("gpu"), col, row, w, h, value)
end
function gpu.copy(col, row, w, h, tx, ty)
return driver.gpu.copy(component.primary("gpu"), col, row, w, h, tx, ty)
end
-------------------------------------------------------------------------------
local function onComponentAvailable(_, componentType) local function onComponentAvailable(_, componentType)
if (componentType == "screen" and component.isAvailable("gpu")) or if (componentType == "screen" and component.isAvailable("gpu")) or
(componentType == "gpu" and component.isAvailable("screen")) (componentType == "gpu" and component.isAvailable("screen"))
then then
gpu.bind(component.primary("screen")) component.primary("gpu").bind(component.primary("screen").address)
local maxX, maxY = gpu.maxResolution() local maxX, maxY = gpu.maxResolution()
gpu.resolution(maxX, maxY) component.primary("gpu").setResolution(maxX, maxY)
end end
end end
local function onComponentUnavailable(_, componentType) -- local function onScreenResized(_, address, width, height)
if componentType == "gpu" or componentType == "screen" then -- if component.isPrimary(address) and component.isAvailable("gpu") then
resolutionX, resolutionY = nil, nil -- component.primary("gpu").getResolution = function()
end -- return width, height
end -- end
-- end
local function onScreenResized(_, address, width, height) -- end
if component.primary("screen") == address then
resolutionX = width
resolutionY = height
end
end
return function() return function()
event.listen("component_available", onComponentAvailable) event.listen("component_available", onComponentAvailable)
event.listen("component_unavailable", onComponentUnavailable) -- event.listen("screen_resized", onScreenResized)
event.listen("screen_resized", onScreenResized)
end end

View File

@ -1,7 +1,9 @@
local file = {} local file = {}
function file:close() function file:close()
self:flush() if self.mode ~= "r" and self.mode ~= "rb" then
self:flush()
end
return self.stream:close() return self.stream:close()
end end
@ -252,7 +254,7 @@ end
function file.new(mode, stream, nogc) function file.new(mode, stream, nogc)
local result = { local result = {
mode = mode, mode = mode or "r",
stream = stream, stream = stream,
buffer = "", buffer = "",
bufferSize = math.max(128, math.min(8 * 1024, os.freeMemory() / 8)), bufferSize = math.max(128, math.min(8 * 1024, os.freeMemory() / 8)),
@ -364,7 +366,7 @@ function io.lines(filename, ...)
end end
function io.open(path, mode) function io.open(path, mode)
local stream, result = driver.filesystem.open(path, mode) local stream, result = fs.open(path, mode)
if stream then if stream then
return file.new(mode, stream) return file.new(mode, stream)
else else

View File

@ -1,10 +1,149 @@
keyboard = setmetatable({}, {__index = driver.keyboard})
local pressedChars = {} local pressedChars = {}
local pressedCodes = {} local pressedCodes = {}
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
keyboard = {}
keyboard.keys = {
["1"] = 0x02,
["2"] = 0x03,
["3"] = 0x04,
["4"] = 0x05,
["5"] = 0x06,
["6"] = 0x07,
["7"] = 0x08,
["8"] = 0x09,
["9"] = 0x0A,
["0"] = 0x0B,
a = 0x1E,
b = 0x30,
c = 0x2E,
d = 0x20,
e = 0x12,
f = 0x21,
g = 0x22,
h = 0x23,
i = 0x17,
j = 0x24,
k = 0x25,
l = 0x26,
m = 0x32,
n = 0x31,
o = 0x18,
p = 0x19,
q = 0x10,
r = 0x13,
s = 0x1F,
t = 0x14,
u = 0x16,
v = 0x2F,
w = 0x11,
x = 0x2D,
y = 0x15,
z = 0x2C,
apostrophe = 0x28,
at = 0x91,
back = 0x0E, -- backspace
backslash = 0x2B,
colon = 0x92,
comma = 0x33,
enter = 0x1C,
equals = 0x0D,
grave = 0x29, -- accent grave
lbracket = 0x1A,
lcontrol = 0x1D,
lmenu = 0x38, -- left Alt
lshift = 0x2A,
minus = 0x0C,
numlock = 0x45,
pause = 0xC5,
period = 0x34,
rbracket = 0x1B,
rcontrol = 0x9D,
rmenu = 0xB8, -- right Alt
rshift = 0x36,
scroll = 0x46, -- Scroll Lock
semicolon = 0x27,
slash = 0x35, -- / on main keyboard
space = 0x39,
stop = 0x95,
tab = 0x0F,
underline = 0x93,
-- Keypad (and numpad with numlock off)
up = 0xC8,
down = 0xD0,
left = 0xCB,
right = 0xCD,
home = 0xC7,
["end"] = 0xCF,
pageUp = 0xC9,
pageDown = 0xD1,
insert = 0xD2,
delete = 0xD3,
-- Function keys
f1 = 0x3B,
f2 = 0x3C,
f3 = 0x3D,
f4 = 0x3E,
f5 = 0x3F,
f6 = 0x40,
f7 = 0x41,
f8 = 0x42,
f9 = 0x43,
f10 = 0x44,
f11 = 0x57,
f12 = 0x58,
f13 = 0x64,
f14 = 0x65,
f15 = 0x66,
f16 = 0x67,
f17 = 0x68,
f18 = 0x69,
f19 = 0x71,
-- Japanese keyboards
kana = 0x70,
kanji = 0x94,
convert = 0x79,
noconvert = 0x7B,
yen = 0x7D,
circumflex = 0x90,
ax = 0x96,
-- Numpad
numpad0 = 0x52,
numpad1 = 0x4F,
numpad2 = 0x50,
numpad3 = 0x51,
numpad4 = 0x4B,
numpad5 = 0x4C,
numpad6 = 0x4D,
numpad7 = 0x47,
numpad8 = 0x48,
numpad9 = 0x49,
numpadmul = 0x37,
numpaddiv = 0xB5,
numpadsub = 0x4A,
numpadadd = 0x4E,
numpaddecimal = 0x53,
numpadcomma = 0xB3,
numpadenter = 0x9C,
numpadequals = 0x8D,
}
-- Create inverse mapping for name lookup.
for k, v in pairs(keyboard.keys) do
keyboard.keys[v] = k
end
function keyboard.isControl(char)
return type(char) == "number" and (char < 0x20 or (char >= 0x7F and char <= 0x9F))
end
function keyboard.isKeyDown(charOrCode) function keyboard.isKeyDown(charOrCode)
checkArg(1, charOrCode, "string", "number") checkArg(1, charOrCode, "string", "number")
if type(charOrCode) == "string" then if type(charOrCode) == "string" then

View File

@ -1,18 +0,0 @@
network = {}
net = network
function network.open(port)
return driver.network.open(component.primary("network"), port)
end
function network.close(port)
return driver.network.close(component.primary("network"), port)
end
function network.send(target, port, ...)
return driver.network.send(component.primary("network"), target, port, ...)
end
function network.broadcast(port, ...)
return driver.network.broadcast(component.primary("network"), port, ...)
end

View File

@ -21,19 +21,19 @@ os.execute = function(command)
return shell.execute(head, table.unpack(args)) return shell.execute(head, table.unpack(args))
end end
os.remove = driver.filesystem.remove os.remove = fs.remove
os.rename = driver.filesystem.rename os.rename = fs.rename
function os.sleep(timeout) function os.sleep(timeout)
event.wait(nil, timeout) event.wait(nil, timeout)
end end
function os.tmpname() function os.tmpname()
if driver.filesystem.exists("tmp") then if fs.exists("tmp") then
for i = 1, 10 do for i = 1, 10 do
local name = "tmp/" .. math.random(1, 0x7FFFFFFF) local name = "tmp/" .. math.random(1, 0x7FFFFFFF)
if not driver.filesystem.exists(name) then if not fs.exists(name) then
return name return name
end end
end end

View File

@ -10,24 +10,6 @@ end
redstone = {} redstone = {}
rs = redstone rs = redstone
function rs.analogInput(side)
return driver.redstone.analogInput(component.primary("redstone"), stringToSide(side))
end
function rs.analogOutput(side, value)
return driver.redstone.analogOutput(component.primary("redstone"), stringToSide(side), value)
end
function rs.input(side)
return driver.redstone.input(component.primary("redstone"), stringToSide(side))
end
function rs.output(side, value)
return driver.redstone.output(component.primary("redstone"), stringToSide(side), value)
end
-------------------------------------------------------------------------------
rs.sides = { rs.sides = {
[0] = "bottom", [0] = "bottom",
[1] = "top", [1] = "top",
@ -41,3 +23,25 @@ for k, v in pairs(rs.sides) do
end end
rs.sides.up = rs.sides.top rs.sides.up = rs.sides.top
rs.sides.down = rs.sides.bottom rs.sides.down = rs.sides.bottom
function rs.proxy(address)
local proxy = component.proxy(address)
local analogInput = proxy.analogInput
local analogOutput = proxy.analogOutput
function proxy.analogInput(side)
return analogInput(stringToSide(side))
end
function proxy.analogOutput(side, value)
return analogOutput(stringToSide(side), value)
end
function proxy.input(side)
return proxy.analogInput(side) > 0
end
function proxy.output(side, value)
if value then
return proxy.analogOutput(side, value and 15 or 0)
else
return proxy.analogOutput(side) > 0
end
end
end

View File

@ -1,6 +1,8 @@
local cursorX, cursorY = 1, 1 local cursorX, cursorY = 1, 1
local cursorBlink = nil local cursorBlink = nil
local function gpu() return component.primary("gpu") end
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
term = {} term = {}
@ -11,23 +13,23 @@ end
function term.clear() function term.clear()
if term.isAvailable() then if term.isAvailable() then
local w, h = gpu.resolution() local w, h = gpu().getResolution()
gpu.fill(1, 1, w, h, " ") gpu().fill(1, 1, w, h, " ")
end end
cursorX, cursorY = 1, 1 cursorX, cursorY = 1, 1
end end
function term.clearLine() function term.clearLine()
if term.isAvailable() then if term.isAvailable() then
local w = gpu.resolution() local w = gpu().getResolution()
gpu.fill(1, cursorY, w, 1, " ") gpu().fill(1, cursorY, w, 1, " ")
end end
cursorX = 1 cursorX = 1
end end
function term.cursor(col, row) function term.cursor(col, row)
if col and row then if col and row then
local w, h = gpu.resolution() local w, h = gpu().getResolution()
cursorX = math.min(math.max(col, 1), w) cursorX = math.min(math.max(col, 1), w)
cursorY = math.min(math.max(row, 1), h) cursorY = math.min(math.max(row, 1), h)
end end
@ -39,7 +41,7 @@ function term.cursorBlink(enabled)
cursorBlink.state = not cursorBlink.state cursorBlink.state = not cursorBlink.state
if term.isAvailable() then if term.isAvailable() then
local char = cursorBlink.state and cursorBlink.solid or cursorBlink.alt local char = cursorBlink.state and cursorBlink.solid or cursorBlink.alt
gpu.set(cursorX, cursorY, char) gpu().set(cursorX, cursorY, char)
end end
end end
local function start(alt) local function start(alt)
@ -93,24 +95,24 @@ function term.read(history)
local function remove() local function remove()
local x = start - 1 + cursor - scroll local x = start - 1 + cursor - scroll
local _, y = term.cursor() local _, y = term.cursor()
local w = gpu.resolution() local w = gpu().getResolution()
gpu.copy(x + 1, y, w - x, 1, -1, 0) gpu().copy(x + 1, y, w - x, 1, -1, 0)
local cursor = cursor + (w - x) local cursor = cursor + (w - x)
local char = history[current]:usub(cursor, cursor) local char = history[current]:usub(cursor, cursor)
if char:ulen() == 0 then if char:ulen() == 0 then
char = " " char = " "
end end
gpu.set(w, y, char) gpu().set(w, y, char)
end end
local function render() local function render()
local _, y = term.cursor() local _, y = term.cursor()
local w = gpu.resolution() local w = gpu().getResolution()
local str = history[current]:usub(1 + scroll, 1 + scroll + w - (start - 1)) local str = history[current]:usub(1 + scroll, 1 + scroll + w - (start - 1))
str = str .. string.rep(" ", (w - (start - 1)) - str:ulen()) str = str .. string.rep(" ", (w - (start - 1)) - str:ulen())
gpu.set(start, y, str) gpu().set(start, y, str)
end end
local function scrollEnd() local function scrollEnd()
local w = gpu.resolution() local w = gpu().getResolution()
cursor = history[current]:ulen() + 1 cursor = history[current]:ulen() + 1
scroll = math.max(0, cursor - (w - (start - 1))) scroll = math.max(0, cursor - (w - (start - 1)))
render() render()
@ -118,36 +120,36 @@ function term.read(history)
local function scrollLeft() local function scrollLeft()
scroll = scroll - 1 scroll = scroll - 1
local _, y = term.cursor() local _, y = term.cursor()
local w = gpu.resolution() local w = gpu().getResolution()
gpu.copy(start, y, w - start - 1, 1, 1, 0) gpu().copy(start, y, w - start - 1, 1, 1, 0)
local cursor = w - (start - 1) + scroll local cursor = w - (start - 1) + scroll
local char = history[current]:usub(cursor, cursor) local char = history[current]:usub(cursor, cursor)
if char:ulen() == 0 then if char:ulen() == 0 then
char = " " char = " "
end end
gpu.set(1, y, char) gpu().set(1, y, char)
end end
local function scrollRight() local function scrollRight()
scroll = scroll + 1 scroll = scroll + 1
local _, y = term.cursor() local _, y = term.cursor()
local w = gpu.resolution() local w = gpu().getResolution()
gpu.copy(start + 1, y, w - start, 1, -1, 0) gpu().copy(start + 1, y, w - start, 1, -1, 0)
local cursor = w - (start - 1) + scroll local cursor = w - (start - 1) + scroll
local char = history[current]:usub(cursor, cursor) local char = history[current]:usub(cursor, cursor)
if char:ulen() == 0 then if char:ulen() == 0 then
char = " " char = " "
end end
gpu.set(w, y, char) gpu().set(w, y, char)
end end
local function update() local function update()
local _, y = term.cursor() local _, y = term.cursor()
local w = gpu.resolution() local w = gpu().getResolution()
local cursor = cursor - 1 local cursor = cursor - 1
local x = start - 1 + cursor - scroll local x = start - 1 + cursor - scroll
if cursor < history[current]:ulen() then if cursor < history[current]:ulen() then
gpu.copy(x, y, w - x, 1, 1, 0) gpu().copy(x, y, w - x, 1, 1, 0)
end end
gpu.set(x, y, history[current]:usub(cursor, cursor)) gpu().set(x, y, history[current]:usub(cursor, cursor))
end end
local function copyIfNecessary() local function copyIfNecessary()
if current ~= #history then if current ~= #history then
@ -166,7 +168,7 @@ function term.read(history)
end end
local function handleKeyPress(char, code) local function handleKeyPress(char, code)
if not term.isAvailable() then return end if not term.isAvailable() then return end
local w, h = gpu.resolution() local w, h = gpu().getResolution()
local cancel, blink = false, false local cancel, blink = false, false
term.cursorBlink(false) term.cursorBlink(false)
if code == keyboard.keys.back then if code == keyboard.keys.back then
@ -334,15 +336,15 @@ function term.write(value, wrap)
return return
end end
value = value:gsub("\t", " ") value = value:gsub("\t", " ")
local w, h = gpu.resolution() local w, h = gpu().getResolution()
local function checkCursor() local function checkCursor()
if cursorX > w then if cursorX > w then
cursorX = 1 cursorX = 1
cursorY = cursorY + 1 cursorY = cursorY + 1
end end
if cursorY > h then if cursorY > h then
gpu.copy(1, 1, w, h, 0, -1) gpu().copy(1, 1, w, h, 0, -1)
gpu.fill(1, h, w, 1, " ") gpu().fill(1, h, w, 1, " ")
cursorY = h cursorY = h
end end
end end
@ -350,12 +352,12 @@ function term.write(value, wrap)
while wrap and line:ulen() > w - cursorX + 1 do while wrap and line:ulen() > w - cursorX + 1 do
local partial = line:usub(1, w - cursorX + 1) local partial = line:usub(1, w - cursorX + 1)
line = line:usub(partial:ulen() + 1) line = line:usub(partial:ulen() + 1)
gpu.set(cursorX, cursorY, partial) gpu().set(cursorX, cursorY, partial)
cursorX = cursorX + partial:ulen() cursorX = cursorX + partial:ulen()
checkCursor() checkCursor()
end end
if line:ulen() > 0 then if line:ulen() > 0 then
gpu.set(cursorX, cursorY, line) gpu().set(cursorX, cursorY, line)
cursorX = cursorX + line:ulen() cursorX = cursorX + line:ulen()
end end
if nl:ulen() == 1 then if nl:ulen() == 1 then

View File

@ -6,7 +6,6 @@ object Config {
val resourceDomain = "opencomputers" val resourceDomain = "opencomputers"
val savePath = "opencomputers/" val savePath = "opencomputers/"
val scriptPath = "/assets/" + resourceDomain + "/lua/" val scriptPath = "/assets/" + resourceDomain + "/lua/"
val driverPath = "/assets/" + resourceDomain + "/lua/drivers/"
val screenResolutionsByTier = Array((50, 16), (80, 25), (160, 50)) val screenResolutionsByTier = Array((50, 16), (80, 25), (160, 50))

View File

@ -104,7 +104,7 @@ class Delegator[Child <: Delegate](id: Int) extends Block(id, Material.iron) {
subBlock(metadata) match { subBlock(metadata) match {
case Some(subBlock) => { case Some(subBlock) => {
world.getBlockTileEntity(x, y, z) match { world.getBlockTileEntity(x, y, z) match {
case environment: Environment => case environment: Environment if environment.node != null && environment.node.network != null =>
environment.node.network.remove(environment.node) environment.node.network.remove(environment.node)
case _ => // Nothing special to do. case _ => // Nothing special to do.
} }
@ -293,7 +293,7 @@ class Delegator[Child <: Delegate](id: Int) extends Block(id, Material.iron) {
subBlock(world, x, y, z) match { subBlock(world, x, y, z) match {
case Some(subBlock) => { case Some(subBlock) => {
world.getBlockTileEntity(x, y, z) match { world.getBlockTileEntity(x, y, z) match {
case _: Node => Network.joinOrCreateNetwork(world, x, y, z) case _: Environment => Network.joinOrCreateNetwork(world, x, y, z)
case _ => // Nothing special to do. case _ => // Nothing special to do.
} }
subBlock.onBlockAdded(world, x, y, z) subBlock.onBlockAdded(world, x, y, z)

View File

@ -59,12 +59,6 @@ class Computer(isClient: Boolean) extends Rotatable with component.Computer.Envi
} }
// ----------------------------------------------------------------------- // // ----------------------------------------------------------------------- //
//
// override def save(nbt: NBTTagCompound) {}
//
// override def load(nbt: NBTTagCompound) {}
override def onMessage(message: Message) = null
override def updateEntity() = if (!worldObj.isRemote) { override def updateEntity() = if (!worldObj.isRemote) {
instance.update() instance.update()

View File

@ -16,7 +16,7 @@ class CommandBlock(entity: TileEntityCommandBlock) extends ManagedComponent {
@LuaCallback("setValue") @LuaCallback("setValue")
def setValue(message: Message): Array[Object] = { def setValue(message: Message): Array[Object] = {
val value = message.checkString(0) val value = message.checkString(1)
entity.setCommand(value) entity.setCommand(value)
entity.worldObj.markBlockForUpdate(entity.xCoord, entity.yCoord, entity.zCoord) entity.worldObj.markBlockForUpdate(entity.xCoord, entity.yCoord, entity.zCoord)
result(true) result(true)

View File

@ -94,10 +94,10 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl
// ----------------------------------------------------------------------- // // ----------------------------------------------------------------------- //
def recomputeMemory() = if (lua != null) { def recomputeMemory() = stateMonitor.synchronized(if (lua != null) {
lua.gc(LuaState.GcAction.COLLECT, 0) lua.gc(LuaState.GcAction.COLLECT, 0)
lua.setTotalMemory(kernelMemory + owner.installedMemory) lua.setTotalMemory(kernelMemory + owner.installedMemory)
} })
// ----------------------------------------------------------------------- // // ----------------------------------------------------------------------- //
@ -113,6 +113,9 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl
// Mark state change in owner, to send it to clients. // Mark state change in owner, to send it to clients.
owner.markAsChanged() owner.markAsChanged()
// Push a dummy signal to get the computer going.
signal("dummy")
// All green, computer started successfully. // All green, computer started successfully.
owner.node.network.sendToVisible(owner.node, "computer.started") owner.node.network.sendToVisible(owner.node, "computer.started")
true true
@ -156,17 +159,17 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl
true true
}) })
def addComponent(address: String, name: String) { def addComponent(component: Component) {
if (!components.contains(address)) { if (!components.contains(component.address)) {
components += address -> name components += component.address -> component.name
signal("component_added", address) signal("component_added", component.address, component.name)
} }
} }
def removeComponent(address: String) { def removeComponent(component: Component) {
if (components.contains(address)) { if (components.contains(component.address)) {
components -= address components -= component.address
signal("component_removed", address) signal("component_removed", component.address, component.name)
} }
} }
@ -483,6 +486,12 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl
case Some(value) => lua = value case Some(value) => lua = value
} }
// Connect the ROM and `/tmp` node to our owner.
if (owner.node.network != null) {
rom.foreach(rom => owner.node.network.connect(owner.node, rom.node))
tmp.foreach(tmp => owner.node.network.connect(owner.node, tmp.node))
}
try { try {
// Push a couple of functions that override original Lua API functions or // Push a couple of functions that override original Lua API functions or
// that add new functionality to it. // that add new functionality to it.
@ -570,17 +579,16 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl
// Until we get to ingame screens we log to Java's stdout. // Until we get to ingame screens we log to Java's stdout.
lua.pushScalaFunction(lua => { lua.pushScalaFunction(lua => {
for (i <- 1 to lua.getTop) lua.`type`(i) match { println((1 to lua.getTop).map(i => lua.`type`(i) match {
case LuaType.NIL => print("nil") case LuaType.NIL => "nil"
case LuaType.BOOLEAN => print(lua.toBoolean(i)) case LuaType.BOOLEAN => lua.toBoolean(i)
case LuaType.NUMBER => print(lua.toNumber(i)) case LuaType.NUMBER => lua.toNumber(i)
case LuaType.STRING => print(lua.toString(i)) case LuaType.STRING => lua.toString(i)
case LuaType.TABLE => print("table") case LuaType.TABLE => "table"
case LuaType.FUNCTION => print("function") case LuaType.FUNCTION => "function"
case LuaType.THREAD => print("thread") case LuaType.THREAD => "thread"
case LuaType.LIGHTUSERDATA | LuaType.USERDATA => print("userdata") case LuaType.LIGHTUSERDATA | LuaType.USERDATA => "userdata"
} }).mkString(" "))
println()
0 0
}) })
lua.setGlobal("print") lua.setGlobal("print")
@ -596,7 +604,7 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl
if (filter.isEmpty || name.matches(filter.get)) { if (filter.isEmpty || name.matches(filter.get)) {
lua.pushString(address) lua.pushString(address)
lua.pushString(name) lua.pushString(name)
lua.rawSet(-2) lua.rawSet(-3)
} }
} }
1 1
@ -616,6 +624,22 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl
}) })
lua.setGlobal("componentType") lua.setGlobal("componentType")
lua.pushScalaFunction(lua => {
owner.node.network.sendToAddress(owner.node, lua.checkString(1), "component.methods") match {
case Array(names: Array[String]) =>
lua.newTable()
for ((name, index) <- names.zipWithIndex) {
lua.pushString(name)
lua.rawSet(-2, index + 1)
}
1
case _ =>
lua.pushNil()
lua.pushString("no such component")
2
}
})
lua.setGlobal("componentMethods")
// Set up functions used to send component.invoke network messages. // Set up functions used to send component.invoke network messages.
def parseArgument(lua: LuaState, index: Int): AnyRef = lua.`type`(index) match { def parseArgument(lua: LuaState, index: Int): AnyRef = lua.`type`(index) match {
@ -643,6 +667,7 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl
} }
def pushResult(lua: LuaState, value: Any): Unit = value match { def pushResult(lua: LuaState, value: Any): Unit = value match {
case null | Unit => lua.pushNil()
case value: Boolean => lua.pushBoolean(value) case value: Boolean => lua.pushBoolean(value)
case value: Byte => lua.pushNumber(value) case value: Byte => lua.pushNumber(value)
case value: Short => lua.pushNumber(value) case value: Short => lua.pushNumber(value)
@ -680,45 +705,43 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl
case _ => throw new Exception("no such component") case _ => throw new Exception("no such component")
}) match { }) match {
case Array(results@_*) => case Array(results@_*) =>
lua.pushBoolean(true)
results.foreach(pushResult(lua, _)) results.foreach(pushResult(lua, _))
1 + results.length results.length
case _ => case _ =>
lua.pushBoolean(true) 0
1
} }
} catch { } catch {
case e: Throwable if e.getMessage != null && !e.getMessage.isEmpty => case e: Throwable if e.getMessage != null && !e.getMessage.isEmpty =>
lua.pushBoolean(false) lua.pushNil()
lua.pushString(e.getMessage) lua.pushString(e.getMessage)
2 2
case _: FileNotFoundException => case _: FileNotFoundException =>
lua.pushBoolean(false) lua.pushNil()
lua.pushString("file not found") lua.pushString("file not found")
2 2
case _: SecurityException => case _: SecurityException =>
lua.pushBoolean(false) lua.pushNil()
lua.pushString("access denied") lua.pushString("access denied")
2 2
case _: IOException => case _: IOException =>
lua.pushBoolean(false) lua.pushNil()
lua.pushString("i/o error") lua.pushString("i/o error")
2 2
case _: NoSuchMethodException => case _: NoSuchMethodException =>
lua.pushBoolean(false) lua.pushNil()
lua.pushString("no such method") lua.pushString("no such method")
2 2
case _: IllegalArgumentException => case _: IllegalArgumentException =>
lua.pushBoolean(false) lua.pushNil()
lua.pushString("bad argument") lua.pushString("bad argument")
2 2
case _: Throwable => case _: Throwable =>
lua.pushBoolean(false) lua.pushNil()
lua.pushString("unknown error") lua.pushString("unknown error")
2 2
} }
}) })
lua.setGlobal("componentCall") lua.setGlobal("componentInvoke")
// List of installed GPUs - this is used during boot to allow giving some // List of installed GPUs - this is used during boot to allow giving some
// feedback on the process, since booting can take some time. It feels a // feedback on the process, since booting can take some time. It feels a
@ -750,7 +773,11 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl
lua.newThread() // Left 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 // Run to the first yield in kernel, to get a good idea of how much
// memory all the basic functionality we provide needs. // memory all the basic functionality we provide needs.
lua.pop(lua.resume(1, 0)) val results = lua.resume(1, 0)
if (lua.status(1) != LuaState.YIELD)
if (!lua.toBoolean(-2)) throw new Exception(lua.toString(-1))
else throw new Exception("kernel return unexpectedly")
lua.pop(results)
// Run the garbage collector to get rid of stuff left behind after the // 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 // initialization phase to get a good estimate of the base memory usage
@ -765,12 +792,6 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl
// Clear any left-over signals from a previous run. // Clear any left-over signals from a previous run.
signals.clear() signals.clear()
// Connect the ROM and `/tmp` node to our owner.
if (owner.node.network != null) {
rom.foreach(rom => owner.node.network.connect(owner.node, rom.node))
tmp.foreach(tmp => owner.node.network.connect(owner.node, tmp.node))
}
return true return true
} }
catch { catch {
@ -997,17 +1018,16 @@ object Computer {
// ----------------------------------------------------------------------- // // ----------------------------------------------------------------------- //
override def onMessage(message: Message) = { override def onMessage(message: Message) = {
message.source match {
case component: Component if component.canBeSeenBy(node) =>
message.name match {
case "system.connect" => instance.addComponent(component)
case "system.disconnect" => instance.removeComponent(component)
case _ =>
}
case _ =>
}
if (instance.isRunning) { if (instance.isRunning) {
message.source match {
case node: Component if node.canBeSeenBy(node) =>
message.name match {
case "system.connect" =>
instance.addComponent(message.source.address, message.source.name)
case "system.disconnect" =>
instance.removeComponent(message.source.address)
}
case _ =>
}
message.data match { message.data match {
// Arbitrary signals, usually from other components. // Arbitrary signals, usually from other components.
case Array(name: String, args@_*) if message.name == "computer.signal" => case Array(name: String, args@_*) if message.name == "computer.signal" =>

View File

@ -22,7 +22,7 @@ class FileSystem(val fileSystem: api.fs.FileSystem) extends ManagedComponent {
@LuaCallback("setLabel") @LuaCallback("setLabel")
def setLabel(message: Message): Array[Object] = { def setLabel(message: Message): Array[Object] = {
label = message.checkString(0) label = message.checkString(1)
if (label.length > 16) if (label.length > 16)
label = label.substring(0, 16) label = label.substring(0, 16)
result(true) result(true)
@ -43,24 +43,24 @@ class FileSystem(val fileSystem: api.fs.FileSystem) extends ManagedComponent {
@LuaCallback("exists") @LuaCallback("exists")
def exists(message: Message): Array[Object] = def exists(message: Message): Array[Object] =
result(fileSystem.exists(clean(message.checkString(0)))) result(fileSystem.exists(clean(message.checkString(1))))
@LuaCallback("size") @LuaCallback("size")
def size(message: Message): Array[Object] = def size(message: Message): Array[Object] =
result(fileSystem.size(clean(message.checkString(0)))) result(fileSystem.size(clean(message.checkString(1))))
@LuaCallback("isDirectory") @LuaCallback("isDirectory")
def isDirectory(message: Message): Array[Object] = def isDirectory(message: Message): Array[Object] =
result(fileSystem.isDirectory(clean(message.checkString(0)))) result(fileSystem.isDirectory(clean(message.checkString(1))))
@LuaCallback("lastModified") @LuaCallback("lastModified")
def lastModified(message: Message): Array[Object] = def lastModified(message: Message): Array[Object] =
result(fileSystem.lastModified(clean(message.checkString(0)))) result(fileSystem.lastModified(clean(message.checkString(1))))
@LuaCallback("list") @LuaCallback("list")
def list(message: Message): Array[Object] = def list(message: Message): Array[Object] =
Option(fileSystem.list(clean(message.checkString(0)))) match { Option(fileSystem.list(clean(message.checkString(1)))) match {
case Some(list) => list.map(_.asInstanceOf[AnyRef]) case Some(list) => Array(list)
case _ => null case _ => null
} }
@ -68,23 +68,23 @@ class FileSystem(val fileSystem: api.fs.FileSystem) extends ManagedComponent {
def makeDirectory(message: Message): Array[Object] = { def makeDirectory(message: Message): Array[Object] = {
def recurse(path: String): Boolean = !fileSystem.exists(path) && (fileSystem.makeDirectory(path) || def recurse(path: String): Boolean = !fileSystem.exists(path) && (fileSystem.makeDirectory(path) ||
(recurse(path.split("/").dropRight(1).mkString("/")) && fileSystem.makeDirectory(path))) (recurse(path.split("/").dropRight(1).mkString("/")) && fileSystem.makeDirectory(path)))
result(recurse(clean(message.checkString(0)))) result(recurse(clean(message.checkString(1))))
} }
@LuaCallback("remove") @LuaCallback("remove")
def remove(message: Message): Array[Object] = { def remove(message: Message): Array[Object] = {
def recurse(parent: String): Boolean = (!fileSystem.isDirectory(parent) || def recurse(parent: String): Boolean = (!fileSystem.isDirectory(parent) ||
fileSystem.list(parent).forall(child => recurse(parent + "/" + child))) && fileSystem.delete(parent) fileSystem.list(parent).forall(child => recurse(parent + "/" + child))) && fileSystem.delete(parent)
result(recurse(clean(message.checkString(0)))) result(recurse(clean(message.checkString(1))))
} }
@LuaCallback("rename") @LuaCallback("rename")
def rename(message: Message): Array[Object] = def rename(message: Message): Array[Object] =
result(fileSystem.rename(clean(message.checkString(0)), clean(message.checkString(1)))) result(fileSystem.rename(clean(message.checkString(1)), clean(message.checkString(2))))
@LuaCallback("close") @LuaCallback("close")
def close(message: Message): Array[Object] = { def close(message: Message): Array[Object] = {
val handle = message.checkInteger(0) val handle = message.checkInteger(1)
Option(fileSystem.file(handle)) match { Option(fileSystem.file(handle)) match {
case Some(file) => case Some(file) =>
owners.get(message.source.address) match { owners.get(message.source.address) match {
@ -101,8 +101,8 @@ class FileSystem(val fileSystem: api.fs.FileSystem) extends ManagedComponent {
if (owners.get(message.source.address).fold(false)(_.size >= Config.maxHandles)) if (owners.get(message.source.address).fold(false)(_.size >= Config.maxHandles))
result(Unit, "too many open handles") result(Unit, "too many open handles")
else { else {
val path = message.checkString(0) val path = message.checkString(1)
val mode = message.checkString(1) val mode = if (message.data.length > 2) message.checkString(2) else "r"
val handle = fileSystem.open(clean(path), Mode.parse(mode)) val handle = fileSystem.open(clean(path), Mode.parse(mode))
if (handle > 0) { if (handle > 0) {
owners.getOrElseUpdate(message.source.address, mutable.Set.empty[Int]) += handle owners.getOrElseUpdate(message.source.address, mutable.Set.empty[Int]) += handle
@ -112,8 +112,8 @@ class FileSystem(val fileSystem: api.fs.FileSystem) extends ManagedComponent {
@LuaCallback("read") @LuaCallback("read")
def read(message: Message): Array[Object] = { def read(message: Message): Array[Object] = {
val handle = message.checkInteger(0) val handle = message.checkInteger(1)
val n = message.checkInteger(1) val n = message.checkInteger(2)
Option(fileSystem.file(handle)) match { Option(fileSystem.file(handle)) match {
case None => throw new IOException("bad file descriptor") case None => throw new IOException("bad file descriptor")
case Some(file) => case Some(file) =>
@ -139,9 +139,9 @@ class FileSystem(val fileSystem: api.fs.FileSystem) extends ManagedComponent {
@LuaCallback("seek") @LuaCallback("seek")
def seek(message: Message): Array[Object] = { def seek(message: Message): Array[Object] = {
val handle = message.checkInteger(0) val handle = message.checkInteger(1)
val whence = message.checkString(1) val whence = message.checkString(2)
val offset = message.checkInteger(2) val offset = message.checkInteger(3)
Option(fileSystem.file(handle)) match { Option(fileSystem.file(handle)) match {
case Some(file) => case Some(file) =>
whence match { whence match {
@ -157,8 +157,8 @@ class FileSystem(val fileSystem: api.fs.FileSystem) extends ManagedComponent {
@LuaCallback("write") @LuaCallback("write")
def write(message: Message): Array[Object] = { def write(message: Message): Array[Object] = {
val handle = message.checkInteger(0) val handle = message.checkInteger(1)
val value = message.checkByteArray(1) val value = message.checkByteArray(2)
Option(fileSystem.file(handle)) match { Option(fileSystem.file(handle)) match {
case Some(file) => file.write(value); result(true) case Some(file) => file.write(value); result(true)
case _ => throw new IOException("bad file descriptor") case _ => throw new IOException("bad file descriptor")

View File

@ -15,7 +15,7 @@ class GraphicsCard(val maxResolution: (Int, Int)) extends ManagedComponent {
@LuaCallback("bind") @LuaCallback("bind")
def bind(message: Message): Array[Object] = { def bind(message: Message): Array[Object] = {
val address = message.checkString(0) val address = message.checkString(1)
node.network.node(address) match { node.network.node(address) match {
case null => Array(Unit, "invalid address") case null => Array(Unit, "invalid address")
case value if value.name() == "screen" => case value if value.name() == "screen" =>
@ -30,8 +30,8 @@ class GraphicsCard(val maxResolution: (Int, Int)) extends ManagedComponent {
@LuaCallback("setResolution") @LuaCallback("setResolution")
def setResolution(message: Message): Array[Object] = { def setResolution(message: Message): Array[Object] = {
val w = message.checkInteger(0) val w = message.checkInteger(1)
val h = message.checkInteger(1) val h = message.checkInteger(2)
val (mw, mh) = maxResolution val (mw, mh) = maxResolution
if (w <= mw && h <= mh) if (w <= mw && h <= mh)
trySend("screen.resolution=", w, h) trySend("screen.resolution=", w, h)
@ -50,18 +50,18 @@ class GraphicsCard(val maxResolution: (Int, Int)) extends ManagedComponent {
@LuaCallback("set") @LuaCallback("set")
def set(message: Message): Array[Object] = { def set(message: Message): Array[Object] = {
val x = message.checkInteger(0) val x = message.checkInteger(1)
val y = message.checkInteger(1) val y = message.checkInteger(2)
val value = message.checkString(2) val value = message.checkString(3)
trySend("screen.set", x - 1, y - 1, value) trySend("screen.set", x - 1, y - 1, value)
} }
@LuaCallback("fill") @LuaCallback("fill")
def fill(message: Message): Array[Object] = { def fill(message: Message): Array[Object] = {
val x = message.checkInteger(0) val x = message.checkInteger(1)
val y = message.checkInteger(1) val y = message.checkInteger(2)
val w = message.checkInteger(2) val w = message.checkInteger(3)
val h = message.checkInteger(3) val h = message.checkInteger(4)
val value = message.checkString(4) val value = message.checkString(4)
if (value.length == 1) if (value.length == 1)
trySend("screen.fill", x - 1, y - 1, w, h, value.charAt(0)) trySend("screen.fill", x - 1, y - 1, w, h, value.charAt(0))
@ -71,12 +71,12 @@ class GraphicsCard(val maxResolution: (Int, Int)) extends ManagedComponent {
@LuaCallback("copy") @LuaCallback("copy")
def copy(message: Message): Array[Object] = { def copy(message: Message): Array[Object] = {
val x = message.checkInteger(0) val x = message.checkInteger(1)
val y = message.checkInteger(1) val y = message.checkInteger(2)
val w = message.checkInteger(2) val w = message.checkInteger(3)
val h = message.checkInteger(3) val h = message.checkInteger(4)
val tx = message.checkInteger(4) val tx = message.checkInteger(5)
val ty = message.checkInteger(5) val ty = message.checkInteger(6)
trySend("screen.copy", x - 1, y - 1, w, h, tx, ty) trySend("screen.copy", x - 1, y - 1, w, h, tx, ty)
} }

View File

@ -17,40 +17,40 @@ class NetworkCard extends ManagedComponent {
@LuaCallback("open") @LuaCallback("open")
def open(message: Message): Array[Object] = { def open(message: Message): Array[Object] = {
val port = checkPort(message.checkInteger(0)) val port = checkPort(message.checkInteger(1))
result(openPorts.add(port)) result(openPorts.add(port))
} }
@LuaCallback("close") @LuaCallback("close")
def close(message: Message): Array[Object] = { def close(message: Message): Array[Object] = {
if (message.data.length == 0) { if (message.data.length < 2) {
openPorts.clear() openPorts.clear()
result(true) result(true)
} }
else { else {
val port = checkPort(message.checkInteger(0)) val port = checkPort(message.checkInteger(1))
result(openPorts.remove(port)) result(openPorts.remove(port))
} }
} }
@LuaCallback("isOpen") @LuaCallback("isOpen")
def isOpen(message: Message): Array[Object] = { def isOpen(message: Message): Array[Object] = {
val port = checkPort(message.checkInteger(0)) val port = checkPort(message.checkInteger(1))
result(openPorts.contains(port)) result(openPorts.contains(port))
} }
@LuaCallback("send") @LuaCallback("send")
def send(message: Message): Array[Object] = { def send(message: Message): Array[Object] = {
val address = message.checkString(0) val address = message.checkString(1)
val port = checkPort(message.checkInteger(1)) val port = checkPort(message.checkInteger(2))
node.network.sendToAddress(node, address, "network.message", Seq(Int.box(port)) ++ message.data.drop(2): _*) node.network.sendToAddress(node, address, "network.message", Seq(Int.box(port)) ++ message.data.drop(3): _*)
result(true) result(true)
} }
@LuaCallback("broadcast") @LuaCallback("broadcast")
def broadcast(message: Message): Array[Object] = { def broadcast(message: Message): Array[Object] = {
val port = checkPort(message.checkInteger(0)) val port = checkPort(message.checkInteger(1))
node.network.sendToVisible(node, "network.message", Seq(Int.box(port)) ++ message.data.drop(1): _*) node.network.sendToVisible(node, "network.message", Seq(Int.box(port)) ++ message.data.drop(2): _*)
result(true) result(true)
} }

View File

@ -10,22 +10,22 @@ class RedstoneCard extends ManagedComponent {
@LuaCallback("getInput") @LuaCallback("getInput")
def getInput(message: Message): Array[Object] = { def getInput(message: Message): Array[Object] = {
val side = message.checkInteger(0) val side = message.checkInteger(1)
node.network.sendToAddress(node, message.source.address, node.network.sendToAddress(node, message.source.address,
"redstone.input", ForgeDirection.getOrientation(side)) "redstone.input", ForgeDirection.getOrientation(side))
} }
@LuaCallback("getOutput") @LuaCallback("getOutput")
def getOutput(message: Message): Array[Object] = { def getOutput(message: Message): Array[Object] = {
val side = message.checkInteger(0) val side = message.checkInteger(1)
node.network.sendToAddress(node, message.source.address, node.network.sendToAddress(node, message.source.address,
"redstone.output", ForgeDirection.getOrientation(side)) "redstone.output", ForgeDirection.getOrientation(side))
} }
@LuaCallback("setOutput") @LuaCallback("setOutput")
def setOutput(message: Message): Array[Object] = { def setOutput(message: Message): Array[Object] = {
val side = message.checkInteger(0) val side = message.checkInteger(1)
val value = message.checkInteger(1) val value = message.checkInteger(2)
node.network.sendToAddress(node, message.source.address, node.network.sendToAddress(node, message.source.address,
"redstone.output=", ForgeDirection.getOrientation(side.toInt), Int.box(value)) "redstone.output=", ForgeDirection.getOrientation(side.toInt), Int.box(value))
} }

View File

@ -2,6 +2,7 @@ package li.cil.oc.server.network
import cpw.mods.fml.common.FMLCommonHandler import cpw.mods.fml.common.FMLCommonHandler
import cpw.mods.fml.relauncher.Side import cpw.mods.fml.relauncher.Side
import java.lang.reflect.InvocationTargetException
import li.cil.oc.api import li.cil.oc.api
import li.cil.oc.api.network.environment.{Environment, LuaCallback} import li.cil.oc.api.network.environment.{Environment, LuaCallback}
import li.cil.oc.api.network.{Message, Visibility} import li.cil.oc.api.network.{Message, Visibility}
@ -60,18 +61,16 @@ class Component(host: Environment, name: String, reachability: Visibility) exten
// ----------------------------------------------------------------------- // // ----------------------------------------------------------------------- //
override def receive(message: Message) = { override def receive(message: Message) = {
if (message.name == "computer.started" && canBeSeenBy(message.source))
network.sendToAddress(this, message.source.address, "computer.signal", "component_added")
if (message.name == "component.methods") if (message.name == "component.methods")
if (canBeSeenBy(message.source)) if (canBeSeenBy(message.source))
Array(luaCallbacks.keys.toSeq: _*) Array(Array(luaCallbacks.keys.toSeq: _*))
else null else null
else if (message.name == "component.call") else if (message.name == "component.invoke") {
luaCallbacks.get(message.name) match { luaCallbacks.get(message.data()(0).asInstanceOf[String]) match {
case Some(callback) => callback(host, message) case Some(callback) => callback(host, message)
case _ => throw new NoSuchMethodException() case _ => throw new NoSuchMethodException()
} }
}
else super.receive(message) else super.receive(message)
} }
@ -113,7 +112,11 @@ object Component {
throw new IllegalArgumentException("Invalid use of LuaCallback annotation (name must not be null or empty).") throw new IllegalArgumentException("Invalid use of LuaCallback annotation (name must not be null or empty).")
} }
else if (!callbacks.contains(a.value)) { else if (!callbacks.contains(a.value)) {
callbacks += a.value -> ((o, e) => m.invoke(o, e).asInstanceOf[Array[Object]]) callbacks += a.value -> ((o, e) => try {
m.invoke(o, e).asInstanceOf[Array[Object]]
} catch {
case e: InvocationTargetException => throw e.getCause
})
} }
} }
) )

View File

@ -1,10 +1,9 @@
package li.cil.oc.server.network package li.cil.oc.server.network
import java.util.logging.Level import li.cil.oc.api
import li.cil.oc.api.network.Visibility import li.cil.oc.api.network.Visibility
import li.cil.oc.api.network.environment.Environment import li.cil.oc.api.network.environment.Environment
import li.cil.oc.server.network import li.cil.oc.server.network
import li.cil.oc.{api, OpenComputers}
import net.minecraft.block.Block import net.minecraft.block.Block
import net.minecraft.tileentity.TileEntity import net.minecraft.tileentity.TileEntity
import net.minecraft.world.{IBlockAccess, World} import net.minecraft.world.{IBlockAccess, World}
@ -292,12 +291,13 @@ class Network private(private val addressedNodes: mutable.Map[String, Network.No
} }
private def send(message: Network.Message, targets: Iterable[api.network.Node]) = { private def send(message: Network.Message, targets: Iterable[api.network.Node]) = {
var error: Option[Throwable] = None
def protectedSend(target: api.network.Node) = try { def protectedSend(target: api.network.Node) = try {
//println("receive(" + message.name + "(" + message.data.mkString(", ") + "): " + message.source.address.get + ":" + message.source.name + " -> " + target.address.get + ":" + target.name + ")") //println("receive(" + message.name + "(" + message.data.mkString(", ") + "): " + message.source.address.get + ":" + message.source.name + " -> " + target.address.get + ":" + target.name + ")")
target.receive(message) target.receive(message)
} catch { } catch {
case e: Throwable => case e: Throwable =>
OpenComputers.log.log(Level.WARNING, "Error in message handler", e) if (error.isEmpty) error = Some(e)
null null
} }
@ -313,7 +313,10 @@ class Network private(private val addressedNodes: mutable.Map[String, Network.No
case null => // Ignore. case null => // Ignore.
case r => result = r case r => result = r
} }
result error match {
case Some(e) => throw e
case _ => result
}
} }
} }
} }
@ -477,7 +480,7 @@ object Network extends api.detail.NetworkAPI {
private def typeError(index: Int, have: AnyRef, want: String) = private def typeError(index: Int, have: AnyRef, want: String) =
new IllegalArgumentException( new IllegalArgumentException(
"bad argument #%d (%s expected, got %have)". "bad argument #%d (%s expected, got %s)".
format(index + 1, want, typeName(have))) format(index + 1, want, typeName(have)))
private def typeName(value: AnyRef): String = value match { private def typeName(value: AnyRef): String = value match {