OC-KittenOS/repository/apps/app-metamachine.lua

545 lines
14 KiB
Lua

-- This is released into the public domain.
-- No warranty is provided, implied or otherwise.
-- app-metamachine.lua : Virtual Machine
-- Authors: 20kdc
local loaderPkg, loaderPid, vmName = ...
local icecap = neo.requireAccess("x.neo.pub.base", "fs")
local libVGPU = require("metamachine-vgpu")
local vmBaseCoroutineWrap
local vmComponent, vmComputer, vmOs
local vmEnvironment
local vmSelfdestruct = false
local vmSuperVM = true
local signalStack = {}
local postVMRList = {}
-- File structure:
-- vm-* : Virtual machine configuration
-- vm-
local vmConfiguration = {
-- true : Physical
-- {type, ...} : Virtual
-- NOTE : The following rules are set.
-- k-computer always exists
-- k-gpu always exists
-- k-log always exists
-- k-tmpfs always exists in non-Super VMs
["world"] = {"filesystem", "/", false},
["eeprom"] = {"eeprom", "/confboot.lua", "/confdata.bin", "Configurator", true},
["screen"] = {"screen", "configurator", 50, 15, 8}
}
if vmName then
neo.ensurePathComponent("vm-" .. vmName)
vmSuperVM = false
local f = icecap.open("/vm-" .. vmName, false)
vmConfiguration = require("serial").deserialize(f.read("*a"))
f.close()
if not vmConfiguration then error("The VM configuration was unloadable.") end
vmConfiguration["k-tmpfs"] = {"filesystem", "/vt-" .. vmName .. "/", false}
end
local function clone(t)
if type(t) == "table" then
local b = {}
for k, v in pairs(t) do
b[k] = v
end
return b
end
return t
end
-- by window ID = {address, internal}
local screensInt = {
}
-- by component address = callback
local screensAll = {
}
local tmpAddress = "k-tmpfs"
local passthroughs = {}
local components = {
["k-computer"] = {
type = "computer",
beep = function ()
end,
start = function ()
return false
end,
stop = function ()
vmSelfdestruct = true
coroutine.yield(0.05)
end,
isRunning = function ()
return true
end,
getProgramLocations = function ()
-- Entries of {"file", "lootdisk"}
return {}
end
},
["k-gpu"] = libVGPU.newGPU(screensAll),
["k-log"] = {
type = "ocemu",
log = neo.emergency
}
}
-- Clones of components made on-demand.
local proxies = {}
setmetatable(proxies, {__mode = "v"})
vmComponent = {
list = function (filter, exact)
-- This is an iterator :(
local t = {}
for k, v in pairs(components) do
local ok = false
if filter then
if v.type == filter or ((not exact) and v.type:match(filter, 1, true)) then
ok = true
end
else
ok = true
end
if ok then
table.insert(t, {k, v.type})
end
end
return function ()
local tr1 = table.remove(t, 1)
if not tr1 then return end
return table.unpack(tr1)
end, 9, nil
end,
invoke = function (com, me, ...)
if not components[com] then error("no component " .. com) end
if not components[com][me] then error("no method " .. com .. "." .. me) end
return components[com][me](...)
end,
proxy = function (com)
if not components[com] then
return nil, "no such component"
end
local p = proxies[com]
if p then return p end
p = clone(components[com])
p.address = com
p.fields = {}
p.slot = 0
proxies[com] = p
return p
end,
type = function (com)
if not components[com] then
return nil, "no such component"
end
return components[com].type
end,
methods = function (com)
local mt = {}
for k, v in pairs(components[address]) do
if type(v) == "function" then
mt[k] = true
end
end
return mt
end,
fields = function (com)
-- This isn't actually supported,
-- because fields are bad-sec nonsense.
-- Luckily, everybody knows this, so nobody uses them.
return {}
end,
doc = function (address, method)
if not components[address] then
error("No such component " .. address)
end
if not components[address][method] then
error("No such method " .. method)
end
return tostring(components[address][method])
end
}
-- Prepare configured components
local insertionCallbacks = {
["screen"] = function (address, title, w, h, d)
local activeLines = {}
local scrW = neo.requireAccess("x.neo.pub.window", "primary window")(w, h, title)
local gpuC, scrI, scrC
gpuC, scrI, scrC = libVGPU.newBuffer(scrW, {address .. "-kb"}, w, h, function (nw, nh)
table.insert(signalStack, {"screen_resized", address, nw, nh})
end, function (l)
if activeLines[l] then
return
end
activeLines[l] = true
table.insert(postVMRList, function ()
scrI.line(l)
activeLines = {}
end)
end)
components[address] = scrC
components[address .. "-kb"] = {type = "keyboard"}
screensInt[scrW.id] = {address, scrI}
screensAll[address] = gpuC
end,
["eeprom"] = function (address, boot, data, name, ro)
local codeSize = 4096
local dataSize = 256
local function getCore(fd)
local f = icecap.open(fd, false)
if not f then return "" end
local contents = f.read("*a")
f.close()
return contents
end
local function setCore(fd, size, contents, important)
checkArg(1, contents, "string")
if #contents > size then return nil, "too large" end
if ro and important then
return nil, "storage is readonly"
end
local f = icecap.open(fd, true)
if not f then return nil, "storage is readonly" end
f.write(contents)
f.close()
return true
end
components[address] = {
type = "eeprom",
get = function ()
return getCore(boot)
end,
set = function (contents)
return setCore(boot, codeSize, contents, true)
end,
makeReadonly = function ()
ro = true
return true
end,
getChecksum = function ()
return "00000000"
end,
getSize = function ()
return codeSize
end,
getDataSize = function ()
return dataSize
end,
getData = function ()
return getCore(data)
end,
setData = function (contents)
return setCore(data, dataSize, contents, false)
end
}
end,
["filesystem"] = function (address, path, ro)
components[address] = require("metamachine-vfs")(icecap, address, path, ro)
end
}
for k, v in pairs(vmConfiguration) do
if type(v) == "string" then
local root = neo.requireAccess("k.root", "component passthrough")
local ty = root.component.type(k)
if ty then
passthroughs[k] = true
components[k] = root.component.proxy(k)
if ty == "screen" then
-- Need to ensure the screen in question is for the taking
local div = neo.requireAccess("x.neo.sys.session", "ability to divorce screens")
div.disclaimMonitor(k)
local div2 = neo.requireAccess("x.neo.sys.screens", "ability to claim screens")
screensAll[k] = div2.claim(k)
assert(screensAll[k], "Hardware screen " .. k .. " unavailable.")
end
end
else
assert(insertionCallbacks[v[1]], "Cannot insert virtual " .. v[1])
insertionCallbacks[v[1]](k, table.unpack(v, 2))
end
end
vmOs = clone(os)
vmComputer = {}
vmComputer.shutdown = function (...)
vmSelfdestruct = true
coroutine.yield(0.05)
end
vmComputer.pushSignal = function (...)
table.insert(signalStack, {...})
end
vmComputer.pullSignal = function (time)
if not signalStack[1] then
if type(time) == "number" then
time = time + os.uptime()
coroutine.yield(time)
if not signalStack[1] then
return
end
else
while not signalStack[1] do
coroutine.yield(math.huge)
end
end
end
return table.unpack(table.remove(signalStack, 1))
end
vmComputer.totalMemory = os.totalMemory
vmOs.totalMemory = nil
vmComputer.freeMemory = os.freeMemory
vmOs.freeMemory = nil
vmComputer.energy = os.energy
vmOs.energy = nil
vmComputer.maxEnergy = os.maxEnergy
vmOs.maxEnergy = nil
local startupUptime = os.uptime()
vmComputer.uptime = function ()
return os.uptime() - startupUptime
end
vmOs.uptime = nil
vmComputer.address = os.address
vmOs.address = nil
vmComputer.isRobot = function ()
return false
end
vmComputer.address = function ()
return "k-computer"
end
vmComputer.tmpAddress = function ()
return tmpAddress
end
local eepromAddress = "k-eeprom"
vmComputer.getBootAddress = function ()
return eepromAddress
end
vmComputer.setBootAddress = function (a)
eepromAddress = a
end
vmComputer.users = function ()
return {}
end
vmComputer.addUser = function ()
return false, "user support not available"
end
vmComputer.removeUser = function ()
return false, "user support not available"
end
vmComputer.beep = function (...)
return vmComponent.invoke("k-computer", "beep", ...)
end
vmComputer.getDeviceInfo = function (...)
return vmComponent.invoke("k-computer", "getDeviceInfo", ...)
end
vmComputer.getProgramLocations = function (...)
return vmComponent.invoke("k-computer", "getProgramLocations", ...)
end
vmComputer.getArchitectures = function (...)
return vmComponent.invoke("k-computer", "getArchitectures", ...)
end
vmComputer.getArchitecture = function (...)
return vmComponent.invoke("k-computer", "getArchitecture", ...)
end
vmComputer.setArchitecture = function (...)
return vmComponent.invoke("k-computer", "setArchitecture", ...)
end
vmUnicode = clone(unicode)
vmUnicode.safeTextSupport = nil
vmUnicode.undoSafeTextSupport = nil
vmEnvironment = {
_VERSION = _VERSION,
component = vmComponent,
computer = vmComputer,
table = clone(table),
math = clone(math),
string = clone(string),
unicode = vmUnicode,
-- Scheme here:
-- A yield's first argument is nil for an actual yield,
-- or the time to add a timer at (math.huge if no timeout) for a pullSignal.
-- This is not exactly the same, but is very similar, to that of machine.lua,
-- differing mainly in how pullSignal timeout scheduling occurs.
coroutine = {
yield = function (...)
return coroutine.yield(nil, ...)
end,
-- The way this is defined by machine.lua makes it true even when it arguably shouldn't be. Oh well.
isyieldable = coroutine.isyieldable,
status = coroutine.status,
create = function (f)
return coroutine.create(function (...)
return nil, f(...)
end)
end,
running = coroutine.running,
wrap = function (f)
local pf = coroutine.wrap(function (...)
return nil, f(...)
end)
return function (...)
local last = {...}
while true do
local tabpack = {pf(table.unpack(last))}
if not tabpack[1] then
return table.unpack(tabpack, 2)
end
last = {coroutine.yield(tabpack[1])}
end
end
end,
resume = function (co, ...)
local last = {...}
while true do
local tabpack = {coroutine.resume(co, table.unpack(last))}
if not tabpack[1] then
neo.emergency(co, table.unpack(tabpack))
return table.unpack(tabpack)
elseif not tabpack[2] then
return tabpack[1], table.unpack(tabpack, 3)
end
last = {coroutine.yield(tabpack[2])}
end
end
},
os = vmOs,
debug = clone(debug),
bit32 = clone(bit32),
utf8 = clone(utf8),
assert = assert,
ipairs = ipairs,
next = next,
load = function (a, b, c, d)
if rawequal(d, nil) then
d = vmEnvironment
end
return load(a, b, c, d)
end,
pairs = pairs, pcall = function (...)
local r = {pcall(...)}
if not r[1] then
neo.emergency("pcall error:", table.unpack(r, 2))
end
return table.unpack(r)
end,
xpcall = xpcall, select = select,
type = type, error = error,
tonumber = tonumber, tostring = tostring,
setmetatable = setmetatable, getmetatable = getmetatable,
rawset = rawset, rawget = rawget,
rawlen = rawlen, rawequal = rawequal,
checkArg = checkArg
}
vmEnvironment._G = vmEnvironment
if vmSuperVM then
vmEnvironment._MMstartVM = function (vmName)
neo.executeAsync("app-metamachine", vmName)
end
vmEnvironment._MMserial = function (...)
return require("serial").serialize(...)
end
vmEnvironment._MMdeserial = function (...)
return require("serial").deserialize(...)
end
vmEnvironment.print = neo.emergency
local root = neo.requestAccess("k.root")
if root then
vmEnvironment._MMcomList = root.component.list
else
vmEnvironment._MMcomList = function ()
return function ()
end
end
end
end
-- bootstrap
vmBaseCoroutineWrap = coroutine.wrap(function ()
vmBaseCoroutine = coroutine.running()
eepromAddress = vmComponent.list("eeprom")()
if not eepromAddress then
error("No EEPROM")
end
local code = vmComponent.invoke(eepromAddress, "get")
local res, f = load(code, "=eeprom", "t", vmEnvironment)
if not res then
error(f)
else
res()
end
end)
while ((not vmBaseCoroutine) or (coroutine.status(vmBaseCoroutine) ~= "dead")) and not vmSelfdestruct do
local details = {vmBaseCoroutineWrap()}
while postVMRList[1] do
table.remove(postVMRList, 1)()
end
if details[1] then
local checkTimer = nil
if details[1] ~= math.huge then
checkTimer = neo.scheduleTimer(details[1])
--neo.emergency("metamachine timer " .. details[1])
else
--neo.emergency("metamachine HANG")
end
while true do
local ev = {coroutine.yield()}
if ev[1] == "k.timer" then
if ev[2] == checkTimer then
break
end
elseif ev[1]:sub(1, 2) == "h." then
if passthroughs[ev[2]] then
ev[1] = ev[1]:sub(3)
table.insert(signalStack, ev)
break
end
elseif ev[1] == "x.neo.pub.window" then
local id = ev[2]
if ev[3] == "key" then
if ev[6] then
table.insert(signalStack, {"key_down", screensInt[id][1] .. "-kb", ev[4], ev[5], "neo"})
else
table.insert(signalStack, {"key_up", screensInt[id][1] .. "-kb", ev[4], ev[5], "neo"})
end
break
elseif ev[3] == "line" then
screensInt[id][2].line(ev[4])
elseif ev[3] == "touch" or ev[3] == "drag" or ev[3] == "drop" or ev[3] == "scroll" then
local x = ev[4]
local y = ev[5]
if screensInt[id][2].precise then
x = (x - 1) + ev[6]
y = (y - 1) + ev[7]
end
table.insert(signalStack, {ev[3], screensInt[id][1], x, y, ev[8], "neo"})
break
elseif ev[3] == "close" then
return
end
end
end
else
error("Yield in root coroutine")
end
end