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

Multitasking

Started by DannySMc, 02 March 2015 - 02:31 PM
DannySMc #1
Posted 02 March 2015 - 03:31 PM
Hi guys,

I have to ask and this is a very vague question,

How do you implement multitasking into an OS? I have tried many times, but can't seem to get my head around how it would work,

Any help would be awesome thanks!

- Danny -
Lignum #2
Posted 02 March 2015 - 03:38 PM
Use coroutines.
Lyqyd #3
Posted 02 March 2015 - 04:16 PM
You'd create a coroutine manager. Programs would be started as coroutines and the distribution of events to them is handled by your coroutine manager. You only give the player input events to whatever coroutine is currently in focus. If you have questions about specific parts of the general structure, feel free to ask!
DannySMc #4
Posted 02 March 2015 - 05:06 PM
Thanks a lot for all your help guys :D/>
You'd create a coroutine manager. Programs would be started as coroutines and the distribution of events to them is handled by your coroutine manager. You only give the player input events to whatever coroutine is currently in focus. If you have questions about specific parts of the general structure, feel free to ask!

Okay, so I wanted to make a new OS as a small test, as I looked at building on my old one, but it would be a lot more work, so instead I am gonna start again.

So here are my questions:
1. How would I create a 'coroutine manager'?
2. How do I redraw the new coroutine every time? Also if I draw on one and then want to change to a different task how does a coroutine redraw everything? and store the old one? Window API?
3. I think I am also talking about buffers too?
4. This was a little too much to wrap my head around… I think I am asking for some specific details… As I don't understand how Lua can even do this also…

Thanks a lot for all your help guys :D/>
KingofGamesYami #5
Posted 02 March 2015 - 06:12 PM
1. Read the link Lignum posted. It is a very good tutorial on how coroutines work

2. You don't "redraw" the coroutine. The coroutine will draw itself, but your manager will probably assign a window to each routine, which will be redirected to while that routine is running.

3. Yes, buffers are helpful.

4. Simple Explanation of coroutines:

You have a program. The program runs another program until that program yields, and saves the result to use as a filter for events.

This is already using a coroutine, however it is not using it in any useful way. Now imagine we have two coroutines. Coroutine A and Coroutine B. You first run A until it yields, then run B until it yields, and repeat. If a coroutine's status becomes "dead" at any point, you'll want to remove it.

Really, just read the tutorial. It'll do a much better job explaining than me.
DannySMc #6
Posted 02 March 2015 - 10:27 PM
1. Read the link Lignum posted. It is a very good tutorial on how coroutines work

2. You don't "redraw" the coroutine. The coroutine will draw itself, but your manager will probably assign a window to each routine, which will be redirected to while that routine is running.

3. Yes, buffers are helpful.

4. Simple Explanation of coroutines:

You have a program. The program runs another program until that program yields, and saves the result to use as a filter for events.

This is already using a coroutine, however it is not using it in any useful way. Now imagine we have two coroutines. Coroutine A and Coroutine B. You first run A until it yields, then run B until it yields, and repeat. If a coroutine's status becomes "dead" at any point, you'll want to remove it.

Really, just read the tutorial. It'll do a much better job explaining than me.

I kind of get it..
So the coroutine will redraw itself when I resume it?

Also could i use normal apis with it, like redirect to it and then draw? or do I have to do the window.write() etc…?

Will have a look thanks!
Bomb Bloke #7
Posted 02 March 2015 - 10:45 PM
Your coroutine will boil down to a function that you're running alongside other functions. Whenever it yields, you might grant some other coroutines some run-time before you allow it to resume.

Whether or not that coroutine draws anything entirely depends on the function you built it out of. Resuming a coroutine doesn't automatically redraw everything that function had drawn in the past. The functions for dealing with coroutines have absolutely no control over your display.

If you want to run multiple scripts at the same time via coroutines, and are worried that they might draw over each other's output, that's when you'd rig up a display buffering scheme - redirect each script to a separate buffer object, then manually trigger a redraw of that before resuming the associated coroutine. This is exactly how multishell operates, using the window API to handle its display buffering.
DannySMc #8
Posted 02 March 2015 - 10:57 PM
Your coroutine will boil down to a function that you're running alongside other functions. Whenever it yields, you might grant some other coroutines some run-time before you allow it to resume.

Whether or not that coroutine draws anything entirely depends on the function you built it out of. Resuming a coroutine doesn't automatically redraw everything that function had drawn in the past. The functions for dealing with coroutines have absolutely no control over your display.

If you want to run multiple scripts at the same time via coroutines, and are worried that they might draw over each other's output, that's when you'd rig up a display buffering scheme - redirect each script to a separate buffer object, then manually trigger a redraw of that before resuming the associated coroutine. This is exactly how multishell operates, using the window API to handle its display buffering.

Ahh okay, so how exactly do I index a coroutine? As in if I want to make a tab based multitasking system, how am I able to index it into a table? like:

local task1 = coroutine.create(afunction())
local task2 = coroutine.create(anotherf())
table.insert(taskstable, task1)
table.insert(taskstable, task2)

Then run it like:

coroutine.resume(taskstable[1])

EDIT:
Also how would I open a file and run the contents of that program in a coroutine?
Edited on 02 March 2015 - 09:58 PM
Bomb Bloke #9
Posted 02 March 2015 - 11:03 PM
local task1 = coroutine.create(afunction())
local task2 = coroutine.create(anotherf())
table.insert(taskstable, task1)
table.insert(taskstable, task2)

This would more of less work, but when creating your coroutines you need to build them out of functions. "afunction()" runs a function and returns the result, and you can't build a coroutine out of that result (unless that also happens to be a function) - you'd generally just pass in "afunction" directly.

Also how would I open a file and run the contents of that program in a coroutine?

Look into the "loadfile" function.
DannySMc #10
Posted 02 March 2015 - 11:15 PM
local task1 = coroutine.create(afunction())
local task2 = coroutine.create(anotherf())
table.insert(taskstable, task1)
table.insert(taskstable, task2)

This would more of less work, but when creating your coroutines you need to build them out of functions. "afunction()" runs a function and returns the result, and you can't build a coroutine out of that result (unless that also happens to be a function) - you'd generally just pass in "afunction" directly.

Also how would I open a file and run the contents of that program in a coroutine?

Look into the "loadfile" function.

I made this: but why does it not work?:S I added the parent in because when the coroutine is created it directly snaps to it and won't run the main() function?:S (In sublime it is nicely indented but these forums don't like it :P/>)


--Test:

function maths()
while true do
print("> Maths stuff")
print("Press 1 to continue")
print("Press 2 to go back")
local args = { os.pullEvent("char") }
if args[2] == "1" then
for i = 1, 10 do
print(i)
end
elseif args[2] == "2" then
coroutine.yield()
end
end
end

function calc()
while true do
print("> Calculator")
print("Press 1 to continue")
print("Press 2 to go back")
local args = { os.pullEvent("char") }
if args[2] == "1" then
print("> 1st number:")
local nNum1 = tonumber(read())
print("> 2nd number:")
local nNum2 = tonumber(read())
print(nNum1.." + "..nNum2.." = "..nNum1+nNum2)
elseif args[2] == "2" then
coroutine.yield()
end
end
end

function main()
while true do
print("Press the numerical number for either option")
print("1. Maths")
print("2. Calculator")
local args = { os.pullEvent("char") }
if args[1] == "char" then
if args[2] == "1" then
coroutine.resume(mathsco)
elseif args[2] == "2" then
coroutine.resume(calcco)
end
end
end
end

parent = coroutine.create(main())
mathsco = coroutine.create(maths())
calcco = coroutine.create(calc())


main()
Edited on 02 March 2015 - 10:15 PM
Bomb Bloke #11
Posted 03 March 2015 - 02:57 AM
Bear in mind that os.pullEvent() is basically a fancy version of coroutine.yield(). The main difference is that it's rigged to pre-emptively catch terminate events, and handles them in a special way. Here's the relevant code from bios.lua:

function os.pullEventRaw( sFilter )
    return coroutine.yield( sFilter )
end

function os.pullEvent( sFilter )
    local eventData = { os.pullEventRaw( sFilter ) }
    if eventData[1] == "terminate" then
        error( "Terminated", 0 )
    end
    return unpack( eventData )
end
DannySMc #12
Posted 03 March 2015 - 09:13 AM
Bear in mind that os.pullEvent() is basically a fancy version of coroutine.yield(). The main difference is that it's rigged to pre-emptively catch terminate events, and handles them in a special way. Here's the relevant code from bios.lua:

function os.pullEventRaw( sFilter )
	return coroutine.yield( sFilter )
end

function os.pullEvent( sFilter )
	local eventData = { os.pullEventRaw( sFilter ) }
	if eventData[1] == "terminate" then
		error( "Terminated", 0 )
	end
	return unpack( eventData )
end

I know this, but why does the code I posted not work? Thanks
Bomb Bloke #13
Posted 03 March 2015 - 10:22 AM
One reason is that you're still attempting to build coroutines out of function results instead of functions, which as I mentioned, won't work. To be more specific, that means you need to change this sort of thing:

mathsco = coroutine.create(maths())

… to this sort of thing:

mathsco = coroutine.create(maths)

You also seem to be thinking that this:

coroutine.yield()

… will break out of your functions in a way that this:

local args = { os.pullEvent("char") }

… won't. However, both coroutine.yield() and os.pullEvent() will yield, because os.pullEvent() calls coroutine.yield() - there's very little difference between the two.

And then there's the matter of passing event data back into your coroutines when you resume them, and figuring out when it's time not to resume them…

Long story short, you need to further your understanding of how coroutines work before you can start writing this sort of code. Your current attempt can't simply be fixed by altering a couple of lines.

I suggest going back to the tutorial Lignum linked you to, and asking questions about any specific parts you don't understand. Once you feel you're familiar with the subject material, read the source code of the parallel API until that makes sense to you too. If you want an example of how you might further build that sort of logic into an OS, consider reading the source of the multishell script, too. From there, you'll have a much better foundation of knowledge from which to start writing your own code.
DannySMc #14
Posted 03 March 2015 - 10:41 AM
One reason is that you're still attempting to build coroutines out of function results instead of functions, which as I mentioned, won't work. To be more specific, that means you need to change this sort of thing:

mathsco = coroutine.create(maths())

… to this sort of thing:

mathsco = coroutine.create(maths)

You also seem to be thinking that this:

coroutine.yield()

… will break out of your functions in a way that this:

local args = { os.pullEvent("char") }

… won't. However, both coroutine.yield() and os.pullEvent() will yield, because os.pullEvent() calls coroutine.yield() - there's very little difference between the two.

And then there's the matter of passing event data back into your coroutines when you resume them, and figuring out when it's time not to resume them…

Long story short, you need to further your understanding of how coroutines work before you can start writing this sort of code. Your current attempt can't simply be fixed by altering a couple of lines.

I suggest going back to the tutorial Lignum linked you to, and asking questions about any specific parts you don't understand. Once you feel you're familiar with the subject material, read the source code of the parallel API until that makes sense to you too. If you want an example of how you might further build that sort of logic into an OS, consider reading the source of the multishell script, too. From there, you'll have a much better foundation of knowledge from which to start writing your own code.

Okay please do me one favor, will someone write a script that will be a menu that I can switch between two coroutines? as this is what is getting me and I need to see it in action… thanks in advance. I have looked at the tutorial multiple times, but can't seem to understand how it works…

On some other research, how does this parallel api work? will this allow me to make a simple menu? and run the coroutines? or?
Bomb Bloke #15
Posted 03 March 2015 - 10:54 AM
The multishell script is the "menu" you're asking for. Each script gets a tab. You click a tab, the relevant script "takes focus", so to speak. Scripts which aren't "in focus" are not resumed with "user input" events such as mouse clicks or typed characters. Window objects are used to handle the conflicts in screen usage.

But read the parallel API first. It's a far simpler coroutine manager which takes a list of functions, rigs them up as coroutines and resumes each in turn over and over again until either one dies (when calling parallel.waitForAny), or until they all die (when running parallel.waitForAll). Take note of the way in which it uses the "filters" to work out which events should be used to resume which coroutines, the way in which it passes that event data on to resumed coroutines, and the way it handles dead coroutines.
DannySMc #16
Posted 03 March 2015 - 11:21 AM
The multishell script is the "menu" you're asking for. Each script gets a tab. You click a tab, the relevant script "takes focus", so to speak. Scripts which aren't "in focus" are not resumed with "user input" events such as mouse clicks or typed characters. Window objects are used to handle the conflicts in screen usage.

But read the parallel API first. It's a far simpler coroutine manager which takes a list of functions, rigs them up as coroutines and resumes each in turn over and over again until either one dies (when calling parallel.waitForAny), or until they all die (when running parallel.waitForAll). Take note of the way in which it uses the "filters" to work out which events should be used to resume which coroutines, the way in which it passes that event data on to resumed coroutines, and the way it handles dead coroutines.

Okay on that, how do you use these filters? I have had a read through it, just wondered how you would run an event? then wait? Now it sounds silly but I want to make a new OS, of course this one is for personal use but at the moment it won't be over the top advanced it is just to use the coroutines, like I need to figure out how to run a program then stop?
My idea: (it is gonna be basic, I just need to make sure it can handle the drawing from my api still):
I have a desktop:

1. When I click an icon for example calculator, it will run the program and add it to the taskbar.
2. then I can click home to go back, but the calculator program is halted, but when I click the calculator button in the taskbar i will be able to resume where I was, whether that being while typing or while it is waiting for an event (os.pullEvent()).
3. Then I can go back to the home screen and run another app, example worm, I can then click this and it will open it up and run it while adding it to the taskbar.
4. I should be able to switch between both apps and still be able to access the desktop
5. If the calculator ever stops running or I want to exit I can press say an X button that will delete the entry and stop the program/window and discard it?

Like windows.

I get it won't still be running but something that will allow me to switch between tasks and it will always be there.. wherever I left off.

I thought maybe using multiple windows? but if so how would I use the taskbar to redirect me back to the window and redraw the windows contents? All these apps are functions in my main os file? so i could be like run(functioncalculator()) etc.

Also if I have an app, how could I run a file in a new window? opening up the files contents and running it or?

If possible could you help me with this, code would be helpful so I can see how it done, so I can implement it myself because at the moment, I don't know where to start and when i say I am struggling to get my head around it you say read another link and this link still doesn't help, if possible I need examples, thanks again though as this is a big help!
Edited on 03 March 2015 - 10:22 AM
DannySMc #17
Posted 03 March 2015 - 11:28 AM
This is what I have made so far:
[attachment=2152:menu.PNG]

Each tab on line 3 will be able to resume from an already started process, the bar is made of a table of all running processes, of course it is an example but the idea is the same:)
Bomb Bloke #18
Posted 04 March 2015 - 01:11 AM
If possible could you help me with this, code would be helpful so I can see how it done, so I can implement it myself because at the moment, I don't know where to start and when i say I am struggling to get my head around it you say read another link and this link still doesn't help, if possible I need examples, thanks again though as this is a big help!

Just saying "you don't get it" doesn't get us anywhere. Go back to the tutorial, and if you encounter a part you don't understand, point out the segment and I can rephrase it for you.

Likewise, the parallel API is a perfectly good code example. Don't understand what a given line is doing? Then quote out the first line that's giving you trouble, and it can be explained to you - etc until you get it.
DannySMc #19
Posted 04 March 2015 - 09:12 AM
If possible could you help me with this, code would be helpful so I can see how it done, so I can implement it myself because at the moment, I don't know where to start and when i say I am struggling to get my head around it you say read another link and this link still doesn't help, if possible I need examples, thanks again though as this is a big help!

Just saying "you don't get it" doesn't get us anywhere. Go back to the tutorial, and if you encounter a part you don't understand, point out the segment and I can rephrase it for you.

Likewise, the parallel API is a perfectly good code example. Don't understand what a given line is doing? Then quote out the first line that's giving you trouble, and it can be explained to you - etc until you get it.

Okay will have a try, I guess when I say I don't get it I mean, someone could so kind as to write a in-depth way of making the menu system I need, as it will be a lot more help, because I get the examples but none of them explain what I need it for…