This is a read-only snapshot of the ComputerCraft forums, taken in April 2020.
Creator's profile picture

Multitasking kernel draws windows at wrong moments

Started by Creator, 20 March 2015 - 03:40 PM
Creator #1
Posted 20 March 2015 - 04:40 PM
Hi guys,

currently I am working on the kernel of my OS. The problem I am facing is the following: whenever I change the process, the previous window stays there. However, if I change again, the window that was supposed to be draw before is present.


Here is the kernel code

Spoiler


--[[
TheOS Kernel
by Creator
for OmniOS &
you to learn
from it! (Yeah learn, rather become dumber)
theoriginalbit
also gets credit for
his safePairs function.
Lots of thanks to BombBloke too for
finalizing and bug fixing it.
]]--


--[[::VARIABLES::]]--
local logMessage = ""
local newRoutine = ""
local history = {}
local w,h = term.getSize()
local monitors = {}
local currTerm, routines, activeRoutine, eventBuffer = term.current(), {}, "", {}
local eventFilter = {["key"] = true, ["mouse_click"] = true, ["paste"] = true,["char"] = true, ["terminate"] = true, ["mouse_scroll"] = true, ["mouse_drag"] = true}

local function safePairs(t)
  local keys = {}
  for k,v in pairs(t) do
keys[#keys+1] = k
  end
  local i = 0
  return function()
i = i + 1
return keys[i], t[keys[i]]
  end
end

local function drawClosed()
paintutils.drawLine(w,1,w,h,colors.black)
term.setTextColor(colors.white)
term.setBackgroundColor(colors.black)
term.setCursorPos(w,h/2+2)
term.write("<")
end

--[[local function switch()
if routines[newRoutine] and not newRoutine == "" then
routines[activeRoutine].window.setVisible( false )
activeRoutine = newRoutine
routines[activeRoutine].window.setVisible(true)
newRoutine = ""
end
end]]--

local function drawOpen()
term.setCursorBlink(false)
paintutils.drawFilledBox(w-15,1,w,h,colors.black)
local xVsProcess, curr = {}, 1
term.setTextColor(colors.white)
for i,v in pairs(routines) do
paintutils.drawLine(w-15,curr*2,w,curr*2,i == activeRoutine and colours.blue or colours.black)
paintutils.drawLine(w-15,curr*2+1,w,curr*2+1,i == activeRoutine and colours.blue or colours.black)
term.setBackgroundColour(i == activeRoutine and colours.blue or colours.black)
term.setCursorPos(w-14,curr*2)
term.write("x "..v.title)
term.setCursorPos(w-12,curr*2+1)
term.write(coroutine.status(v.routine))
xVsProcess[curr*2] = i
curr = curr + 1
end

while true do
local evnt = {os.pullEventRaw()}
log.log("Close",xVsProcess[evnt[4]])
if evnt[1] == "mouse_click" then
if w-12 <= evnt[3] and evnt[3] <= w then
if xVsProcess[evnt[4]] then
routines[activeRoutine].window.setVisible(false)
--term.redirect(currTerm)
log.log("Multitask","Current: "..activeRoutine.." New: "..xVsProcess[evnt[4]])
activeRoutine = xVsProcess[evnt[4]]
term.redirect(routines[activeRoutine].window)
routines[activeRoutine].window.setVisible(true)
routines[activeRoutine].window.redraw()
term.redirect(currTerm)
return
end
elseif w-14 == evnt[3] then
if xVsProcess[evnt[4]] and xVsProcess[evnt[4]] ~= "Debug1" then
if activeRoutine == xVsProcess[evnt[4]] then
activeRoutine = "Debug1"
routines[activeRoutine].window.setVisible(true)
end
routines[xVsProcess[evnt[4]]] = nil
return
end
else return end
else eventBuffer[#eventBuffer+1] = evnt end
end
end

local function checkIfDead(routine)
if coroutine.status(routines[routine].routine) == "dead" then
routines[routine] = nil
if routine == activeRoutine then
activeRoutine = "Debug1"
routines[activeRoutine].window.setVisible(true)
end
return true
else return false end
end

Kernel = {}

function Kernel.newRoutine(name,title,func,permission,tParent,...)
if tParent == term or tParent == term.current() or tParent == "term" then
tParent = term.current()
else
if monitors[tParent] then
log.log("Processes/Mon","A program is using the monitor!")
return "A program is using the monitor!"
end
if not peripheral.isPresent(tParent) then
log.log("Processes/Mon","The monitor does not exist!")
return "The monitor does not exist!"
end
if not peripheral.getType(tParent) == "monitor" then
log.log("Processes/Mon","The peripheral is not a monitor!")
return "The peripheral is not a monitor!"
end
monitors[tParent] = true
end
log.log("System","Launching task "..name..". By kernel")
local oldName = name
do
local tries = 1
while routines[name .. tries] do tries = tries + 1 end
name = name .. tries
end
if permission == "userTest" then
log.log("Env","Launching task "..name..": adding environment. By kernel")
local env = Sandbox.newEnv(oldName)
log.log("Env","Environment was successful")
setfenv(func,env)
end

routines[name] = {
["title"] = title,
["permission"] = permission,
["path"] = path,
["routine"] = coroutine.create(func),
["window"] = (tParent == term or tParent == term.current()) and OmniWindow.create(tParent,1,1,w-1,h,true) or OmniWindow.create(peripheral.wrap(tParent),1,1,w-1,h,true),
["filter"] = "",
["parent"] = (tParent == term or tParent == term.current()) and "term" or "mon",
}

--Run it!
if routines[activeRoutine] then
routines[activeRoutine].window.setVisible(false)
term.redirect(currTerm)
term.redirect(routines[name].window)
routines[name].window.setVisible(false)
logMessage, routines[name].filter = coroutine.resume(routines[name].routine,...)
if not logMessage then
log.log(name,"Error: "..tostring(routines[name].filter),name)
end
term.redirect(currTerm)
checkIfDead(name,"term")
term.redirect(routines[activeRoutine].window)
routines[activeRoutine].window.setVisible(true)
routines[activeRoutine].window.redraw()
else
activeRoutine = name
term.redirect(routines[activeRoutine].window)
routines[activeRoutine].window.setVisible(true)
logMessage, routines[activeRoutine].filter = coroutine.resume(routines[activeRoutine].routine,...)
if not logMessage then
log.log(activeRoutine,"Error: "..tostring(routines[activeRoutine].filter),activeRoutine)
end
term.redirect(currTerm)
checkIfDead(activeRoutine,"term")
end
end

function Kernel.getPermission(program)
return routines[program].permission or "Not a valid program"
end

function Kernel.kill(thread)
if thread == "Debug1" then
return "Cannot kill debug, instead use kill !zygote"
else
if thread == activeRoutine then
activeRoutine = "Debug1"
routines[activeRoutine].window.setVisible(true)
end
routines[thread] = nil
end
end

function Kernel.getScreen(routine)
return routines[routine] and routines[routine].window.getBuffer() or false
end

function Kernel.list()
local buffer = {}
for i,v in pairs(routines) do
buffer[#buffer+1] = i
end
return buffer
end

function Kernel.switch(newTask)
newRoutine = newTask
end

drawClosed()

Kernel.newRoutine(...)

while true do
--switch()
local event = #eventBuffer == 0 and {os.pullEventRaw()} or table.remove(eventBuffer,1)
if fs.exists("OmniOS/Drivers/"..event[1]..".lua") then
local func = loadfile("OmniOS/Drivers/"..event[1]..".lua")
func(unpack(event))
routines[activeRoutine].window.redraw()
else
if (event[1] == "mouse_click" or event[1] == "monitor_touch") and event[3] == w then
drawOpen()
drawClosed()
routines[activeRoutine].window.redraw()
elseif eventFilter[event[1]] then
if routines[activeRoutine].filter == nil or routines[activeRoutine].filter == "" or routines[activeRoutine].filter == event[1] then
term.redirect(routines[activeRoutine].window)
logMessage, routines[activeRoutine].filter = coroutine.resume(routines[activeRoutine].routine,unpack(event))
if not logMessage then
log.log(activeRoutine,"Error: "..tostring(routines[activeRoutine].filter),activeRoutine)
end
term.redirect(currTerm)
checkIfDead(activeRoutine,"term")
end
else
for i,v in safePairs(routines) do
if routines[i] and routines[i].filter == event[1] or routines[i].filter == nil then
term.redirect(routines[i].window)
logMessage, routines[i].filter = coroutine.resume(routines[i].routine,unpack(event))
if not logMessage then
log.log(i,"Error: "..tostring(routines[i].filter),i)
end
term.redirect(currTerm)
checkIfDead(i,"term")
end
end
end
end
end


You can see the code of the whole OS here.

Thanks in advance,

~Creator


PS: For convenience, here is a link to the code in GitHub. It's easier to read!
Edited on 14 May 2015 - 05:24 PM
Creator #2
Posted 29 March 2015 - 12:12 AM
Hi guys,

I am writing an OS and currently I am having some trouble with the kernel. The Issu is the following:

When I load the kernel with the first program (Desktop), there is no problem. However, the Desktop can initialize other processes. When I do start another thread, it does the following:

- Desktop thread dies (the routines.Desktop1 is being set to nil, I don't know how this magic happens, but I don't want this.)
Note: Currently the first app is FileX. It is only temporary and for debugging purposes. You can start a new thread by right clicking on a file and chosing Open with >> Edit.
- New thread does not show up.

Here is the kernel code: (The exterior programs are OK, don't worry about them.)

Kernel

--[[
TheOS Kernel
by Creator
for TheOS &amp;
you to learn
from it! (Yeah learn, rather become dumber)
theoriginalbit
also gets credit for
his safePairs function.
]]--
local args = {...}
local w,h = term.getSize()
local currTerm = term.current()
local routines = {}
local routinesToKill = {}
local activeRoutine = ""
local eventGlobal = true
local halfH = math.floor(h/2 +2)
local first = true
local function drawClosed()
paintutils.drawLine(w,1,w,h,colors.black)
term.setTextColor(colors.white)
term.setBackgroundColor(colors.black)
term.setCursorPos(w,halfH)
term.write("<")
end
local function safePairs(t)
  local keys = {}
  for k,v in pairs(t) do
	keys[#keys+1] = k
  end
  local i = 0
  return function()
	i = i + 1
	return keys[i], t[keys[i]]
  end
end
local function drawOpen()
paintutils.drawFilledBox(w-15,1,w,h,colors.black)
local xVsProcess = {}
local curr = 1
term.setTextColor(colors.white)
for i,v in pairs(routines) do
  term.setCursorPos(w-14,(curr*2))
  term.write("x "..v.title)
  term.setCursorPos(w-12,(curr*2)+1)
  term.write(coroutine.status(v.routine))
  xVsProcess[curr*2] = v.ID
  curr = curr + 1
end
local notH = true
local switchRoutine = false
local toDelete = false
while notH do
  evnt = {os.pullEventRaw("mouse_click")}
  if w-12 <= evnt[3] and evnt[3] <= w then
   print(event[3])
   if xVsProcess[evnt[4]] ~= nil then
	notH = false
	switchRoutine = true
   end
  elseif w-14 == evnt[3] then
   term.setCursorPos(evnt[3],evnt[4])
   print("yes")
   if xVsProcess[evnt[4]] ~= nil then
	print("himom")
	notH = false
	toDelete = true
   end
   os.pullEventRaw()
  else
   notH = false
  end
end
print(toDelete)
if switchRoutine then
  print(xVsProcess[evnt[4]])
  routines[activeRoutine].window.setVisible( false )
  print(activeRoutine)
  activeRoutine = xVsProcess[evnt[4]]
  routines[activeRoutine].window.setVisible(true)
elseif toDelete then
  if activeRoutine == xVsProcess[evnt[4]] then print("Changing "..xVsProcess[evnt[4]]) activeRoutine = "Desktop1" end
  if not xVsProcess[evnt[4]] == "Desktop1" then
   routines[xVsProcess[evnt[4]]] = nil
  end
  term.redirect(routines[activeRoutine].window)
  routines[activeRoutine].window.setVisible(true)
end
end
function newRoutine(name,title,func,permission,...)
name = name.."1"
if not routines[name] then
  local notUnique = true
  local tries = 1
  while notUnique do
   tries = tries + 1
   if routines[name] ~= nil then
	name = name:sub(1,-2)..tostring(tries)
   else
	notUnique = false
   end
  end
  local arguments = {...}
  routines[name] = {}
  routines[name].routine = coroutine.create(func)
  if first then
   routines[name].window = window.create(term.current(),1,1,w-1,h,true)
   activeRoutine = name
   first = false
  else
   routines[name].window = window.create(term.current(),1,1,w-1,h,false)
  end
  routines[name].title = title
  routines[name].ID = name
  routines[name].permission = permission
  routines[name].path = path
  --Run it!
  routines[activeRoutine].window.setVisible(false)
  activeRoutine = name
  term.redirect(routines[activeRoutine].window)
  routines[activeRoutine].window.setVisible(true)
  coroutine.resume(routines[activeRoutine].routine,unpack(arguments))
  term.redirect(currTerm)
end
end
local function checkIfDead(routine)
local wasDead = false
status = coroutine.status(routines[routine].routine)
print(routine.."."..status)
if status == "dead" then
  wasDead = true
  routines[activeRoutine].window.setVisible(false)
  routines[routine] = nil
  if routine == activeRoutine then activeRoutine = "Desktop1" end
  for i,v in pairs(routines) do print(i) end
  print("cow")
  os.pullEventRaw()
  routines[activeRoutine].window.setVisible(true)
end
return wasDead
end
local function main()
while true do
  routinesToKill = {}
  event = {os.pullEventRaw()}
  if (event[1] == "mouse_click" or event[1] == "monitor_touch") and event[3] == w then
   drawOpen()
   term.setBackgroundColor(colors.black)
   term.clear()
   drawClosed()
   routines[activeRoutine].window.redraw()
  elseif event[1] == "key" or event[1] == "mouse_click" or event[1] == "monitor_touch" or event[1] == "paste" or event[1] == "char" then
   checkIfDead(activeRoutine)
   term.redirect(routines[activeRoutine].window)
   routines[activeRoutine].window.redraw()
   coroutine.resume(routines[activeRoutine].routine,unpack(event))
   term.redirect(currTerm)
  else
   for i,v in safePairs(routines) do
	if not checkIfDead(i) then
	 term.redirect(routines[i].window)
	 coroutine.resume(routines[i].routine,unpack(event))
	 term.redirect(currTerm)
	end
   end
  end
end
end

drawClosed()
newRoutine(unpack(args))
main()

Sorry for the indentation, if you want to see some good indentation, visit this site.

Of course you can download the current state of TheOS by writing this into the shell:


pastebin run 2DMDuHci TheOS

TheOS/Boot.lua

PS: There are a lot of debugging prints.
Edited on 28 March 2015 - 11:14 PM
Bomb Bloke #3
Posted 30 March 2015 - 03:43 PM
Your installation script is busted. Just for giggles, here's an alternative:

pastebin run cUYTGbpb get pKeHUKEs TheOS

In your "kernel", on line 111 in newRoutine(), you're setting the active routine to be the one you're creating - then on 121, you're trying to halt rendering on the previously active routine, forgetting that you've already discarded the pointer to it. You really want to remove 111 entirely, then set 121 to something like:

if routines[activeRoutine] then routines[activeRoutine].window.setVisible(false) end

This is important, because if you fail to make the old window invisible then it won't automatically redraw when you go to make it visible again later (because it's already visible, so the window API doesn't see the point).

On line 157, don't forget terminate, mouse_scroll, and mouse_drag. You would probably be better off making a table, eg filterEvent = {["terminate"] = true, ["key"] = true, etc, then testing if filterEvent[event[1]].

It looks like you forgot to comment out line 141.

Line 85 checks if an inverted xVsProcess[evnt[4]] (which will always be equal to the boolean value false!) is equal to "Desktop1". That'll never be the case. You meant to use ~=.

Your use of an ID key is a bit useless, given that routine[whatever].ID == whatever. For example, on line 52 you could just set xVsProcess[curr*2] to i.

You don't want to check if the active coroutine is dead immediately before you attempt to pass an event to it. If the coroutine IS dead then the event will end up being passed to the desktop instead, which might not be what the user expects. Dead coroutines should be cleaned as soon as they die.

In the end I decided to condense the whole thing down to make it more readible (to me, at least). In the process I added an event buffering system to preserve those your side-menu would otherwise consume (as I was talking about here), stripped out the bugs mentioned above, and likely dealt with a few others as well.
Creator #4
Posted 30 March 2015 - 07:11 PM
Wow, thank you. think I am beggining to understand it. With you modifications, it is almost flawwless. Thank you.

I am modifying a little bit your modification. You get a +1.
flaghacker #5
Posted 14 May 2015 - 07:12 PM
Github link doesn't work.
Creator #6
Posted 14 May 2015 - 07:23 PM
Sorry, I just updated it.
Creator #7
Posted 14 May 2015 - 08:59 PM
Can anyone help me? I have looked ove the code 20 time and it still does not work.
flaghacker #8
Posted 14 May 2015 - 09:21 PM
Debugging some else's OS is a huge pain. Have you tried putting in some debug log lines? "looking over it" is not enough. And just putting a huge pile of code here and asking "somewhere in here there is some problem that causes drawing issues" seems a bit… 'lazy' to me.

Edit: Just my two cents, I'm sorry if I'm rude.
Edited on 14 May 2015 - 07:24 PM
Creator #9
Posted 14 May 2015 - 09:30 PM
I amnot asking to get the OS debugged. I just want someone to see the fail in my logic in these 100 - 200 lines because I don't find it.

Bomb Bloke did it once, very nice of him.
Lyqyd #10
Posted 14 May 2015 - 11:05 PM
It is a bigger ask than we usually get. As the author, you're already more familiar with it. The thing most of us would be doing would be to use logging to narrow down where the problem occurs, and you've got a head start on us there, since you already know the best places to log! Can you try to narrow the search area down some? It will be much easier for us to help if you can slice it down further.
Bomb Bloke #11
Posted 15 May 2015 - 01:24 AM
I remember addressing the described bug with a rewrite in a later thread - which I wrote because it was easier than typing out the full list of bugs you'd've needed to address to get it to work properly yourself - so why's this ~2 month old thread getting bumped?
Lyqyd #12
Posted 15 May 2015 - 02:23 AM
Threads merged.
Creator #13
Posted 15 May 2015 - 07:51 AM
The kernel is not crashing. So logging won't help. And actually I am logging everything happening. The problem is that the windows don't get drawn at the right moment.
flaghacker #14
Posted 15 May 2015 - 08:38 AM
The kernel is not crashing. So logging won't help. And actually I am logging everything happening. The problem is that the windows don't get drawn at the right moment.

Logging can definitely help tracking diwn the issue. Log some variables in your drawing code, and try to find where they are wrong.
Creator #15
Posted 15 May 2015 - 09:26 AM
I am logging, and by looking over the code, everything seems nice. I can send you the latest version and maybe you can take a look, including logging.
Bomb Bloke #16
Posted 15 May 2015 - 11:15 AM
… gee, now it's much more clear as to what's going on…

Ah well. Skimming through the current source of kernel.lua, it seems a lot of redundancies I thought I got you to remove are back… Or something…

Lines 167 stands out to me as being wrong. It seems to me that you want the top-level terminal active whenever you're not specifically resuming or redrawing a coroutine's window. So I guess take out 157-158 and 167-169; also scrap 80, 82 and 83 - there's no need to redirect to a window if all you want to do is redraw it.

Likewise, remember - there's no point in calling window.redraw() immediately after window.setVisible(true). Per the relevant source:

    function window.setVisible( bVis )
	    if bVisible ~= bVis then
		    bVisible = bVis
		    if bVisible then	    -- If we're changing to visible, then
			    window.redraw()  -- ... automatically redraw!
		    end
	    end
    end

Edit:

You'll probably want to tweak 150 to create new windows as invisible, too.

The rules:

1) Non-active windows must not be "visible" at any time.

2) If a window doesn't auto-redraw when you make it visible (… which you must do when making it active), then you haven't followed rule 1 (or it was already active, and hence already visible).
Edited on 15 May 2015 - 09:49 AM
Creator #17
Posted 16 May 2015 - 12:32 AM
I just pushed a bugfixed version to github. Thank you a lot BombBloke.
It still does not work, but I understand th problem.
Bomb Bloke #18
Posted 16 May 2015 - 06:44 AM
That's still not quite right - again, all non-active windows must not be visible, but the active window should be made visible at the time you make it active. And you leave it visible until such time as another window becomes active.

So:

It is correct to make a window visible around line 164 (when creating a new process when no other processes are active, so the new process is therefore active, and therefore gets a visible window).

It is correct to make a window visible around lines 74-90, if that window belongs to a newly active process.

It is correct to make a window invisible when you create it for an inactive process (line 145), or when its process is losing its active status (lines 74-90).

At no other times should you be toggling window visibility on or off. Active process == visible, inactive process == invisible. That's it.

Likewise, it is correct to manually call the active window's redraw function immediately after drawing over the top of it by way of currTerm (which you do at 217 and 222). At no other time should you manually be calling the redraw functions for any windows - the one exception is if you decide to handle term/moniter_resize events, in which you'd again only redraw the active process for that display.
Edited on 16 May 2015 - 04:48 AM
Creator #19
Posted 16 May 2015 - 10:16 PM
BombBloke, thank you again. You are really helpfull. ;)/>