mirror of
https://github.com/20kdc/OC-KittenOS.git
synced 2024-11-27 04:48:05 +11:00
611 lines
16 KiB
Lua
611 lines
16 KiB
Lua
-- Copyright (C) 2018-2021 by KittenOS NEO contributors
|
|
--
|
|
-- Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
|
|
--
|
|
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
|
|
-- THIS SOFTWARE.
|
|
|
|
-- 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 fakeArch = _VERSION
|
|
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,
|
|
getDeviceInfo = function ()
|
|
return {
|
|
["k-computer"] = {
|
|
["class"] = "system",
|
|
["description"] = "Computer",
|
|
["product"] = "Freeziflow Liquid-Cooling Unit",
|
|
["vendor"] = "KDC Subsystems",
|
|
["capacity"] = "10",
|
|
["width"] = "",
|
|
["clock"] = ""
|
|
},
|
|
["k-processor"] = {
|
|
["class"] = "processor",
|
|
["description"] = "CPU",
|
|
["product"] = "Celesti4 Quantum Computing System",
|
|
["vendor"] = "KDC Subsystems",
|
|
["capacity"] = "",
|
|
["width"] = "",
|
|
["clock"] = "9000"
|
|
},
|
|
["k-memory"] = {
|
|
["class"] = "memory",
|
|
["description"] = "Memory bank",
|
|
["product"] = "Lun4 Paging Subsystem",
|
|
["vendor"] = "KDC Subsystems",
|
|
["capacity"] = "",
|
|
["width"] = "",
|
|
["clock"] = "9000"
|
|
},
|
|
["k-gpu"] = {
|
|
["class"] = "display",
|
|
["description"] = "Graphics controller",
|
|
["product"] = "Tw1-l GPU Multiplexer",
|
|
["vendor"] = "KDC Subsystems",
|
|
["capacity"] = "8000",
|
|
["width"] = "8",
|
|
["clock"] = "9000"
|
|
}
|
|
}
|
|
end,
|
|
getArchitectures = function ()
|
|
return {fakeArch}
|
|
end,
|
|
setArchitecture = function (a)
|
|
fakeArch = a
|
|
end,
|
|
getArchitecture = function ()
|
|
return fakeArch
|
|
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 such component " .. com) end
|
|
if not components[com][me] then error("no such 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)
|
|
if not components[com] then
|
|
return nil, "no such component"
|
|
end
|
|
local mt = {}
|
|
for k, v in pairs(components[com]) 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")
|
|
end
|
|
if not components[address][method] then
|
|
return
|
|
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 = {
|
|
-- NOTE: I can't give you a definitive list from OC because OC (or OpenOS?) screws up a metatable!
|
|
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] == "clipboard" then
|
|
table.insert(signalStack, {ev[3], screensInt[id][1] .. "-kb", ev[4], "neo"})
|
|
break
|
|
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
|