mirror of
https://github.com/20kdc/OC-KittenOS.git
synced 2024-11-08 19:48:07 +11:00
550 lines
14 KiB
Lua
550 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 = {}
|
|
local tk = {}
|
|
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})
|
|
tk[k] = v.type
|
|
end
|
|
end
|
|
setmetatable(tk, {
|
|
__call = function ()
|
|
local tr1 = table.remove(t, 1)
|
|
if not tr1 then return end
|
|
return table.unpack(tr1)
|
|
end
|
|
})
|
|
return tk
|
|
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
|