Merge branch 'master' of cil.li:oc

Conflicts:
	li/cil/oc/Blocks.scala
This commit is contained in:
Johannes Lohrer 2013-10-06 22:51:56 +02:00
commit 4489781e34
26 changed files with 1362 additions and 694 deletions

View File

@ -64,6 +64,7 @@ flattenAndStore("_ENV", _ENV)
--]] --]]
-- OK, I admit this is a little crazy... here goes: -- OK, I admit this is a little crazy... here goes:
local function wrap(f) local function wrap(f)
assert(f)
-- This is the function that replaces the original API function. It is -- This is the function that replaces the original API function. It is
-- called from userland when it wants something from a driver. -- called from userland when it wants something from a driver.
return function(...) return function(...)
@ -94,5 +95,5 @@ local function wrap(f)
end end
end end
sendToNode = wrap(sendToNode) sendToAddress = wrap(sendToAddress)
nodeName = wrap(nodeName) nodeName = wrap(nodeName)

View File

@ -0,0 +1,752 @@
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 + i
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.fs = {}
function driver.fs.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.fs.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.fs.mount() do
if fs == fsOrPath then
local node = findNode(path)
node.fs = nil
removeEmptyNodes(node)
return true
end
end
end
end
-------------------------------------------------------------------------------
function driver.fs.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.fs.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.fs.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.fs.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.fs.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.list", rest or ""))
if not result[1] 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)
return table.unpack(result)
end
-------------------------------------------------------------------------------
function driver.fs.remove(path)
local node, rest = findNode(path)
if node.fs and rest then
return send(node.fs, "fs.remove", rest)
end
end
function driver.fs.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.fs.copy(oldPath, newPath)
if result then
return driver.fs.remove(oldPath)
else
return nil, reason
end
end
end
end
function driver.fs.copy(fromPath, toPath)
--[[ TODO ]]
return nil, "not implemented"
end
-------------------------------------------------------------------------------
local file = {}
function file:close()
if self.handle then
self:flush()
return self.stream:close()
end
end
function file:flush()
if not self.handle then
return nil, "file is closed"
end
if #self.buffer > 0 then
local result, reason = self.stream:write(self.buffer)
if result then
self.buffer = ""
else
if reason then
return nil, reason
else
return nil, "bad file descriptor"
end
end
end
return self
end
function file:lines(...)
local args = table.pack(...)
return function()
local result = table.pack(self:read(table.unpack(args, 1, args.n)))
if not result[1] and result[2] then
error(result[2])
end
return table.unpack(result, 1, result.n)
end
end
function file:read(...)
if not self.handle then
return nil, "file is closed"
end
local function readChunk()
local result, reason = self.stream:read(self.bufferSize)
if result then
self.buffer = self.buffer .. result
return self
else -- error or eof
return nil, reason
end
end
local function readBytesOrChars(n)
local len, sub
if self.mode == "r" then
len = string.len
sub = string.sub
else
assert(self.mode == "rb")
len = rawlen
sub = string.bsub
end
local result = ""
repeat
if len(self.buffer) == 0 then
local result, reason = readChunk()
if not result then
if reason then
return nil, reason
else -- eof
return nil
end
end
end
local left = n - len(result)
result = result .. sub(self.buffer, 1, left)
self.buffer = sub(self.buffer, left + 1)
until len(result) == n
return result
end
local function readLine(chop)
local start = 1
while true do
local l = self.buffer:find("\n", start, true)
if l then
local result = self.buffer:bsub(1, l + (chop and -1 or 0))
self.buffer = self.buffer:bsub(l + 1)
return result
else
start = #self.buffer
local result, reason = readChunk()
if not result then
if reason then
return nil, reason
else -- eof
local result = #self.buffer > 0 and self.buffer or nil
self.buffer = ""
return result
end
end
end
end
end
local function readAll()
repeat
local result, reason = readChunk()
if not result and reason then
return nil, reason
end
until not result -- eof
local result = self.buffer
self.buffer = ""
return result
end
local function read(n, format)
if type(format) == "number" then
return readBytesOrChars(format)
else
if not type(format) == "string" or format:sub(1, 1) ~= "*" then
error("bad argument #" .. n .. " (invalid option)")
end
format = format:sub(2, 2)
if format == "n" then
--[[ TODO ]]
error("not implemented")
elseif format == "l" then
return readLine(true)
elseif format == "L" then
return readLine(false)
elseif format == "a" then
return readAll()
else
error("bad argument #" .. n .. " (invalid format)")
end
end
end
local result = {}
local formats = table.pack(...)
if formats.n == 0 then
return readLine(true)
end
for i = 1, formats.n do
table.insert(result, read(i, formats[i]))
end
return table.unpack(result)
end
function file:seek(whence, offset)
if not self.handle then
return nil, "file is closed"
end
whence = tostring(whence or "cur")
assert(whence == "set" or whence == "cur" or whence == "end",
"bad argument #1 (set, cur or end expected, got " .. whence .. ")")
offset = offset or 0
checkArg(2, offset, "number")
assert(math.floor(offset) == offset, "bad argument #2 (not an integer)")
if whence == "cur" and offset ~= 0 then
offset = offset - #(self.buffer or "")
end
local result, reason = self.stream:seek(whence, offset)
if result then
if offset ~= 0 then
self.buffer = ""
elseif whence == "cur" then
result = result - #self.buffer
end
end
return result, reason
end
function file:setvbuf(mode, size)
if not self.handle then
return nil, "file is closed"
end
mode = mode or self.bufferMode
size = size or self.bufferSize
assert(mode == "no" or mode == "full" or mode == "line",
"bad argument #1 (no, full or line expected, got " .. tostring(mode) .. ")")
assert(mode == "no" or type(size) == "number",
"bad argument #2 (number expected, got " .. type(size) .. ")")
self:flush()
self.bufferMode = mode
self.bufferSize = mode == "no" and 0 or size
return self.bufferMode, self.bufferSize
end
function file:write(...)
if not self.handle then
return nil, "file is closed"
end
local args = table.pack(...)
for i = 1, args.n do
if type(args[i]) == "number" then
args[i] = tostring(args[i])
end
checkArg(i, args[i], "string")
end
for i = 1, args.n do
local arg = args[i]
local result, reason
if (self.bufferMode == "full" or self.bufferMode == "line") and
self.bufferSize - #self.buffer < #arg
then
result, reason = self:flush()
if not result then
return nil, reason
end
end
if self.bufferMode == "full" then
if #arg > self.bufferSize then
result, reason = self.stream:write(arg)
else
self.buffer = self.buffer .. arg
result = self
end
elseif self.bufferMode == "line" then
local l
repeat
local idx = self.buffer:find("\n", l or 1, true)
if idx then
l = idx
end
until not idx
if l then
result, reason = self:flush()
if not result then
return nil, reason
end
result, reason = self.stream:write(arg:bsub(1, l))
if not result then
return nil, reason
end
arg = arg:bsub(l + 1)
end
if #arg > self.bufferSize then
result, reason = self.stream:write(arg)
else
self.buffer = arg
result = self
end
else -- no
result, reason = self.stream:write(arg)
end
if not result then
return nil, reason
end
end
return self
end
-------------------------------------------------------------------------------
function file.new(fs, handle, mode, stream, nogc)
local result = {
fs = fs,
handle = handle,
mode = mode,
buffer = "",
bufferSize = math.min(8 * 1024, os.totalMemory() / 16),
bufferMode = "full"
}
result.stream = setmetatable(stream, {__index = {file = result}})
local metatable = {
__index = file,
__metatable = "file"
}
if not nogc then
metatable.__gc = function(self)
-- file.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.
event.timer(0, function()
self:close()
end)
end
end
return setmetatable(result, metatable)
end
-------------------------------------------------------------------------------
local fileStream = {}
function fileStream:close()
send(self.file.fs, "fs.close", self.file.handle)
self.file.handle = nil
end
function fileStream:read(n)
return send(self.file.fs, "fs.read", self.file.handle, n)
end
function fileStream:seek(whence, offset)
return send(self.file.fs, "fs.seek", self.file.handle, whence, offset)
end
function fileStream:write(str)
return send(self.file.fs, "fs.write", self.file.handle, str)
end
-------------------------------------------------------------------------------
function driver.fs.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
return file.new(node.fs, handle, mode, fileStream)
end
function driver.fs.type(object)
if type(object) == "table" then
if getmetatable(object) == "file" then
if object.handle then
return "file"
else
return "closed file"
end
end
end
return nil
end
-------------------------------------------------------------------------------
function loadfile(filename, env)
local file, reason = driver.fs.open(filename)
if not file then
return nil, reason
end
local source, reason = file:read("*a")
file:close()
if not source then
return nil, reason
end
return load(source, "=" .. filename, env)
end
function dofile(filename)
local program, reason = loadfile(filename)
if not program then
return error(reason)
end
return program()
end
-------------------------------------------------------------------------------
io = {}
-------------------------------------------------------------------------------
local stdinStream = {}
function stdinStream:close()
return nil, "cannot close standard file"
end
function stdinStream:read(n)
end
function stdinStream:seek(whence, offset)
return nil, "bad file descriptor"
end
function stdinStream:write(str)
return nil, "bad file descriptor"
end
local stdoutStream = {}
function stdoutStream:close()
return nil, "cannot close standard file"
end
function stdoutStream:read(n)
return nil, "bad file descriptor"
end
function stdoutStream:seek(whence, offset)
return nil, "bad file descriptor"
end
function stdoutStream:write(str)
term.write(str)
return self
end
io.stdin = file.new(nil, "stdin", "r", stdinStream, true)
io.stdout = file.new(nil, "stdout", "w", stdoutStream, true)
io.stderr = io.stdout
io.stdout:setvbuf("no")
-------------------------------------------------------------------------------
local input, output = io.stdin, io.stdout
-------------------------------------------------------------------------------
function io.close(file)
return (file or io.output()):close()
end
function io.flush()
return io.output():flush()
end
function io.input(file)
if file then
if type(file) == "string" then
local result, reason = io.open(file)
if not result then
error(reason)
end
input = result
elseif io.type(file) then
input = file
else
error("bad argument #1 (string or file expected, got " .. type(file) .. ")")
end
end
return input
end
function io.lines(filename, ...)
if filename then
local result, reason = io.open(filename)
if not result then
error(reason)
end
local args = table.pack(...)
return function()
local result = table.pack(file:read(table.unpack(args, 1, args.n)))
if not result[1] then
if result[2] then
error(result[2])
else -- eof
file:close()
return nil
end
end
return table.unpack(result, 1, result.n)
end
else
return io.input():lines()
end
end
io.open = driver.fs.open
function io.output(file)
if file then
if type(file) == "string" then
local result, reason = io.open(file, "w")
if not result then
error(reason)
end
output = result
elseif io.type(file) then
output = file
else
error("bad argument #1 (string or file expected, got " .. type(file) .. ")")
end
end
return output
end
-- TODO io.popen = function(prog, mode) end
function io.read(...)
return io.input():read(...)
end
-- TODO io.tmpfile = function() end
io.type = driver.fs.type
function io.write(...)
return io.output():write(...)
end
function print(...)
local args = table.pack(...)
for i = 1, args.n do
local arg = tostring(args[i])
if i > 1 then
arg = "\t" .. arg
end
io.stdout:write(arg)
end
io.stdout:write("\n")
end
-------------------------------------------------------------------------------
os.remove = driver.fs.remove
os.rename = driver.fs.rename
-- TODO os.tmpname = function() end

View File

@ -1,446 +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 + i
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.fs = {}
function driver.fs.mount(fs, path)
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
end
function driver.fs.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}
repeat
local node = table.remove(queue)
if node.fs == fsOrPath then
node.fs = nil
removeEmptyNodes(node)
return true
end
for _, child in ipairs(node.children) do
table.insert(queue, child)
end
until #queue == 0
end
end
-------------------------------------------------------------------------------
function driver.fs.exists(path)
local node, rest = findNode(path)
if not rest then -- virtual directory
return true
end
if node.fs then
return sendToNode(node.fs, "fs.exists", rest)
end
end
function driver.fs.size(path)
local node, rest = findNode(path)
if node.fs and rest then
return sendToNode(node.fs, "fs.size", rest)
end
return 0 -- no such file or directory or virtual directory
end
function driver.fs.listdir(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 = {sendToNode(node.fs, "fs.list", rest or "")}
if not result[1] 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)
return table.unpack(result)
end
-------------------------------------------------------------------------------
function driver.fs.remove(path)
local node, rest = findNode(path)
if node.fs and rest then
return sendToNode(node.fs, "fs.remove", rest)
end
end
function driver.fs.rename(oldPath, newPath)
--[[ TODO moving between file systems will require actual data copying...
local node, rest = findNode(path)
local newNode, newRest = findNode(newPath)
if node.fs and rest and newNode and newRest then
return sendToNode(node.fs, "fs.rename", rest)
end
]]
end
-------------------------------------------------------------------------------
local file = {}
function file.close(f)
if f.handle then
f:flush()
sendToNode(f.fs, "fs.close", f.handle)
f.handle = nil
end
end
function file.flush(f)
if not f.handle then
return nil, "file is closed"
end
if #(f.buffer or "") > 0 then
local result, reason = sendToNode(f.fs, "fs.write", f.handle, f.buffer)
if result then
f.buffer = nil
else
if reason then
return nil, reason
else
return nil, "invalid file"
end
end
end
return f
end
function file.read(f, ...)
if not f.handle then
return nil, "file is closed"
end
local function readChunk()
local read, reason = sendToNode(f.fs, "fs.read", f.handle, f.bsize)
if read then
f.buffer = (f.buffer or "") .. read
return true
else
return nil, reason
end
end
local function readBytes(n)
while #(f.buffer or "") < n do
local result, reason = readChunk()
if not result then
if reason then
return nil, reason
end
break
end
end
local result
if f.buffer then
if #f.buffer > format then
result = f.buffer:bsub(1, format)
f.buffer = f.buffer:bsub(format + 1)
else
result = f.buffer
f.buffer = nil
end
end
return result
end
local function readLine(chop)
while true do
local l = (f.buffer or ""):find("\n", 1, true)
if l then
local rl = l + (chop and -1 or 0)
local line = f.buffer:bsub(1, rl)
f.buffer = f.buffer:bsub(l + 1)
return line
else
local result, reason = readChunk()
if not result then
if reason then
return nil, reason
else
local line = f.buffer
f.buffer = nil
return line
end
end
end
end
end
local function readAll()
repeat
local result, reason = readChunk()
if not result and reason then
return nil, reason
end
until not result
local result = f.buffer or ""
f.buffer = nil
return result
end
local function read(n, format)
if type(format) == "number" then
return readBytes(format)
else
if not type(format) == "string" or format:sub(1, 1) ~= "*" then
error("bad argument #" .. n .. " (invalid option)")
end
format = format:sub(2, 2)
if format == "n" then
error("not implemented")
elseif format == "l" then
return readLine(true)
elseif format == "L" then
return readLine(false)
elseif format == "a" then
return readAll()
else
error("bad argument #" .. n .. " (invalid format)")
end
end
end
local results = {}
local formats = {...}
if #formats == 0 then
return readLine(true)
end
for n, format in ipairs(formats) do
table.insert(results, read(n, format))
end
return table.unpack(results)
end
function file.seek(f, whence, offset)
if not f.handle then
return nil, "file is closed"
end
whence = whence or "cur"
assert(whence == "set" or whence == "cur" or whence == "end",
"bad argument #1 (set, cur or end expected, got " .. tostring(whence) .. ")")
offset = offset or 0
checkArg(2, offset, "number")
assert(math.floor(offset) == offset, "bad argument #2 (not an integer)")
if whence == "cur" and offset ~= 0 then
offset = offset - #(f.buffer or "")
end
local result, reason = sendToNode(f.fs, "fs.seek", f.handle, whence, offset)
if result then
if offset ~= 0 then
f.buffer = nil
elseif whence == "cur" then
result = result - #(f.buffer or "")
end
end
return result, reason
end
function file.setvbuf(f, mode, size)
if not f.handle then
return nil, "file is closed"
end
assert(mode == "no" or mode == "full" or mode == "line",
"bad argument #1 (no, full or line expected, got " .. tostring(mode) .. ")")
assert(mode == "no" or type(size) == "number",
"bad argument #2 (number expected, got " .. type(size) .. ")")
f:flush()
f.bmode = mode
f.bsize = size
end
function file.write(f, ...)
if not f.handle then
return nil, "file is closed"
end
local args = {...}
for n, arg in ipairs(args) do
if type(arg) == "number" then
args[n] = tostring(arg)
end
checkArg(n, arg, "string")
end
for _, arg in ipairs(args) do
local result, reason
if f.bmode == "full" or #(f.buffer or "") + #arg > f.bsize then
if #(f.buffer or "") + #arg > f.bsize then
result, reason = f:flush()
if result then
if #arg > f.bsize then
result, reason = sendToNode(f.fs, "fs.write", f.handle, arg)
else
f.buffer = arg
end
end
else
f.buffer = (f.buffer or "") .. arg
result = f
end
elseif f.bmode == "line" then
repeat
local l = (f.buffer or ""):find("\n", 1, true)
if l then
result, reason = f:flush()
if result then
result, reason = sendToNode(f.fs, "fs.write", f.handle, arg:bsub(1, l))
if result then
f.buffer = arg:bsub(l + 1)
end
end
else
f.buffer = f.buffer .. arg
end
until not l or not result
else
if #(f.buffer or "") > 0 then
result, reason = f:flush()
if not result then
return nil, reason
end
end
result, reason = sendToNode(f.fs, "fs.write", f.handle, arg)
end
if not result then
return nil, reason
end
end
return f
end
-------------------------------------------------------------------------------
function driver.fs.open(path, mode)
mode = 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 " .. tostring(mode) .. ")")
local node, rest = findNode(path)
if not node.fs or not rest then -- files can only be in file systems
return nil, "file not found"
end
local handle, reason = sendToNode(node.fs, "fs.open", rest or "", mode)
if not handle then
return nil, reason
end
return setmetatable({
fs = node.fs,
handle = handle,
bsize = math.min(8 * 1024, os.totalMemory() / 16),
bmode = "full"
}, {
__index = file,
__gc = function(f)
-- file.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.
event.timer(0, function()
file.close(f)
end)
end
})
end
function driver.fs.type(f)
local info = getFileInfo(f, true)
if not info then
return nil
elseif not info.handle then
return "closed file"
else
return "file"
end
end
-------------------------------------------------------------------------------
function loadfile(file, env)
local f, reason = driver.fs.open(file)
if not f then
return nil, reason
end
local source, reason = f:read("*a")
f:close()
if not source then
return nil, reason
end
return load(source, "=" .. file, env)
end
function dofile(file)
local f, reason = loadfile(file)
if not f then
return nil, reason
end
return f()
end

View File

@ -1,48 +1,52 @@
driver.gpu = {} driver.gpu = {}
function driver.gpu.setResolution(gpu, screen, w, h)
sendToNode(gpu, "gpu.resolution=", screen, w, h)
end
function driver.gpu.getResolution(gpu, screen)
return sendToNode(gpu, "gpu.resolution", screen)
end
function driver.gpu.getResolutions(gpu, screen)
return sendToNode(gpu, "gpu.resolutions", screen)
end
function driver.gpu.set(gpu, screen, col, row, value)
sendToNode(gpu, "gpu.set", screen, col, row, value)
end
function driver.gpu.fill(gpu, screen, col, row, w, h, value)
sendToNode(gpu, "gpu.fill", screen, col, row, w, h, value:sub(1, 1))
end
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(gpu, screen) function driver.gpu.bind(gpu, screen)
return { checkArg(1, gpu, "string")
setResolution = function(w, h) checkArg(2, screen, "string")
driver.gpu.setResolution(gpu, screen, w, h) return send(gpu, "gpu.bind", screen)
end,
getResolution = function()
return driver.gpu.getResolution(gpu, screen)
end,
getResolutions = function()
return driver.gpu.getResolutions(gpu, screen)
end,
set = function(col, row, value)
driver.gpu.set(gpu, screen, col, row, value)
end,
fill = function(col, ro, w, h, value)
driver.gpu.fill(gpu, screen, 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)
end 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.resolutions(gpu)
checkArg(1, gpu, "string")
return send(gpu, "gpu.resolutions")
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:sub(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 end

View File

@ -14,14 +14,14 @@ driver.redstone.sides.down = driver.redstone.sides.bottom
local owner = os.address() local owner = os.address()
function driver.redstone.analogInput(card, side) function driver.redstone.analogInput(card, side)
sendToNode(card, owner, "redstone.input", side) send(card, owner, "redstone.input", side)
end end
function driver.redstone.analogOutput(card, side, value) function driver.redstone.analogOutput(card, side, value)
if value then if value then
sendToNode(card, owner, "redstone.output=", side, tonumber(value)) send(card, owner, "redstone.output=", side, tonumber(value))
else else
return sendToNode(card, owner, "redstone.output", side) return send(card, owner, "redstone.output", side)
end end
end end

View File

@ -91,6 +91,7 @@ local sandbox = {
date = os.date, date = os.date,
difftime = os.difftime, difftime = os.difftime,
time = os.time, time = os.time,
uptime = os.uptime,
freeMemory = os.freeMemory, freeMemory = os.freeMemory,
totalMemory = os.totalMemory, totalMemory = os.totalMemory,
address = os.address, address = os.address,
@ -98,6 +99,8 @@ local sandbox = {
}, },
string = { string = {
breverse = string.breverse,
bsub = string.bsub,
byte = string.byte, byte = string.byte,
char = string.char, char = string.char,
dump = string.dump, dump = string.dump,
@ -125,33 +128,27 @@ local sandbox = {
} }
sandbox._G = sandbox sandbox._G = sandbox
-- Note: 'write' will be replaced by init script/term API.
function sandbox.write(...) end
function sandbox.print(...)
sandbox.write(...)
sandbox.write("\n")
end
function sandbox.load(code, source, env) function sandbox.load(code, source, env)
return load(code, source, "t", env or sandbox) return load(code, source, "t", env or sandbox)
end end
function sandbox.checkArg(n, have, ...) function sandbox.checkArg(n, have, ...)
have = type(have) have = type(have)
for _, want in pairs({...}) do local want = table.pack(...)
if have == want then for i = 1, want.n do
if have == want[i] then
return return
end end
end end
local msg = "bad argument #" .. n .. " (" .. table.concat({...}, " or ") .. " expected, got " .. have .. ")" local msg = "bad argument #" .. n .. " (" .. table.concat({...}, " or ") .. " expected, got " .. have .. ")"
error(debug.traceback(msg, 3), 2) error(debug.traceback(msg, 2), 2)
end end
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
--[[ Install wrappers for coroutine management that reserves the first value --[[ Install wrappers for coroutine management that reserves the first value
returned by yields for internal stuff. Used for sleeping and message returned by yields for internal stuff. Used for sleeping and message
calls (sendToNode and its ilk) that happen synchronized (Server thread). calls (sendToAddress) that happen synchronized (Server thread).
--]] --]]
local deadline = 0 local deadline = 0
@ -166,7 +163,7 @@ local function main(args)
sandbox.driver.fs.mount(os.romAddress(), "/boot") sandbox.driver.fs.mount(os.romAddress(), "/boot")
local result, reason = sandbox.loadfile("/boot/init.lua") local result, reason = sandbox.loadfile("/boot/init.lua")
if not result then if not result then
error(reason) error(reason, 0)
end end
return coroutine.create(result) return coroutine.create(result)
end end
@ -176,32 +173,32 @@ local function main(args)
if not debug.gethook(co) then if not debug.gethook(co) then
debug.sethook(co, checkDeadline, "", 10000) debug.sethook(co, checkDeadline, "", 10000)
end end
local result = {coroutine.resume(co, table.unpack(args))} local result = table.pack(coroutine.resume(co, table.unpack(args, 1, args.n)))
if result[1] then if result[1] then
args = {coroutine.yield(result[2])} -- system yielded value args = table.pack(coroutine.yield(result[2])) -- system yielded value
else else
error(result[2]) error(result[2], 0)
end end
end end
end end
function sandbox.coroutine.resume(co, ...) function sandbox.coroutine.resume(co, ...)
local args = {...} local args = table.pack(...)
while true do while true do
if not debug.gethook(co) then -- don't reset counter if not debug.gethook(co) then -- don't reset counter
debug.sethook(co, checkDeadline, "", 10000) debug.sethook(co, checkDeadline, "", 10000)
end end
local result = {coroutine.resume(co, table.unpack(args))} local result = table.pack(coroutine.resume(co, table.unpack(args, 1, args.n)))
checkDeadline() checkDeadline()
if result[1] then if result[1] then
local isSystemYield = coroutine.status(co) ~= "dead" and result[2] ~= nil local isSystemYield = coroutine.status(co) ~= "dead" and result[2] ~= nil
if isSystemYield then if isSystemYield then
args = coroutine.yield(result[2]) args = table.pack(coroutine.yield(result[2]))
else else
return true, table.unpack(result, 3) return true, table.unpack(result, 3, result.n)
end end
else -- error: result = (bool, string) else -- error: result = (bool, string)
return table.unpack(result) return table.unpack(result, 1, result.n)
end end
end end
end end
@ -210,6 +207,18 @@ function sandbox.coroutine.yield(...)
return coroutine.yield(nil, ...) return coroutine.yield(nil, ...)
end end
function sandbox.pcall(...)
local result = table.pack(pcall(...))
checkDeadline()
return table.unpack(result, 1, result.n)
end
function sandbox.xpcall(...)
local result = table.pack(xpcall(...))
checkDeadline()
return table.unpack(result, 1, result.n)
end
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
function sandbox.os.shutdown() function sandbox.os.shutdown()
@ -221,13 +230,13 @@ function sandbox.os.reboot()
end end
function sandbox.os.signal(name, timeout) function sandbox.os.signal(name, timeout)
local waitUntil = os.clock() + (type(timeout) == "number" and timeout or math.huge) local waitUntil = os.uptime() + (type(timeout) == "number" and timeout or math.huge)
while os.clock() < waitUntil do repeat
local signal = {coroutine.yield(waitUntil - os.clock())} local signal = table.pack(coroutine.yield(waitUntil - os.uptime()))
if signal and (name == signal[1] or name == nil) then if signal.n > 0 and (name == signal[1] or name == nil) then
return table.unpack(signal) return table.unpack(signal, 1, signal.n)
end
end end
until os.uptime() >= waitUntil
end end
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
@ -239,9 +248,8 @@ function sandbox.driver.componentType(id)
end end
do do
local env = setmetatable({ local env = setmetatable({ send = sendToAddress },
sendToNode = sendToNode, { __index = sandbox, __newindex = sandbox })
}, { __index = sandbox, __newindex = sandbox })
for name, code in pairs(drivers()) do for name, code in pairs(drivers()) do
local driver, reason = load(code, "=" .. name, "t", env) local driver, reason = load(code, "=" .. name, "t", env)
if not driver then if not driver then
@ -260,4 +268,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. -- Also, yield once to allow initializing up to here to get a memory baseline.
return pcall(main, {coroutine.yield()}) return pcall(main, table.pack(coroutine.yield()))

View File

@ -55,7 +55,7 @@ function event.fire(name, ...)
end end
local elapsed = {} local elapsed = {}
for id, info in pairs(timers) do for id, info in pairs(timers) do
if info.after < os.clock() then if info.after < os.uptime() then
table.insert(elapsed, info.callback) table.insert(elapsed, info.callback)
timers[id] = nil timers[id] = nil
end end
@ -70,7 +70,7 @@ end
function event.timer(timeout, callback) function event.timer(timeout, callback)
local id = #timers + 1 local id = #timers + 1
timers[id] = {after = os.clock() + timeout, callback = callback} timers[id] = {after = os.uptime() + timeout, callback = callback}
return id return id
end end
@ -96,8 +96,9 @@ function event.error(message)
end end
function coroutine.sleep(seconds) function coroutine.sleep(seconds)
seconds = seconds or math.huge
checkArg(1, seconds, "number") checkArg(1, seconds, "number")
local target = os.clock() + seconds local target = os.uptime() + seconds
repeat repeat
local closest = target local closest = target
for _, info in pairs(timers) do for _, info in pairs(timers) do
@ -105,6 +106,6 @@ function coroutine.sleep(seconds)
closest = info.after closest = info.after
end end
end end
event.fire(os.signal(nil, closest - os.clock())) event.fire(os.signal(nil, closest - os.uptime()))
until os.clock() >= target until os.uptime() >= target
end end

View File

@ -3,17 +3,144 @@ os.remove = driver.fs.remove
os.rename = driver.fs.rename os.rename = driver.fs.rename
-- TODO os.tmpname = function() end -- TODO os.tmpname = function() end
local function unavailable()
return nil, "bad file descriptor"
end
io = {} io = {}
-- TODO io.flush = function() end
-- TODO io.lines = function(filename) end io.stdin = {handle="stdin"}
io.open = driver.fs.open
function io.stdin:close()
return nil, "cannot close standard file"
end
io.stdin.flush = unavailable
function io.stdin:lines(...)
return function()
local result = {self:read(...)}
if not result[1] and result[2] then
error(result[2])
end
return table.unpack(result)
end
end
function io.stdin:read(...)
end
io.stdin.seek = unavailable
io.stdin.setvbuf = unavailable
io.stdin.write = unavailable
io.stdout = {handle="stdout"}
io.stdout.close = io.stdin.close
function io.stdout:flush()
return self -- no-op
end
io.stdout.lines = unavailable
io.stdout.read = unavailable
io.stdout.seek = unavailable
io.stdout.setvbuf = unavailable
function io.stdout:write()
return nil, "bad file descriptor"
end
io.stderr = io.stdout
io.lines = function(filename)
local f = io.open(filename)
return function()
if f then
local result, reason = f:read("*l")
if result then
return result
else
f:close()
return nil, reason
end
end
end
-- TODO io.popen = function(prog, mode) end -- TODO io.popen = function(prog, mode) end
io.read = driver.fs.read
-- TODO io.tmpfile = function() end -- TODO io.tmpfile = function() end
io.type = driver.fs.type io.type = driver.fs.type
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
local input, output = io.stdin, io.stdout
function io.close(file)
(file or output):close()
end
function io.flush()
output:flush()
end
function io.input(file)
if file then
if type(file) == "string" then
local result, reason = io.open(file)
if not result then
error(reason)
end
input = result
elseif io.type(file) then
input = file
else
error("bad argument #1 (string or file expected, got " .. type(file) .. ")")
end
end
return input
end
function io.open(...)
local result, reason = driver.fs.open(...)
if result then
current = result
end
return result, reason
end
function io.output(file)
if file then
if type(file) == "string" then
local result, reason = io.open(file, "w")
if not result then
error(reason)
end
output = result
elseif io.type(file) then
output = file
else
error("bad argument #1 (string or file expected, got " .. type(file) .. ")")
end
end
return output
end
function io.read(...)
if current then
return current:read(...)
end
end
function io.write(...)
if current then
return current:write(...)
end
end
-------------------------------------------------------------------------------
event.listen("component_added", function(_, address) event.listen("component_added", function(_, address)
if component.type(address) == "filesystem" and address ~= os.romAddress() then if component.type(address) == "filesystem" and address ~= os.romAddress() then
local name = address:sub(1, 3) local name = address:sub(1, 3)

View File

@ -1,19 +1,20 @@
local gpu = nil local gpuAddress, screenAddress, keyboardAddress = nil, nil, nil
local gpuAddress, screenAddress, keyboardAddress = false, false, false local width, height = 0, 0
local screenWidth, screenHeight = 0, 0
local cursorX, cursorY = 1, 1 local cursorX, cursorY = 1, 1
local cursorBlink = nil local cursorBlink = nil
local function bindIfPossible() local function rebind(gpu, screen)
if gpuAddress and screenAddress then if gpu == gpuAddress and screen == screenAddress then
if not gpu then return
gpu = driver.gpu.bind(gpuAddress, screenAddress)
screenWidth, screenHeight = gpu.getResolution()
event.fire("term_available")
end end
elseif gpu then local oldGpu, oldScreen = gpuAddress, screenAddress
gpu = nil gpuAddress, screenAddress = gpu, screen
screenWidth, screenHeight = 0, 0 if gpu and screen then
driver.gpu.bind(gpuAddress, screenAddress)
width, height = driver.gpu.resolution(gpuAddress)
event.fire("term_available")
elseif gpuAddress and screenAddress then
width, height = 0, 0
event.fire("term_unavailable") event.fire("term_unavailable")
end end
end end
@ -22,35 +23,26 @@ end
term = {} term = {}
function term.gpu(address) function term.available()
if address ~= nil and ({boolean=true, string=true})[type(address)] then return gpuAddress and screenAddress
gpuAddress = address
bindIfPossible()
end
return gpuAddress, gpu
end end
function term.screen(address) function term.clear()
if address ~= nil and ({boolean=true, string=true})[type(address)] then if term.available() then
screenAddress = address driver.gpu.fill(term.gpu(), 1, 1, width, height, " ")
bindIfPossible()
end end
return screenAddress cursorX, cursorY = 1, 1
end end
function term.keyboard(address) function term.clearLine()
if address ~= nil and ({boolean=true, string=true})[type(address)] then if term.available() then
keyboardAddress = address driver.gpu.fill(term.gpu(), 1, cursorY, width, 1, " ")
end end
return keyboardAddress cursorX = 1
end
function term.screenSize()
return screenWidth, screenHeight
end end
function term.cursor(col, row) function term.cursor(col, row)
if row and col then if col and row then
cursorX = math.max(col, 1) cursorX = math.max(col, 1)
cursorY = math.max(row, 1) cursorY = math.max(row, 1)
end end
@ -61,12 +53,10 @@ function term.cursorBlink(enabled)
if type(enabled) == "boolean" and enabled ~= (cursorBlink ~= nil) then if type(enabled) == "boolean" and enabled ~= (cursorBlink ~= nil) then
local function toggleBlink() local function toggleBlink()
cursorBlink.state = not cursorBlink.state cursorBlink.state = not cursorBlink.state
if gpu then if term.available() then
if cursorBlink.state then -- 0x2588 is a solid block.
gpu.set(cursorX, cursorY, string.char(0x2588)) -- Solid block. local char = cursorBlink.state and string.char(0x2588) or " "
else driver.gpu.set(term.gpu(), cursorX, cursorY, char)
gpu.set(cursorX, cursorY, " ")
end
end end
end end
if enabled then if enabled then
@ -83,20 +73,52 @@ function term.cursorBlink(enabled)
return cursorBlink ~= nil return cursorBlink ~= nil
end end
function term.gpu(...)
local args = table.pack(...)
if args.n > 0 then
checkArg(1, args[1], "string", "nil")
rebind(args[1], term.screen())
end
return gpuAddress
end
function term.keyboard(...)
local args = table.pack(...)
if args.n > 0 then
checkArg(1, args[1], "string", "nil")
keyboardAddress = args[1]
end
return keyboardAddress
end
function term.screen(...)
local args = table.pack(...)
if args.n > 0 then
checkArg(1, args[1], "string", "nil")
rebind(term.gpu(), args[1])
end
return screenAddress
end
function term.size()
return width, height
end
function term.write(value, wrap) function term.write(value, wrap)
value = tostring(value) value = tostring(value)
local w, h = screenWidth, screenHeight local w, h = width, height
if value:len() == 0 or not gpu or w < 1 or h < 1 then if value:len() == 0 or not term.available() or w < 1 or h < 1 then
return return
end end
value = value:gsub("\t", " ")
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) driver.gpu.copy(term.gpu(), 1, 1, w, h, 0, -1)
gpu.fill(1, h, w, 1, " ") driver.gpu.fill(term.gpu(), 1, h, w, 1, " ")
cursorY = h cursorY = h
end end
end end
@ -104,12 +126,12 @@ function term.write(value, wrap)
while wrap and line:len() > w - cursorX + 1 do while wrap and line:len() > w - cursorX + 1 do
local partial = line:sub(1, w - cursorX + 1) local partial = line:sub(1, w - cursorX + 1)
line = line:sub(partial:len() + 1) line = line:sub(partial:len() + 1)
gpu.set(cursorX, cursorY, partial) driver.gpu.set(term.gpu(), cursorX, cursorY, partial)
cursorX = cursorX + partial:len() cursorX = cursorX + partial:len()
checkCursor() checkCursor()
end end
if line:len() > 0 then if line:len() > 0 then
gpu.set(cursorX, cursorY, line) driver.gpu.set(term.gpu(), cursorX, cursorY, line)
cursorX = cursorX + line:len() cursorX = cursorX + line:len()
end end
if nl:len() == 1 then if nl:len() == 1 then
@ -120,62 +142,38 @@ function term.write(value, wrap)
end end
end end
function term.clear()
if not gpu then return end
gpu.fill(1, 1, screenWidth, screenHeight, " ")
cursorX, cursorY = 1, 1
end
function term.clearLine()
if not gpu then return end
gpu.fill(1, cursorY, screenWidth, 1, " ")
cursorX = 1
end
write = function(...)
local args = {...}
local first = true
for i = 1, #args do
if not first then
term.write(", ")
end
first = false
term.write(args[i], true)
end
end
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
event.listen("component_added", function(_, address) event.listen("component_added", function(_, address)
local type = component.type(address) local type = component.type(address)
if type == "gpu" and not gpuAddress then if type == "gpu" and not term.gpu() then
term.gpu(address) term.gpu(address)
elseif type == "screen" and not screenAddress then elseif type == "screen" and not term.screen() then
term.screen(address) term.screen(address)
elseif type == "keyboard" and not keyboardAddress then elseif type == "keyboard" and not term.keyboard() then
term.keyboard(address) term.keyboard(address)
end end
end) end)
event.listen("component_removed", function(_, address) event.listen("component_removed", function(_, address)
if gpuAddress == address then if term.gpu() == address then
term.gpu(false) term.gpu(nil)
for address in component.list() do for address in component.list() do
if component.type(address) == "gpu" then if component.type(address) == "gpu" then
term.gpu(address) term.gpu(address)
return return
end end
end end
elseif screenAddress == address then elseif term.screen() == address then
term.screen(false) term.screen(nil)
for address in component.list() do for address in component.list() do
if component.type(address) == "screen" then if component.type(address) == "screen" then
term.screen(address) term.screen(address)
return return
end end
end end
elseif keyboardAddress == address then elseif term.keyboard() == address then
term.keyboard(false) term.keyboard(nil)
for address in component.list() do for address in component.list() do
if component.type(address) == "keyboard" then if component.type(address) == "keyboard" then
term.keyboard(address) term.keyboard(address)
@ -186,8 +184,8 @@ event.listen("component_removed", function(_, address)
end) end)
event.listen("screen_resized", function(_, address, w, h) event.listen("screen_resized", function(_, address, w, h)
if address == screenAddress then if term.screen() == address then
screenWidth = w width = w
screenHeight = h height = h
end end
end) end)

View File

@ -0,0 +1,21 @@
event.listen("component_added", function(_, address)
if component.type(address) == "filesystem" and address ~= os.romAddress() then
local name = address:sub(1, 3)
repeat
name = address:sub(1, name:len() + 1)
until not driver.fs.exists("/mnt/" .. name)
driver.fs.mount(address, "/mnt/" .. name)
local autorun = "/mnt/" .. name .. "/autorun"
if driver.fs.exists(autorun .. ".lua") then
dofile(autorun .. ".lua")
elseif driver.fs.exists(autorun) then
dofile(autorun)
end
end
end)
event.listen("component_removed", function(_, address)
if component.type(address) == "filesystem" then
driver.fs.umount(address)
end
end)

View File

@ -6,15 +6,15 @@ local isRunning = false
local function onKeyDown(_, address, char, code) local function onKeyDown(_, address, char, code)
if isRunning then return end -- ignore events while running a command if isRunning then return end -- ignore events while running a command
if address ~= term.keyboard() then return end if address ~= term.keyboard() then return end
local _, gpu = term.gpu() if not term.available() then return end
if not gpu then return end
local x, y = term.cursor() local x, y = term.cursor()
local w, h = term.size()
local keys = driver.keyboard.keys local keys = driver.keyboard.keys
if code == keys.back then if code == keys.back then
if command:len() == 0 then return end if command:len() == 0 then return end
command = command:sub(1, -2) command = command:sub(1, -2)
term.cursor(command:len() + 3, y) -- from leading "> " term.cursor(command:len() + 3, y) -- from leading "> "
gpu.set(x - 1, y, " ") -- overwrite cursor blink driver.gpu.set(term.gpu(), x - 1, y, " ") -- overwrite cursor blink
elseif code == keys.enter then elseif code == keys.enter then
if command:len() == 0 then return end if command:len() == 0 then return end
term.cursorBlink(false) term.cursorBlink(false)
@ -25,21 +25,21 @@ local function onKeyDown(_, address, char, code)
end end
if code then if code then
isRunning = true isRunning = true
local result = {pcall(code)} local result = table.pack(pcall(code))
isRunning = false isRunning = false
if not result[1] or #result > 1 then if not result[1] or result.n > 1 then
print(table.unpack(result, 2)) print(table.unpack(result, 2, result.n))
end end
else else
print(result) print(result)
end end
lastCommand = command lastCommand = command
command = "" command = ""
write("> ") term.write("> ")
term.cursorBlink(true) term.cursorBlink(true)
elseif code == keys.up then elseif code == keys.up then
command = lastCommand command = lastCommand
gpu.fill(3, y, screenWidth, 1, " ") driver.gpu.fill(term.gpu(), 3, y, w, 1, " ")
term.cursor(3, y) term.cursor(3, y)
term.write(command) term.write(command)
term.cursor(command:len() + 3, y) term.cursor(command:len() + 3, y)
@ -64,7 +64,7 @@ event.listen("term_available", function()
term.clear() term.clear()
command = "" command = ""
print("OpenOS v1.0 (" .. math.floor(os.totalMemory() / 1024) .. "k RAM)") print("OpenOS v1.0 (" .. math.floor(os.totalMemory() / 1024) .. "k RAM)")
write("> ") term.write("> ")
event.listen("key_down", onKeyDown) event.listen("key_down", onKeyDown)
event.listen("clipboard", onClipboard) event.listen("clipboard", onClipboard)
end) end)

View File

@ -1,12 +1,12 @@
dofile("/boot/api/event.lua") dofile("/boot/api/event.lua")
dofile("/boot/api/component.lua") dofile("/boot/api/component.lua")
dofile("/boot/api/filesystem.lua")
dofile("/boot/api/term.lua") dofile("/boot/api/term.lua")
dofile("/boot/sh.lua") dofile("/boot/bin/automount.lua")
dofile("/boot/bin/sh.lua")
driver.fs.umount("/boot") driver.fs.umount("/boot")
event.fire(...) event.fire(...)
while true do while true do
event.fire(os.signal()) coroutine.sleep()
end end

View File

@ -9,7 +9,7 @@ object Blocks {
var screen: Screen = null var screen: Screen = null
var keyboard: Keyboard = null var keyboard: Keyboard = null
var powersupply: PowerSupply = null var powersupply: PowerSupply = null
var powerdistributer: PowerDistributor = null var powerdistributor: PowerDistributor = null
def init() { def init() {
// IMPORTANT: the multi block must come first, since the sub blocks will // IMPORTANT: the multi block must come first, since the sub blocks will
// try to register with it. Also, the order the sub blocks are created in // try to register with it. Also, the order the sub blocks are created in
@ -21,6 +21,6 @@ object Blocks {
screen = new Screen(blockSimple) screen = new Screen(blockSimple)
keyboard = new Keyboard(blockSpecial) keyboard = new Keyboard(blockSpecial)
powersupply = new PowerSupply(blockSimple) powersupply = new PowerSupply(blockSimple)
powerdistributer = new PowerDistributor(blockSimple) powerdistributor = new PowerDistributor(blockSimple)
} }
} }

View File

@ -16,8 +16,33 @@ import li.cil.oc.api.network.Node
* <p/> * <p/>
* Alternatively to using the factory methods for file systems in `Filesystem` * Alternatively to using the factory methods for file systems in `Filesystem`
* you are free to implement this interface yourself. * you are free to implement this interface yourself.
* <p/>
* Note that all paths passed here are assumed to be absolute in the underlying
* file system implementation, meaning they do not contain any "." or "..", and
* are relative to the root of the file system.
*/ */
trait FileSystem extends Persistable { trait FileSystem extends Persistable {
/**
* The total storage capacity of the file system, in bytes.
* <p/>
* For read-only systems this should return zero, for writable file systems
* that do not enforce a storage limit this should be a negative value.
*
* @return the total storage space of this file system.
*/
def spaceTotal = 0L
/**
* The used storage capacity of the file system, in bytes.
* <p/>
* For read-only systems this should return zero.
*
* @return the used storage space of this file system.
*/
def spaceUsed = 0L
// ----------------------------------------------------------------------- //
/** /**
* Tests if a file or directory exists at the specified path. * Tests if a file or directory exists at the specified path.
* <p/> * <p/>
@ -75,6 +100,20 @@ trait FileSystem extends Persistable {
// ----------------------------------------------------------------------- // // ----------------------------------------------------------------------- //
/**
* Create the specified directory, and if necessary any parent directories
* that do not yet exist.
* <p/>
* This is only available for writable file systems. For read-only systems
* it should just always return false.
*
* @param path the path to the directory to create.
* @return true if the directory was created; false otherwise.
*/
def makeDirectories(path: String): Boolean =
!exists(path) && (makeDirectory(path) ||
(makeDirectories(path.split("/").dropRight(1).mkString("/")) && makeDirectory(path)))
/** /**
* Deletes a file or folder. * Deletes a file or folder.
* <p/> * <p/>
@ -153,6 +192,23 @@ trait FileSystem extends Persistable {
*/ */
def close() def close()
// ----------------------------------------------------------------------- //
/**
* Actual directory creation implementation.
* <p/>
* This is called from the possibly recursive `makeDirectories` function, and
* guarantees that the parent directory of the directory to be created
* already exists. So this should always only create a single directory.
* <p/>
* This is only available for writable file systems. For read-only systems
* it should just always return false.
*
* @param path the path to the directory to create.
* @return true if the directory was created; false otherwise.
*/
protected def makeDirectory(path: String) = false
/** /**
* Actual deletion implementation. * Actual deletion implementation.
* <p/> * <p/>

View File

@ -70,16 +70,22 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl
// ----------------------------------------------------------------------- // // ----------------------------------------------------------------------- //
private var timeStarted = 0L // Game-world time for os.clock(). private var timeStarted = 0L // Game-world time [ms] for os.uptime().
private var worldTime = 0L // Game-world time for os.time(). private var worldTime = 0L // Game-world time for os.time().
private var lastUpdate = 0L // Real-world time for pause detection. private var lastUpdate = 0L // Real-world time [ms] for pause detection.
private var sleepUntil = Long.MaxValue // Real-world time. private var cpuTime = 0L // Pseudo-real-world time [ns] for os.clock().
private var cpuStart = 0L // Pseudo-real-world time [ns] for os.clock().
private var sleepUntil = Double.PositiveInfinity // Real-world time [ms].
private var wasRunning = false // To signal stops synchronously. private var wasRunning = false // To signal stops synchronously.
private var message: Option[String] = None // For error messages.
// ----------------------------------------------------------------------- // // ----------------------------------------------------------------------- //
def recomputeMemory() = if (lua != null) def recomputeMemory() = if (lua != null)
@ -154,10 +160,25 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl
worldTime = owner.world.getWorldInfo.getWorldTotalTime worldTime = owner.world.getWorldInfo.getWorldTotalTime
// Signal stops to the network. This is used to close file handles, for example. // Signal stops to the network. This is used to close file handles, for example.
if (wasRunning && !isRunning) if (wasRunning && !isRunning) {
owner.network.foreach(_.sendToVisible(owner, "computer.stopped")) owner.network.foreach(_.sendToVisible(owner, "computer.stopped"))
// Clear any screens we use while we're at it.
owner.network.foreach(_.sendToNeighbors(owner, "gpu.fill",
1.0, 1.0, Double.PositiveInfinity, Double.PositiveInfinity, " ".getBytes("UTF-8")))
}
wasRunning = isRunning wasRunning = isRunning
// If there was an error message (i.e. the computer crashed) display it on
// any screens we used (stored in GPUs).
if (message.isDefined) {
owner.network.foreach(network => {
for ((line, row) <- message.get.lines.zipWithIndex) {
network.sendToNeighbors(owner, "gpu.set", 1.0, 1.0 + row, line.getBytes("UTF-8"))
}
})
message = None
}
// Check if we should switch states. // Check if we should switch states.
stateMonitor.synchronized(state match { stateMonitor.synchronized(state match {
// Computer is rebooting. // Computer is rebooting.
@ -204,16 +225,14 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl
// This can happen if we run out of memory while converting a Java // 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 // exception to a string (which we have to do to avoid keeping
// userdata on the stack, which cannot be persisted). // 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 message = Some("not enough memory")
//owner.network.foreach(_.sendToVisible(owner, "computer.crashed", "not enough memory"))
close() close()
case e: java.lang.Error if e.getMessage == "not enough memory" => 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 message = Some("not enough memory")
// TODO get this to the world as a computer.crashed message. problem: synchronizing it.
//owner.network.foreach(_.sendToVisible(owner, "computer.crashed", "not enough memory"))
close() close()
case e: Throwable => { case e: Throwable => {
OpenComputers.log.log(Level.WARNING, "Faulty Lua implementation for synchronized calls.", e) OpenComputers.log.log(Level.WARNING, "Faulty Lua implementation for synchronized calls.", e)
message = Some("protocol error")
close() close()
} }
} }
@ -273,6 +292,9 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl
rom.foreach(_.load(nbt.getCompoundTag("rom"))) rom.foreach(_.load(nbt.getCompoundTag("rom")))
kernelMemory = nbt.getInteger("kernelMemory") kernelMemory = nbt.getInteger("kernelMemory")
timeStarted = nbt.getLong("timeStarted") timeStarted = nbt.getLong("timeStarted")
cpuTime = nbt.getLong("cpuTime")
if (nbt.hasKey("message"))
message = Some(nbt.getString("message"))
// Clean up some after we're done and limit memory again. // Clean up some after we're done and limit memory again.
lua.gc(LuaState.GcAction.COLLECT, 0) lua.gc(LuaState.GcAction.COLLECT, 0)
@ -353,6 +375,9 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl
nbt.setCompoundTag("rom", romNbt) nbt.setCompoundTag("rom", romNbt)
nbt.setInteger("kernelMemory", kernelMemory) nbt.setInteger("kernelMemory", kernelMemory)
nbt.setLong("timeStarted", timeStarted) nbt.setLong("timeStarted", timeStarted)
nbt.setLong("cpuTime", cpuTime)
if (message.isDefined)
nbt.setString("message", message.get)
} }
catch { catch {
case e: Throwable => { case e: Throwable => {
@ -417,12 +442,9 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl
lua.getGlobal("os") lua.getGlobal("os")
// Custom os.clock() implementation returning the time the computer has // Custom os.clock() implementation returning the time the computer has
// been running, instead of the native library... // been actively running, instead of the native library...
lua.pushScalaFunction(lua => { lua.pushScalaFunction(lua => {
// World time is in ticks, and each second has 20 ticks. Since we lua.pushNumber((cpuTime + (System.nanoTime() - cpuStart)) * 10e-10)
// want os.clock() to return real seconds, though, we'll divide it
// accordingly.
lua.pushNumber((worldTime - timeStarted) / 20.0)
1 1
}) })
lua.setField(-2, "clock") lua.setField(-2, "clock")
@ -438,6 +460,16 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl
}) })
lua.setField(-2, "time") lua.setField(-2, "time")
// The time the computer has been running, as opposed to the CPU time.
lua.pushScalaFunction(lua => {
// World time is in ticks, and each second has 20 ticks. Since we
// want os.uptime() to return real seconds, though, we'll divide it
// accordingly.
lua.pushNumber((worldTime - timeStarted) / 20.0)
1
})
lua.setField(-2, "uptime")
// Allow the system to read how much memory it uses and has available. // Allow the system to read how much memory it uses and has available.
lua.pushScalaFunction(lua => { lua.pushScalaFunction(lua => {
lua.pushInteger(lua.getTotalMemory - kernelMemory) lua.pushInteger(lua.getTotalMemory - kernelMemory)
@ -541,7 +573,7 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl
results.length results.length
case _ => 0 case _ => 0
}) })
lua.setGlobal("sendToNode") lua.setGlobal("sendToAddress")
lua.pushScalaFunction(lua => { lua.pushScalaFunction(lua => {
owner.network.fold(None: Option[Node])(_.node(lua.checkString(1))) match { owner.network.fold(None: Option[Node])(_.node(lua.checkString(1))) match {
@ -592,7 +624,7 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl
// underlying system (which may change across releases). Add some buffer // underlying system (which may change across releases). Add some buffer
// to avoid the init script eating up all the rest immediately. // to avoid the init script eating up all the rest immediately.
lua.gc(LuaState.GcAction.COLLECT, 0) lua.gc(LuaState.GcAction.COLLECT, 0)
kernelMemory = (lua.getTotalMemory - lua.getFreeMemory) + 2048 kernelMemory = (lua.getTotalMemory - lua.getFreeMemory) + 8 * 1024
recomputeMemory() recomputeMemory()
// Clear any left-over signals from a previous run. // Clear any left-over signals from a previous run.
@ -621,6 +653,8 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl
kernelMemory = 0 kernelMemory = 0
signals.clear() signals.clear()
timeStarted = 0 timeStarted = 0
cpuTime = 0
cpuStart = 0
future = None future = None
sleepUntil = Long.MaxValue sleepUntil = Long.MaxValue
@ -628,6 +662,8 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl
owner.markAsChanged() owner.markAsChanged()
}) })
// ----------------------------------------------------------------------- //
private def execute(value: Computer.State.Value) { private def execute(value: Computer.State.Value) {
assert(future.isEmpty) assert(future.isEmpty)
sleepUntil = Long.MaxValue sleepUntil = Long.MaxValue
@ -666,6 +702,7 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl
try { try {
// Resume the Lua state and remember the number of results we get. // Resume the Lua state and remember the number of results we get.
cpuStart = System.nanoTime()
val results = if (callReturn) { val results = if (callReturn) {
// If we were doing a synchronized call, continue where we left off. // If we were doing a synchronized call, continue where we left off.
assert(lua.getTop == 2) assert(lua.getTop == 2)
@ -685,6 +722,7 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl
lua.resume(1, 1 + signal.args.length) lua.resume(1, 1 + signal.args.length)
} }
} }
cpuTime += System.nanoTime() - cpuStart
// Check if the kernel is still alive. // Check if the kernel is still alive.
stateMonitor.synchronized(if (lua.status(1) == LuaState.YIELD) { stateMonitor.synchronized(if (lua.status(1) == LuaState.YIELD) {
@ -696,7 +734,7 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl
// If we have a single number that's how long we may wait before // If we have a single number that's how long we may wait before
// resuming the state again. // resuming the state again.
else if (results == 1 && lua.isNumber(2)) { else if (results == 1 && lua.isNumber(2)) {
val sleep = (lua.toNumber(2) * 1000).toLong val sleep = lua.toNumber(2) * 1000
lua.pop(results) lua.pop(results)
// But only sleep if we don't have more signals to process. // But only sleep if we don't have more signals to process.
if (signals.isEmpty) { if (signals.isEmpty) {
@ -746,9 +784,7 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl
else { else {
// This can trigger another out of memory error if the original // This can trigger another out of memory error if the original
// error was an out of memory error. // 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 message = Some(lua.toString(3))
// TODO get this to the world as a computer.crashed message. problem: synchronizing it.
//owner.network.sendToAll(owner, "computer.crashed", lua.toString(3))
} }
close() close()
}) })
@ -756,16 +792,13 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl
catch { catch {
case e: LuaRuntimeException => case e: LuaRuntimeException =>
OpenComputers.log.warning("Kernel crashed. This is a bug!\n" + e.toString + "\tat " + e.getLuaStackTrace.mkString("\n\tat ")) OpenComputers.log.warning("Kernel crashed. This is a bug!\n" + e.toString + "\tat " + e.getLuaStackTrace.mkString("\n\tat "))
message = Some("kernel panic")
close() 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 message = Some("not enough memory")
// TODO get this to the world as a computer.crashed message. problem: synchronizing it.
//owner.network.sendToAll(owner, "computer.crashed", "not enough memory")
close() close()
case e: java.lang.Error if e.getMessage == "not enough memory" => 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 message = Some("not enough memory")
// TODO get this to the world as a computer.crashed message. problem: synchronizing it.
//owner.network.sendToAll(owner, "computer.crashed", "not enough memory")
close() close()
} }
} }

View File

@ -37,6 +37,14 @@ class FileSystem(val fileSystem: api.FileSystem) extends Node {
set.clear() set.clear()
} }
None None
case Array() if message.name == "fs.spaceTotal" =>
val space = fileSystem.spaceTotal
if (space < 0)
Some(Array("unlimited"))
else
Some(Array(space.asInstanceOf[Any]))
case Array() if message.name == "fs.spaceUsed" =>
Some(Array(fileSystem.spaceUsed.asInstanceOf[Any]))
case Array(path: Array[Byte]) if message.name == "fs.exists" => case Array(path: Array[Byte]) if message.name == "fs.exists" =>
Some(Array(fileSystem.exists(clean(path)).asInstanceOf[Any])) Some(Array(fileSystem.exists(clean(path)).asInstanceOf[Any]))
case Array(path: Array[Byte]) if message.name == "fs.size" => case Array(path: Array[Byte]) if message.name == "fs.size" =>

View File

@ -1,10 +1,16 @@
package li.cil.oc.server.component package li.cil.oc.server.component
import li.cil.oc.api.network.{Node, Visibility, Message} import li.cil.oc.api.network.{Node, Visibility, Message}
import li.cil.oc.common.component.ScreenEnvironment
import net.minecraft.nbt.NBTTagCompound
class GraphicsCard extends Node { class GraphicsCard extends Node {
val supportedResolutions = List(List(40, 24), List(80, 24)) val supportedResolutions = List(List(40, 24), List(80, 24))
private var screen: Option[String] = None
// ----------------------------------------------------------------------- //
override def name = "gpu" override def name = "gpu"
override def visibility = Visibility.Neighbors override def visibility = Visibility.Neighbors
@ -12,32 +18,66 @@ class GraphicsCard extends Node {
override def receive(message: Message) = { override def receive(message: Message) = {
super.receive(message) super.receive(message)
message.data match { message.data match {
case Array(screen: Array[Byte], w: Double, h: Double) if message.name == "gpu.resolution=" => case Array(address: Array[Byte]) if message.name == "gpu.bind" =>
network.fold(None: Option[Array[Any]])(network => {
network.node(new String(address, "UTF-8")) match {
case None => Some(Array(Unit, "invalid address"))
case Some(node: ScreenEnvironment) =>
screen = node.address
Some(Array(true.asInstanceOf[Any]))
case _ => Some(Array(Unit, "not a screen"))
}
})
case Array() if message.name == "network.disconnect" && message.source.address == screen => screen = None; None
case Array(w: Double, h: Double) if message.name == "gpu.resolution=" =>
if (supportedResolutions.contains((w.toInt, h.toInt))) if (supportedResolutions.contains((w.toInt, h.toInt)))
trySend(new String(screen, "UTF-8"), "screen.resolution=", w.toInt, h.toInt) trySend("screen.resolution=", w.toInt, h.toInt)
else else
Some(Array(Unit, "unsupported resolution")) Some(Array(Unit, "unsupported resolution"))
case Array(screen: Array[Byte]) if message.name == "gpu.resolution" => case Array() if message.name == "gpu.resolution" => trySend("screen.resolution")
trySend(new String(screen, "UTF-8"), "screen.resolution") case Array() if message.name == "gpu.resolutions" => trySend("screen.resolutions") match {
case Array(screen: Array[Byte]) if message.name == "gpu.resolutions" =>
trySend(new String(screen, "UTF-8"), "screen.resolutions") match {
case Some(Array(resolutions@_*)) => case Some(Array(resolutions@_*)) =>
Some(Array(supportedResolutions.intersect(resolutions): _*)) Some(Array(supportedResolutions.intersect(resolutions): _*))
case _ => None case _ => None
} }
case Array(screen: Array[Byte], x: Double, y: Double, value: Array[Byte]) if message.name == "gpu.set" => case Array(x: Double, y: Double, value: Array[Byte]) if message.name == "gpu.set" =>
trySend(new String(screen, "UTF-8"), "screen.set", x.toInt - 1, y.toInt - 1, new String(value, "UTF-8")) trySend("screen.set", x.toInt - 1, y.toInt - 1, new String(value, "UTF-8"))
case Array(screen: Array[Byte], x: Double, y: Double, w: Double, h: Double, value: Array[Byte]) if message.name == "gpu.fill" => case Array(x: Double, y: Double, w: Double, h: Double, value: Array[Byte]) if message.name == "gpu.fill" =>
val s = new String(value, "UTF-8") val s = new String(value, "UTF-8")
if (s.length == 1) if (s.length == 1)
trySend(new String(screen, "UTF-8"), "screen.fill", x.toInt - 1, y.toInt - 1, w.toInt, h.toInt, s.charAt(0)) trySend("screen.fill", x.toInt - 1, y.toInt - 1, w.toInt, h.toInt, s.charAt(0))
else else
Some(Array(Unit, "invalid fill value")) Some(Array(Unit, "invalid fill value"))
case Array(screen: Array[Byte], x: Double, y: Double, w: Double, h: Double, tx: Double, ty: Double) if message.name == "gpu.copy" => case Array(x: Double, y: Double, w: Double, h: Double, tx: Double, ty: Double) if message.name == "gpu.copy" =>
trySend(new String(screen, "UTF-8"), "screen.copy", x.toInt - 1, y.toInt - 1, w.toInt, h.toInt, tx.toInt, ty.toInt) trySend("screen.copy", x.toInt - 1, y.toInt - 1, w.toInt, h.toInt, tx.toInt, ty.toInt)
case _ => None case _ => None
} }
} }
private def trySend(target: String, name: String, data: Any*) = network.fold(None: Option[Array[Any]])(net => net.sendToAddress(this, target, name, data: _*)) override protected def onDisconnect() = {
super.onDisconnect()
screen = None
}
override def load(nbt: NBTTagCompound) = {
super.load(nbt)
if (nbt.hasKey("screen"))
screen = Some(nbt.getString("screen"))
}
override def save(nbt: NBTTagCompound) = {
super.save(nbt)
if (screen.isDefined)
nbt.setString("screen", screen.get)
}
// ----------------------------------------------------------------------- //
private def trySend(name: String, data: Any*): Option[Array[Any]] =
screen match {
case None => Some(Array(Unit, "no screen"))
case Some(screenAddress) => network.fold(None: Option[Array[Any]])(net => {
net.sendToAddress(this, screenAddress, name, data: _*)
})
}
} }

View File

@ -7,7 +7,7 @@ import li.cil.oc.{Config, Items}
import net.minecraft.item.ItemStack import net.minecraft.item.ItemStack
object FileSystem extends Item { object FileSystem extends Item {
override def api = Option(getClass.getResourceAsStream(Config.driverPath + "fs.lua")) override def api = Option(getClass.getResourceAsStream(Config.driverPath + "filesystem.lua"))
override def worksWith(item: ItemStack) = WorksWith(Items.hdd2, Items.hdd4, Items.hdd8)(item) override def worksWith(item: ItemStack) = WorksWith(Items.hdd2, Items.hdd4, Items.hdd8)(item)

View File

@ -7,7 +7,19 @@ import li.cil.oc.api.fs.Mode
trait Capacity extends OutputStreamFileSystem { trait Capacity extends OutputStreamFileSystem {
private var used = computeSize("/") private var used = computeSize("/")
def capacity: Int protected def capacity: Int
// ----------------------------------------------------------------------- //
override def spaceTotal = capacity
override def spaceUsed = used
// ----------------------------------------------------------------------- //
override def makeDirectories(path: String) = {
super.makeDirectories(path)
}
override protected def delete(path: String) = { override protected def delete(path: String) = {
val freed = Config.fileCost + size(path) val freed = Config.fileCost + size(path)
@ -18,6 +30,8 @@ trait Capacity extends OutputStreamFileSystem {
else false else false
} }
// ----------------------------------------------------------------------- //
override protected abstract def openOutputStream(path: String, mode: Mode.Value): Option[io.OutputStream] = { override protected abstract def openOutputStream(path: String, mode: Mode.Value): Option[io.OutputStream] = {
val delta = val delta =
if (exists(path)) if (exists(path))
@ -35,6 +49,8 @@ trait Capacity extends OutputStreamFileSystem {
} }
} }
// ----------------------------------------------------------------------- //
private def computeSize(path: String): Long = private def computeSize(path: String): Long =
Config.fileCost + Config.fileCost +
size(path) + size(path) +

View File

@ -7,6 +7,8 @@ import li.cil.oc.api
trait FileInputStreamFileSystem extends api.FileSystem with InputStreamFileSystem { trait FileInputStreamFileSystem extends api.FileSystem with InputStreamFileSystem {
protected val root: io.File protected val root: io.File
// ----------------------------------------------------------------------- //
override def exists(path: String) = new io.File(root, path).exists() override def exists(path: String) = new io.File(root, path).exists()
override def size(path: String) = new io.File(root, path) match { override def size(path: String) = new io.File(root, path) match {
@ -23,6 +25,8 @@ trait FileInputStreamFileSystem extends api.FileSystem with InputStreamFileSyste
case _ => throw new FileNotFoundException("no such file or directory") case _ => throw new FileNotFoundException("no such file or directory")
} }
// ----------------------------------------------------------------------- //
override protected def openInputStream(path: String) = override protected def openInputStream(path: String) =
Some(new FileInputStream(new io.File(root, path))) Some(new FileInputStream(new io.File(root, path)))
} }

View File

@ -5,10 +5,22 @@ import java.io.FileOutputStream
import li.cil.oc.api.fs.Mode import li.cil.oc.api.fs.Mode
trait FileOutputStreamFileSystem extends FileInputStreamFileSystem with OutputStreamFileSystem { trait FileOutputStreamFileSystem extends FileInputStreamFileSystem with OutputStreamFileSystem {
override protected def openOutputStream(path: String, mode: Mode.Value): Option[io.OutputStream] = override def spaceTotal = -1
Some(new FileOutputStream(new io.File(root, path), mode == Mode.Append))
override def spaceUsed = root.getFreeSpace
// ----------------------------------------------------------------------- //
override def rename(from: String, to: String) = new io.File(root, from).renameTo(new io.File(root, to)) override def rename(from: String, to: String) = new io.File(root, from).renameTo(new io.File(root, to))
// ----------------------------------------------------------------------- //
override protected def makeDirectory(path: String) = new io.File(root, path).mkdir()
override protected def delete(path: String) = new io.File(root, path).delete() override protected def delete(path: String) = new io.File(root, path).delete()
// ----------------------------------------------------------------------- //
override protected def openOutputStream(path: String, mode: Mode.Value): Option[io.OutputStream] =
Some(new FileOutputStream(new io.File(root, path), mode == Mode.Append))
} }

View File

@ -52,7 +52,7 @@ object FileSystem extends api.detail.FileSystemAPI {
extends InputStreamFileSystem extends InputStreamFileSystem
with FileInputStreamFileSystem with FileInputStreamFileSystem
private class ReadWriteFileSystem(protected val root: io.File, val capacity: Int) private class ReadWriteFileSystem(protected val root: io.File, protected val capacity: Int)
extends OutputStreamFileSystem extends OutputStreamFileSystem
with FileOutputStreamFileSystem with FileOutputStreamFileSystem
with Capacity with Capacity

View File

@ -9,6 +9,8 @@ import scala.collection.mutable
trait InputStreamFileSystem extends api.FileSystem { trait InputStreamFileSystem extends api.FileSystem {
private val handles = mutable.Map.empty[Int, Handle] private val handles = mutable.Map.empty[Int, Handle]
// ----------------------------------------------------------------------- //
override def open(path: String, mode: Mode.Value) = if (mode == Mode.Read && exists(path) && !isDirectory(path)) { override def open(path: String, mode: Mode.Value) = if (mode == Mode.Read && exists(path) && !isDirectory(path)) {
val handle = Iterator.continually((Math.random() * Int.MaxValue).toInt + 1).filterNot(handles.contains).next() val handle = Iterator.continually((Math.random() * Int.MaxValue).toInt + 1).filterNot(handles.contains).next()
openInputStream(path) match { openInputStream(path) match {
@ -27,6 +29,8 @@ trait InputStreamFileSystem extends api.FileSystem {
handles.clear() handles.clear()
} }
// ----------------------------------------------------------------------- //
override def load(nbt: NBTTagCompound) { override def load(nbt: NBTTagCompound) {
val handlesNbt = nbt.getTagList("input") val handlesNbt = nbt.getTagList("input")
(0 until handlesNbt.tagCount).map(handlesNbt.tagAt).map(_.asInstanceOf[NBTTagCompound]).foreach(handleNbt => { (0 until handlesNbt.tagCount).map(handlesNbt.tagAt).map(_.asInstanceOf[NBTTagCompound]).foreach(handleNbt => {
@ -56,8 +60,12 @@ trait InputStreamFileSystem extends api.FileSystem {
nbt.setTag("input", handlesNbt) nbt.setTag("input", handlesNbt)
} }
// ----------------------------------------------------------------------- //
protected def openInputStream(path: String): Option[InputStream] protected def openInputStream(path: String): Option[InputStream]
// ----------------------------------------------------------------------- //
private class Handle(val owner: InputStreamFileSystem, val handle: Int, val path: String, val stream: InputStream) extends api.fs.Handle { private class Handle(val owner: InputStreamFileSystem, val handle: Int, val path: String, val stream: InputStream) extends api.fs.Handle {
var isClosed = false var isClosed = false
var position = 0L var position = 0L
@ -82,7 +90,7 @@ trait InputStreamFileSystem extends api.FileSystem {
stream.skip(to) stream.skip(to)
} }
def write(value: Array[Byte]) = throw new IOException("handle is read-only") def write(value: Array[Byte]) = throw new IOException("bad file descriptor")
} }
} }

View File

@ -9,6 +9,8 @@ import scala.collection.mutable
trait OutputStreamFileSystem extends InputStreamFileSystem { trait OutputStreamFileSystem extends InputStreamFileSystem {
private val handles = mutable.Map.empty[Int, Handle] private val handles = mutable.Map.empty[Int, Handle]
// ----------------------------------------------------------------------- //
override def open(path: String, mode: Mode.Value) = mode match { override def open(path: String, mode: Mode.Value) = mode match {
case Mode.Read => super.open(path, mode) case Mode.Read => super.open(path, mode)
case _ => if (!isDirectory(path)) { case _ => if (!isDirectory(path)) {
@ -31,6 +33,8 @@ trait OutputStreamFileSystem extends InputStreamFileSystem {
handles.clear() handles.clear()
} }
// ----------------------------------------------------------------------- //
override def load(nbt: NBTTagCompound) { override def load(nbt: NBTTagCompound) {
super.load(nbt) super.load(nbt)
val handlesNbt = nbt.getTagList("output") val handlesNbt = nbt.getTagList("output")
@ -59,8 +63,12 @@ trait OutputStreamFileSystem extends InputStreamFileSystem {
nbt.setTag("output", handlesNbt) nbt.setTag("output", handlesNbt)
} }
// ----------------------------------------------------------------------- //
protected def openOutputStream(path: String, mode: Mode.Value): Option[OutputStream] protected def openOutputStream(path: String, mode: Mode.Value): Option[OutputStream]
// ----------------------------------------------------------------------- //
private class Handle(val owner: OutputStreamFileSystem, val handle: Int, val path: String, val stream: OutputStream) extends api.fs.Handle { private class Handle(val owner: OutputStreamFileSystem, val handle: Int, val path: String, val stream: OutputStream) extends api.fs.Handle {
var isClosed = false var isClosed = false
val position = 0L val position = 0L
@ -72,9 +80,9 @@ trait OutputStreamFileSystem extends InputStreamFileSystem {
stream.close() stream.close()
} }
def read(into: Array[Byte]) = throw new IOException("handle is write-only") def read(into: Array[Byte]) = throw new IOException("bad file descriptor")
def seek(to: Long) = throw new IOException("handle is write-only") def seek(to: Long) = throw new IOException("bad file descriptor")
def write(value: Array[Byte]) { def write(value: Array[Byte]) {
stream.write(value) stream.write(value)

View File

@ -26,6 +26,8 @@ class Network private(private val addressedNodes: mutable.Map[String, Network.No
addressedNodes.values.foreach(_.data.network = Some(this)) addressedNodes.values.foreach(_.data.network = Some(this))
unaddressedNodes.foreach(_.data.network = Some(this)) unaddressedNodes.foreach(_.data.network = Some(this))
// ----------------------------------------------------------------------- //
override def connect(nodeA: api.network.Node, nodeB: api.network.Node) = { override def connect(nodeA: api.network.Node, nodeB: api.network.Node) = {
val containsA = contains(nodeA) val containsA = contains(nodeA)
val containsB = contains(nodeB) val containsB = contains(nodeB)
@ -104,6 +106,8 @@ class Network private(private val addressedNodes: mutable.Map[String, Network.No
} }
} }
// ----------------------------------------------------------------------- //
override def node(address: String) = addressedNodes.get(address) match { override def node(address: String) = addressedNodes.get(address) match {
case Some(node) => Some(node.data) case Some(node) => Some(node.data)
case _ => None case _ => None
@ -130,6 +134,8 @@ class Network private(private val addressedNodes: mutable.Map[String, Network.No
} }
} }
// ----------------------------------------------------------------------- //
override def sendToAddress(source: api.network.Node, target: String, name: String, data: Any*) = { override def sendToAddress(source: api.network.Node, target: String, name: String, data: Any*) = {
if (source.network.isEmpty || source.network.get != this) if (source.network.isEmpty || source.network.get != this)
throw new IllegalArgumentException("Source node must be in this network.") throw new IllegalArgumentException("Source node must be in this network.")
@ -157,6 +163,8 @@ class Network private(private val addressedNodes: mutable.Map[String, Network.No
else None else None
} }
// ----------------------------------------------------------------------- //
private def contains(node: api.network.Node) = (node.address match { private def contains(node: api.network.Node) = (node.address match {
case None => unaddressedNodes.find(_.data == node) case None => unaddressedNodes.find(_.data == node)
case Some(address) => addressedNodes.get(address) case Some(address) => addressedNodes.get(address)
@ -306,6 +314,18 @@ object Network extends api.detail.NetworkAPI {
} }
} }
private def getNetworkNode(world: IBlockAccess, x: Int, y: Int, z: Int): Option[TileEntity with api.network.Node] =
Option(Block.blocksList(world.getBlockId(x, y, z))) match {
case Some(block) if block.hasTileEntity(world.getBlockMetadata(x, y, z)) =>
world.getBlockTileEntity(x, y, z) match {
case tileEntity: TileEntity with api.network.Node => Some(tileEntity)
case _ => None
}
case _ => None
}
// ----------------------------------------------------------------------- //
@ForgeSubscribe @ForgeSubscribe
def onChunkUnload(e: ChunkEvent.Unload) = def onChunkUnload(e: ChunkEvent.Unload) =
onUnload(e.world, e.getChunk.chunkTileEntityMap.values.asScala.map(_.asInstanceOf[TileEntity])) onUnload(e.world, e.getChunk.chunkTileEntityMap.values.asScala.map(_.asInstanceOf[TileEntity]))
@ -326,15 +346,7 @@ object Network extends api.detail.NetworkAPI {
tileEntities.foreach(t => joinOrCreateNetwork(w, t.xCoord, t.yCoord, t.zCoord)) tileEntities.foreach(t => joinOrCreateNetwork(w, t.xCoord, t.yCoord, t.zCoord))
} }
private def getNetworkNode(world: IBlockAccess, x: Int, y: Int, z: Int): Option[TileEntity with api.network.Node] = // ----------------------------------------------------------------------- //
Option(Block.blocksList(world.getBlockId(x, y, z))) match {
case Some(block) if block.hasTileEntity(world.getBlockMetadata(x, y, z)) =>
world.getBlockTileEntity(x, y, z) match {
case tileEntity: TileEntity with api.network.Node => Some(tileEntity)
case _ => None
}
case _ => None
}
private class Node(val data: api.network.Node) { private class Node(val data: api.network.Node) {
val edges = ArrayBuffer.empty[Edge] val edges = ArrayBuffer.empty[Edge]
@ -382,6 +394,8 @@ object Network extends api.detail.NetworkAPI {
}) filter (_.nonEmpty) map (_.get) }) filter (_.nonEmpty) map (_.get)
} }
// ----------------------------------------------------------------------- //
private class Message(@BeanProperty val source: api.network.Node, private class Message(@BeanProperty val source: api.network.Node,
@BeanProperty val name: String, @BeanProperty val name: String,
@BeanProperty val data: Array[Any] = Array()) extends api.network.Message { @BeanProperty val data: Array[Any] = Array()) extends api.network.Message {

View File

@ -197,6 +197,14 @@ object LuaStateFactory {
// Provide some better Unicode support. // Provide some better Unicode support.
state.getGlobal("string") state.getGlobal("string")
// Rename stuff for binary functionality, to allow byte-wise operations
// operations on the string.
state.getField(-1, "sub")
state.setField(-2, "bsub")
state.getField(-1, "reverse")
state.setField(-2, "breverse")
state.pushScalaFunction(lua => { state.pushScalaFunction(lua => {
lua.pushString(String.valueOf((1 to lua.getTop).map(lua.checkInteger).map(_.toChar).toArray)) lua.pushString(String.valueOf((1 to lua.getTop).map(lua.checkInteger).map(_.toChar).toArray))
1 1
@ -231,11 +239,6 @@ object LuaStateFactory {
}) })
state.setField(-2, "reverse") state.setField(-2, "reverse")
// Rename string.sub to string.bsub (for binary sub, to allow byte-wise
// operations on the string).
state.getField(-1, "sub")
state.setField(-2, "bsub")
state.pushScalaFunction(lua => { state.pushScalaFunction(lua => {
val string = lua.checkString(1) val string = lua.checkString(1)
val start = (lua.checkInteger(2) match { val start = (lua.checkInteger(2) match {