diff --git a/Basalt/objects/Program.lua b/Basalt/objects/Program.lua index 76f4594..28bfc1e 100644 --- a/Basalt/objects/Program.lua +++ b/Basalt/objects/Program.lua @@ -1,808 +1,84 @@ -local Object = require("Object") -local tHex = require("tHex") -local process = require("process") -local xmlValue = require("utils").getValueFromXML +local processes = {} +local process = {} +local processId = 0 -local sub = string.sub +local newPackage = dofile("rom/modules/main/cc/require.lua").make -return function(name, parent) - local base = Object(name) - local objectType = "Program" - base:setZIndex(5) - local object - local cachedPath - local enviroment = {} - - local function createBasaltWindow(x, y, width, height, self) - local xCursor, yCursor = 1, 1 - local bgColor, fgColor = colors.black, colors.white - local cursorBlink = false - local visible = false - - local cacheT = {} - local cacheBG = {} - local cacheFG = {} - - local tPalette = {} - - local emptySpaceLine - local emptyColorLines = {} - - for i = 0, 15 do - local c = 2 ^ i - tPalette[c] = { parent:getBasaltInstance().getBaseTerm().getPaletteColour(c) } - end - - local function createEmptyLines() - emptySpaceLine = (" "):rep(width) - for n = 0, 15 do - local nColor = 2 ^ n - local sHex = tHex[nColor] - emptyColorLines[nColor] = sHex:rep(width) +function process:new(path, window, newEnv, ...) + local args = {...} + local newP = setmetatable({ path = path }, { __index = self }) + newP.window = window + window.current = term.current + window.redirect = term.redirect + newP.processId = processId + if(type(path)=="string")then + newP.coroutine = coroutine.create(function() + local pPath = shell.resolveProgram(path) + local env = setmetatable(newEnv, {__index=_ENV}) + env.shell = shell + env.basaltProgram=true + env.require, env.package = newPackage(env, fs.getDir(pPath)) + if(fs.exists(pPath))then + local file = fs.open(pPath, "r") + local content = file.readAll() + file.close() + local program = load(content, path, "bt", env) + if(program~=nil)then + return program(table.unpack(args)) end end - - local function recreateWindowArray() - createEmptyLines() - local emptyText = emptySpaceLine - local emptyFG = emptyColorLines[colors.white] - local emptyBG = emptyColorLines[colors.black] - for n = 1, height do - cacheT[n] = sub(cacheT[n] == nil and emptyText or cacheT[n] .. emptyText:sub(1, width - cacheT[n]:len()), 1, width) - cacheFG[n] = sub(cacheFG[n] == nil and emptyFG or cacheFG[n] .. emptyFG:sub(1, width - cacheFG[n]:len()), 1, width) - cacheBG[n] = sub(cacheBG[n] == nil and emptyBG or cacheBG[n] .. emptyBG:sub(1, width - cacheBG[n]:len()), 1, width) - end - base.updateDraw(base) - end - recreateWindowArray() - - local function updateCursor() - if xCursor >= 1 and yCursor >= 1 and xCursor <= width and yCursor <= height then - --parentTerminal.setCursorPos(xCursor + x - 1, yCursor + y - 1) - else - --parentTerminal.setCursorPos(0, 0) - end - --parentTerminal.setTextColor(fgColor) - end - - local function internalBlit(sText, sTextColor, sBackgroundColor) - -- copy pasti strikes again (cc: window.lua) - local nStart = xCursor - local nEnd = nStart + #sText - 1 - if yCursor >= 1 and yCursor <= height then - if nStart <= width and nEnd >= 1 then - -- Modify line - if nStart == 1 and nEnd == width then - cacheT[yCursor] = sText - cacheFG[yCursor] = sTextColor - cacheBG[yCursor] = sBackgroundColor - else - local sClippedText, sClippedTextColor, sClippedBackgroundColor - if nStart < 1 then - local nClipStart = 1 - nStart + 1 - local nClipEnd = width - nStart + 1 - sClippedText = sub(sText, nClipStart, nClipEnd) - sClippedTextColor = sub(sTextColor, nClipStart, nClipEnd) - sClippedBackgroundColor = sub(sBackgroundColor, nClipStart, nClipEnd) - elseif nEnd > width then - local nClipEnd = width - nStart + 1 - sClippedText = sub(sText, 1, nClipEnd) - sClippedTextColor = sub(sTextColor, 1, nClipEnd) - sClippedBackgroundColor = sub(sBackgroundColor, 1, nClipEnd) - else - sClippedText = sText - sClippedTextColor = sTextColor - sClippedBackgroundColor = sBackgroundColor - end - - local sOldText = cacheT[yCursor] - local sOldTextColor = cacheFG[yCursor] - local sOldBackgroundColor = cacheBG[yCursor] - local sNewText, sNewTextColor, sNewBackgroundColor - if nStart > 1 then - local nOldEnd = nStart - 1 - sNewText = sub(sOldText, 1, nOldEnd) .. sClippedText - sNewTextColor = sub(sOldTextColor, 1, nOldEnd) .. sClippedTextColor - sNewBackgroundColor = sub(sOldBackgroundColor, 1, nOldEnd) .. sClippedBackgroundColor - else - sNewText = sClippedText - sNewTextColor = sClippedTextColor - sNewBackgroundColor = sClippedBackgroundColor - end - if nEnd < width then - local nOldStart = nEnd + 1 - sNewText = sNewText .. sub(sOldText, nOldStart, width) - sNewTextColor = sNewTextColor .. sub(sOldTextColor, nOldStart, width) - sNewBackgroundColor = sNewBackgroundColor .. sub(sOldBackgroundColor, nOldStart, width) - end - - cacheT[yCursor] = sNewText - cacheFG[yCursor] = sNewTextColor - cacheBG[yCursor] = sNewBackgroundColor - end - object:updateDraw() - end - xCursor = nEnd + 1 - if (visible) then - updateCursor() - end - end - end - - local function setText(_x, _y, text) - if (text ~= nil) then - local gText = cacheT[_y] - if (gText ~= nil) then - cacheT[_y] = sub(gText:sub(1, _x - 1) .. text .. gText:sub(_x + (text:len()), width), 1, width) - end - end - object:updateDraw() - end - - local function setBG(_x, _y, colorStr) - if (colorStr ~= nil) then - local gBG = cacheBG[_y] - if (gBG ~= nil) then - cacheBG[_y] = sub(gBG:sub(1, _x - 1) .. colorStr .. gBG:sub(_x + (colorStr:len()), width), 1, width) - end - end - object:updateDraw() - end - - local function setFG(_x, _y, colorStr) - if (colorStr ~= nil) then - local gFG = cacheFG[_y] - if (gFG ~= nil) then - cacheFG[_y] = sub(gFG:sub(1, _x - 1) .. colorStr .. gFG:sub(_x + (colorStr:len()), width), 1, width) - end - end - object:updateDraw() - end - - local setTextColor = function(color) - if type(color) ~= "number" then - error("bad argument #1 (expected number, got " .. type(color) .. ")", 2) - elseif tHex[color] == nil then - error("Invalid color (got " .. color .. ")", 2) - end - fgColor = color - end - - local setBackgroundColor = function(color) - if type(color) ~= "number" then - error("bad argument #1 (expected number, got " .. type(color) .. ")", 2) - elseif tHex[color] == nil then - error("Invalid color (got " .. color .. ")", 2) - end - bgColor = color - end - - local setPaletteColor = function(colour, r, g, b) - -- have to work on - if type(colour) ~= "number" then - error("bad argument #1 (expected number, got " .. type(colour) .. ")", 2) - end - - if tHex[colour] == nil then - error("Invalid color (got " .. colour .. ")", 2) - end - - local tCol - if type(r) == "number" and g == nil and b == nil then - tCol = { colours.rgb8(r) } - tPalette[colour] = tCol - else - if type(r) ~= "number" then - error("bad argument #2 (expected number, got " .. type(r) .. ")", 2) - end - if type(g) ~= "number" then - error("bad argument #3 (expected number, got " .. type(g) .. ")", 2) - end - if type(b) ~= "number" then - error("bad argument #4 (expected number, got " .. type(b) .. ")", 2) - end - - tCol = tPalette[colour] - tCol[1] = r - tCol[2] = g - tCol[3] = b - end - end - - local getPaletteColor = function(colour) - if type(colour) ~= "number" then - error("bad argument #1 (expected number, got " .. type(colour) .. ")", 2) - end - if tHex[colour] == nil then - error("Invalid color (got " .. colour .. ")", 2) - end - local tCol = tPalette[colour] - return tCol[1], tCol[2], tCol[3] - end - - local basaltwindow = { - setCursorPos = function(_x, _y) - if type(_x) ~= "number" then - error("bad argument #1 (expected number, got " .. type(_x) .. ")", 2) - end - if type(_y) ~= "number" then - error("bad argument #2 (expected number, got " .. type(_y) .. ")", 2) - end - xCursor = math.floor(_x) - yCursor = math.floor(_y) - if (visible) then - updateCursor() - end - end; - - getCursorPos = function() - return xCursor, yCursor - end; - - setCursorBlink = function(blink) - if type(blink) ~= "boolean" then - error("bad argument #1 (expected boolean, got " .. type(blink) .. ")", 2) - end - cursorBlink = blink - end; - - getCursorBlink = function() - return cursorBlink - end; - - - getPaletteColor = getPaletteColor, - getPaletteColour = getPaletteColor, - - setBackgroundColor = setBackgroundColor, - setBackgroundColour = setBackgroundColor, - - setTextColor = setTextColor, - setTextColour = setTextColor, - - setPaletteColor = setPaletteColor, - setPaletteColour = setPaletteColor, - - getBackgroundColor = function() - return bgColor - end; - getBackgroundColour = function() - return bgColor - end; - - getSize = function() - return width, height - end; - - getTextColor = function() - return fgColor - end; - getTextColour = function() - return fgColor - end; - - basalt_resize = function(_width, _height) - width, height = _width, _height - recreateWindowArray() - end; - - basalt_reposition = function(_x, _y) - x, y = _x, _y - end; - - basalt_setVisible = function(vis) - visible = vis - end; - - drawBackgroundBox = function(_x, _y, _width, _height, bgCol) - for n = 1, _height do - setBG(_x, _y + (n - 1), tHex[bgCol]:rep(_width)) - end - end; - drawForegroundBox = function(_x, _y, _width, _height, fgCol) - for n = 1, _height do - setFG(_x, _y + (n - 1), tHex[fgCol]:rep(_width)) - end - end; - drawTextBox = function(_x, _y, _width, _height, symbol) - for n = 1, _height do - setText(_x, _y + (n - 1), symbol:rep(_width)) - end - end; - - writeText = function(_x, _y, text, bgCol, fgCol) - bgCol = bgCol or bgColor - fgCol = fgCol or fgColor - setText(x, _y, text) - setBG(_x, _y, tHex[bgCol]:rep(text:len())) - setFG(_x, _y, tHex[fgCol]:rep(text:len())) - end; - - basalt_update = function() - if (parent ~= nil) then - for n = 1, height do - parent:setText(x, y + (n - 1), cacheT[n]) - parent:setBG(x, y + (n - 1), cacheBG[n]) - parent:setFG(x, y + (n - 1), cacheFG[n]) - end - end - end; - - scroll = function(offset) - if type(offset) ~= "number" then - error("bad argument #1 (expected number, got " .. type(offset) .. ")", 2) - end - if offset ~= 0 then - local sEmptyText = emptySpaceLine - local sEmptyTextColor = emptyColorLines[fgColor] - local sEmptyBackgroundColor = emptyColorLines[bgColor] - for newY = 1, height do - local y = newY + offset - if y >= 1 and y <= height then - cacheT[newY] = cacheT[y] - cacheBG[newY] = cacheBG[y] - cacheFG[newY] = cacheFG[y] - else - cacheT[newY] = sEmptyText - cacheFG[newY] = sEmptyTextColor - cacheBG[newY] = sEmptyBackgroundColor - end - end - end - if (visible) then - updateCursor() - end - end; - - - isColor = function() - return parent:getBasaltInstance().getBaseTerm().isColor() - end; - - isColour = function() - return parent:getBasaltInstance().getBaseTerm().isColor() - end; - - write = function(text) - text = tostring(text) - if (visible) then - internalBlit(text, tHex[fgColor]:rep(text:len()), tHex[bgColor]:rep(text:len())) - end - end; - - clearLine = function() - if (visible) then - setText(1, yCursor, (" "):rep(width)) - setBG(1, yCursor, tHex[bgColor]:rep(width)) - setFG(1, yCursor, tHex[fgColor]:rep(width)) - end - if (visible) then - updateCursor() - end - end; - - clear = function() - for n = 1, height do - setText(1, n, (" "):rep(width)) - setBG(1, n, tHex[bgColor]:rep(width)) - setFG(1, n, tHex[fgColor]:rep(width)) - end - if (visible) then - updateCursor() - end - end; - - blit = function(text, fgcol, bgcol) - if type(text) ~= "string" then - error("bad argument #1 (expected string, got " .. type(text) .. ")", 2) - end - if type(fgcol) ~= "string" then - error("bad argument #2 (expected string, got " .. type(fgcol) .. ")", 2) - end - if type(bgcol) ~= "string" then - error("bad argument #3 (expected string, got " .. type(bgcol) .. ")", 2) - end - if #fgcol ~= #text or #bgcol ~= #text then - error("Arguments must be the same length", 2) - end - if (visible) then - internalBlit(text, fgcol, bgcol) - end - end - - - } - - return basaltwindow + end) + elseif(type(path)=="function")then + newP.coroutine = coroutine.create(function() + path(table.unpack(args)) + end) + else + return end - - base.width = 30 - base.height = 12 - local pWindow = createBasaltWindow(1, 1, base.width, base.height) - local curProcess - local paused = false - local queuedEvent = {} - - local function updateCursor(self) - local xCur, yCur = pWindow.getCursorPos() - local obx, oby = self:getAnchorPosition() - local w,h = self:getSize() - if (obx + xCur - 1 >= 1 and obx + xCur - 1 <= obx + w - 1 and yCur + oby - 1 >= 1 and yCur + oby - 1 <= oby + h - 1) then - self.parent:setCursor(self:isFocused() and pWindow.getCursorBlink(), obx + xCur - 1, yCur + oby - 1, pWindow.getTextColor()) - end - end - - local function resumeProcess(self, event, ...) - local ok, result = curProcess:resume(event, ...) - if (ok==false)and(result~=nil)and(result~="Terminated")then - local val = self:sendEvent("program_error", result) - if(val~=false)then - error("Basalt Program - "..result) - end - end - - if(curProcess:getStatus()=="dead")then - self:sendEvent("program_done") - end - end - - local function mouseEvent(self, event, p1, x, y) - if (curProcess == nil) then - return false - end - if not (curProcess:isDead()) then - if not (paused) then - local absX, absY = self:getAbsolutePosition(self:getAnchorPosition(nil, nil, true)) - resumeProcess(self, event, p1, x-absX+1, y-absY+1) - updateCursor(self) - end - end - end - - local function keyEvent(self, event, key, isHolding) - if (curProcess == nil) then - return false - end - if not (curProcess:isDead()) then - if not (paused) then - if (self.draw) then - resumeProcess(self, event, key, isHolding) - updateCursor(self) - end - end - end - end - - object = { - getType = function(self) - return objectType - end; - - show = function(self) - base.show(self) - pWindow.setBackgroundColor(self.bgColor) - pWindow.setTextColor(self.fgColor) - pWindow.basalt_setVisible(true) - return self - end; - - hide = function(self) - base.hide(self) - pWindow.basalt_setVisible(false) - return self - end; - - setPosition = function(self, x, y, rel) - base.setPosition(self, x, y, rel) - pWindow.basalt_reposition(self:getAnchorPosition()) - return self - end; - - setValuesByXMLData = function(self, data) - base.setValuesByXMLData(self, data) - if(xmlValue("path", data)~=nil)then cachedPath = xmlValue("path", data) end - if(xmlValue("execute", data)~=nil)then if(xmlValue("execute", data))then if(cachedPath~=nil)then self:execute(cachedPath) end end end - end, - - getBasaltWindow = function() - return pWindow - end; - - getBasaltProcess = function() - return curProcess - end; - - setSize = function(self, width, height, rel) - base.setSize(self, width, height, rel) - pWindow.basalt_resize(self:getWidth(), self:getHeight()) - return self - end; - - getStatus = function(self) - if (curProcess ~= nil) then - return curProcess:getStatus() - end - return "inactive" - end; - - setEnviroment = function(self, env) - enviroment = env or {} - return self - end, - - execute = function(self, path, ...) - cachedPath = path or cachedPath - curProcess = process:new(cachedPath, pWindow, enviroment, ...) - pWindow.setBackgroundColor(colors.black) - pWindow.setTextColor(colors.white) - pWindow.clear() - pWindow.setCursorPos(1, 1) - pWindow.setBackgroundColor(self.bgColor) - pWindow.setTextColor(self.fgColor) - pWindow.basalt_setVisible(true) - - resumeProcess(self) - paused = false - if(self.parent~=nil)then - self.parent:addEvent("mouse_click", self) - self.parent:addEvent("mouse_up", self) - self.parent:addEvent("mouse_drag", self) - self.parent:addEvent("mouse_scroll", self) - self.parent:addEvent("key", self) - self.parent:addEvent("key_up", self) - self.parent:addEvent("char", self) - self.parent:addEvent("other_event", self) - end - return self - end; - - stop = function(self) - if (curProcess ~= nil) then - if not (curProcess:isDead()) then - resumeProcess(self, "terminate") - if (curProcess:isDead()) then - if (self.parent ~= nil) then - self.parent:setCursor(false) - end - end - end - end - self.parent:removeEvents(self) - return self - end; - - pause = function(self, p) - paused = p or (not paused) - if (curProcess ~= nil) then - if not (curProcess:isDead()) then - if not (paused) then - self:injectEvents(queuedEvent) - queuedEvent = {} - end - end - end - return self - end; - - isPaused = function(self) - return paused - end; - - injectEvent = function(self, event, p1, p2, p3, p4, ign) - if (curProcess ~= nil) then - if not (curProcess:isDead()) then - if (paused == false) or (ign) then - resumeProcess(self, event, p1, p2, p3, p4) - else - table.insert(queuedEvent, { event = event, args = { p1, p2, p3, p4 } }) - end - end - end - return self - end; - - getQueuedEvents = function(self) - return queuedEvent - end; - - updateQueuedEvents = function(self, events) - queuedEvent = events or queuedEvent - return self - end; - - injectEvents = function(self, events) - if (curProcess ~= nil) then - if not (curProcess:isDead()) then - for _, value in pairs(events) do - resumeProcess(self, value.event, table.unpack(value.args)) - end - end - end - return self - end; - - mouseHandler = function(self, button, x, y) - if (base.mouseHandler(self, button, x, y)) then - mouseEvent(self, "mouse_click", button, x, y) - return true - end - return false - end, - - mouseUpHandler = function(self, button, x, y) - if (base.mouseUpHandler(self, button, x, y)) then - mouseEvent(self, "mouse_up", button, x, y) - return true - end - return false - end, - - scrollHandler = function(self, dir, x, y) - if (base.scrollHandler(self, dir, x, y)) then - mouseEvent(self, "mouse_scroll", dir, x, y) - return true - end - return false - end, - - dragHandler = function(self, button, x, y) - if (base.dragHandler(self, button, x, y)) then - mouseEvent(self, "mouse_drag", button, x, y) - return true - end - return false - end, - - keyHandler = function(self, key, isHolding) - if(base.keyHandler(self, key, isHolding))then - keyEvent(self, "key", key, isHolding) - return true - end - return false - end, - - keyUpHandler = function(self, key) - if(base.keyUpHandler(self, key))then - keyEvent(self, "key_up", key) - return true - end - return false - end, - - charHandler = function(self, char) - if(base.charHandler(self, char))then - keyEvent(self, "char", char) - return true - end - return false - end, - - getFocusHandler = function(self) - base.getFocusHandler(self) - if (curProcess ~= nil) then - if not (curProcess:isDead()) then - if not (paused) then - if (self.parent ~= nil) then - local xCur, yCur = pWindow.getCursorPos() - local obx, oby = self:getAnchorPosition() - local w,h = self:getSize() - if (obx + xCur - 1 >= 1 and obx + xCur - 1 <= obx + w - 1 and yCur + oby - 1 >= 1 and yCur + oby - 1 <= oby + h - 1) then - self.parent:setCursor(pWindow.getCursorBlink(), obx + xCur - 1, yCur + oby - 1, pWindow.getTextColor()) - end - end - end - end - end - end, - - loseFocusHandler = function(self) - base.loseFocusHandler(self) - if (curProcess ~= nil) then - if not (curProcess:isDead()) then - if (self.parent ~= nil) then - self.parent:setCursor(false) - end - end - end - end, - - customEventHandler = function(self, event, ...) - base.customEventHandler(self, event, ...) - if (curProcess == nil) then - return - end - if(event=="basalt_resize")then - local w, h = pWindow.getSize() - local pW, pH = self:getSize() - if(w~=pW)or(h~=pH)then - pWindow.basalt_resize(pW, pH) - if not (curProcess:isDead()) then - resumeProcess(self, "term_resize") - end - end - pWindow.basalt_reposition(self:getAnchorPosition()) - - end - end, - - eventHandler = function(self, event, p1, p2, p3, p4) - base.eventHandler(self, event, p1, p2, p3, p4) - if (curProcess == nil) then - return - end - if not (curProcess:isDead()) then - if not (paused) then - if(event ~= "terminate") then - resumeProcess(self, event, p1, p2, p3, p4) - end - if (self:isFocused()) then - local obx, oby = self:getAnchorPosition() - local xCur, yCur = pWindow.getCursorPos() - if (self.parent ~= nil) then - local w,h = self:getSize() - if (obx + xCur - 1 >= 1 and obx + xCur - 1 <= obx + w - 1 and yCur + oby - 1 >= 1 and yCur + oby - 1 <= oby + h - 1) then - self.parent:setCursor(pWindow.getCursorBlink(), obx + xCur - 1, yCur + oby - 1, pWindow.getTextColor()) - end - end - - if (event == "terminate") then - resumeProcess(self, event) - self.parent:setCursor(false) - return true - end - end - else - table.insert(queuedEvent, { event = event, args = { p1, p2, p3, p4 } }) - end - end - end, - - draw = function(self) - if (base.draw(self)) then - if (self.parent ~= nil) then - local obx, oby = self:getAnchorPosition() - local xCur, yCur = pWindow.getCursorPos() - local w,h = self:getSize() - pWindow.basalt_reposition(obx, oby) - pWindow.basalt_update() - if (obx + xCur - 1 >= 1 and obx + xCur - 1 <= obx + w - 1 and yCur + oby - 1 >= 1 and yCur + oby - 1 <= oby + h - 1) then - self.parent:setCursor(self:isFocused() and pWindow.getCursorBlink(), obx + xCur - 1, yCur + oby - 1, pWindow.getTextColor()) - end - end - end - end, - - onError = function(self, ...) - for _,v in pairs(table.pack(...))do - if(type(v)=="function")then - self:registerEvent("program_error", v) - end - end - if(self.parent~=nil)then - self.parent:addEvent("other_event", self) - end - return self - end, - - onDone = function(self, ...) - for _,v in pairs(table.pack(...))do - if(type(v)=="function")then - self:registerEvent("program_done", v) - end - end - if(self.parent~=nil)then - self.parent:addEvent("other_event", self) - end - return self - end, - - init = function(self) - if(base.init(self))then - self.bgColor = self.parent:getTheme("ProgramBG") - end - end, - - } - - return setmetatable(object, base) + processes[processId] = newP + processId = processId + 1 + return newP end + +function process:resume(event, ...) + local cur = term.current() + term.redirect(self.window) + if(self.filter~=nil)then + if(event~=self.filter)then return end + self.filter=nil + end + local ok, result = coroutine.resume(self.coroutine, event, ...) + + if ok then + self.filter = result + else + printError(result) + end + term.redirect(cur) + return ok, result +end + +function process:isDead() + if (self.coroutine ~= nil) then + if (coroutine.status(self.coroutine) == "dead") then + table.remove(processes, self.processId) + return true + end + else + return true + end + return false +end + +function process:getStatus() + if (self.coroutine ~= nil) then + return coroutine.status(self.coroutine) + end + return nil +end + +function process:start() + coroutine.resume(self.coroutine) +end + +return process