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

Coroutines, Buffers, Gophers API, and help?

Started by DannySMc, 23 March 2015 - 03:08 PM
DannySMc #1
Posted 23 March 2015 - 04:08 PM
So I have an idea to implement a multitasking based off multishell, now the idea I had was being able to disable the top tab bar and just make my own version of the tab menu, but…

How would I start? Would this count as a coroutine manager and buffer?

So when I change tab it will always store what was on the other tab and when I come back it is still there?
Edited on 24 March 2015 - 04:25 PM
Lupus590 #2
Posted 23 March 2015 - 04:48 PM
the default multi-shell makes a lot of use of the default window api, you will need to look at both for a good understanding

also, a lot of people have made their own, it may help to look at their code too
MKlegoman357 #3
Posted 23 March 2015 - 04:52 PM
Every 'tab' would have it's own buffer to write graphics to. Every new program would probably be ran in a coroutine. So yes, you'll need a coroutine manager and a buffer system. There's a great tutorial on CC-specific coroutine management here.
DannySMc #4
Posted 23 March 2015 - 06:46 PM
Yeah but the multishell has all of that already.. From looking at the code… I was just wondering if anyone could help me. This is the second post I have made about this, I get how coroutines work I just want to know how to apply them into an actual program, like when I create a coroutine, how would I index it? So if someone downloaded an app, how do I run that as a coroutine and index it? Example:

local co1 = couroutine.create(function ()
	print("Hello World!")
end)

Do I index it like:

tProcesses = {}
table.insert(tProcesses, co1)

Then run it like:

tProcesses[1]
--or
tProcesses.co1()
[/SPOILER]

The example means that I want to make it dynamic not just make a few coroutines for set functions, I want to be able to make many functions like:
[CODE]
thread.new(nameofprocess)
or something like that.

This is where I don't understand, 'tis why I said about multishell because everything is there, it uses the Window API as a buffer and redirects to different coroutines, like I don't understand how you can yield one coroutine then run another and keep some kind of taskbar still there, which is why I asked about multishell.

I don't learn from reading I learn by taking apart someone else's code or someone writing up so code and walking me through it, I get simple coroutines, but not the multitasking (like other people's OS's can do)?
Edited on 23 March 2015 - 05:47 PM
MKlegoman357 #5
Posted 23 March 2015 - 07:09 PM
That tutorial I linked actually explains how multitasking works in CC. After you read that, I would suggest to first examine the parallel API code, it's a bit shorter and simpler than Multishell. Once you get that, move on to Multishell.

After looking at your example with putting something into a table, do you know exactly how tables and the table library works?


local myTable = {"first"}

table.insert(myTable, "second")
-- same as:
myTable[#myTable + 1] = "second"

myTable #--> {"first", "second"}

table.insert(myTable, 2, "third")
-- same as:
myTable[3] = myTable[2]
myTable[2] = "third"

myTable #--> {"first", "third", "second"}
Edited on 23 March 2015 - 06:10 PM
KingofGamesYami #6
Posted 23 March 2015 - 10:14 PM
To run a coroutine, use coroutine.resume( co, args ) where args are event information. If event information is omitted, nothing will work.
DannySMc #7
Posted 23 March 2015 - 10:17 PM
That tutorial I linked actually explains how multitasking works in CC. After you read that, I would suggest to first examine the parallel API code, it's a bit shorter and simpler than Multishell. Once you get that, move on to Multishell.

After looking at your example with putting something into a table, do you know exactly how tables and the table library works?


local myTable = {"first"}

table.insert(myTable, "second")
-- same as:
myTable[#myTable + 1] = "second"

myTable #--> {"first", "second"}

table.insert(myTable, 2, "third")
-- same as:
myTable[3] = myTable[2]
myTable[2] = "third"

myTable #--> {"first", "third", "second"}

Yeah I do, very familiar with it, I just don't know how coroutines work as some kind of menu system, if someone could explain in detail the steps I have said before that would be very useful, thanks.
Bomb Bloke #8
Posted 23 March 2015 - 11:50 PM
Danny, no offense, but after your last thread I have difficulty believing you've even fully read bubba's tutorial. Trying to get you to pinpoint the bits you're having trouble with was like pulling hen's teeth - I couldn't get you to do it.

If it helps, think of a coroutine as a function which returns every single time it yields, but until it returns "properly", it can be resumed from where it left off. Every time it yields, it's able to return data - think of coroutine.yield("char") (or os.pullEvent("char"), they boil down to nearly the same thing) as being sorta like return "char" - and every time it resumes, it expects data back - think of coroutine.resume(myFunc, "char", "p") as being sorta like myFunc("char", "p").

You don't have to pass event filter requests or event data back and forth when yielding or resuming, but that's the convention with ComputerCraft, and nigh-all user scripts will expect you to do it that way.

Again, if you can't wrap your head around the parallel API's code - a very simple coroutine manager - then you're not ready to look into running menu systems alongside coroutines, either.
DannySMc #9
Posted 24 March 2015 - 09:41 AM
No I seriously did read it, I understand it I just am not completely sure how to make like a manager? If you get what I mean? I shall have a look at the parallel API.
DannySMc #10
Posted 24 March 2015 - 11:55 AM
Hey there,
I have been advancing a little bit and found an API that I like the look of, the only thing I am not sure about is the following:

I get how to initialize the parent couroutine. I know how to spawn more in, but how do you like say switch back to another coroutine? do you just use a break in the coroutine? so a while true do and then break it and it will return to the parent?

As I made a code which would spawn a new coroutine, but it goes to that coroutine? How could I spawn them in the background, and run them when I want, so If I have OEEDS browser and say sketch, how would I run both of them and then switch between them, now I know how to make an interface etc for it, I just need to know how to actually go to it, because you have thread.run() and thread.spawn() but there is no like, thread.resume() etc…?

NOTE: I named the api "thread".

Pastebin link to Gopher's API

EDIT ON THIS:

How do you run a seperate program from this? so instead of using functions how would you specify a program? and load that into a function? and run it as a coroutine?
Edited on 24 March 2015 - 11:03 AM
KingofGamesYami #11
Posted 24 March 2015 - 12:32 PM

local program_function = loadfile( "program" )
--#now it's a function; you can use it
DannySMc #12
Posted 24 March 2015 - 12:38 PM

local program_function = loadfile( "program" )
--#now it's a function; you can use it
Ahh that's awesome thank you! Any idea on the other parts?
KingofGamesYami #13
Posted 24 March 2015 - 12:46 PM
Perhaps you could use the spawnBackground function on line 222?

Edit: Oh, also it'll return to the parent any time it yields, ei pulls an event. (I think, it's not my API)
Edited on 24 March 2015 - 11:47 AM
DannySMc #14
Posted 24 March 2015 - 12:48 PM
Perhaps you could use the spawnBackground function on line 222?

Edit: Oh, also it'll return to the parent any time it yields, ei pulls an event. (I think, it's not my API)

Okay thank you :)/>
DannySMc #15
Posted 24 March 2015 - 02:39 PM
Does anyone have some kind of coroutine and buffer setup? As I just want a simple API that will allow me to switch between co routines and the buffer will be redrawn? As Gopher's API doesn't have a buffer. I don't know how to make one either…
DannySMc #16
Posted 24 March 2015 - 03:35 PM
Hello,

I have been looking around for some kind of buffer and coroutine manager but to no avail have I found one… Is anyone know of one that allows me to use simple functions to get the desired outcome as Gopher's API doesn't have a buffer and multishell, simply doesn't want to work for me…?

Thanks

EDIT:
I just need something like what Nova Horizon has, I want to be able to get a table of running coroutines as well as be able to create, run, kill, run in background etc?
Edited on 24 March 2015 - 03:26 PM
DannySMc #17
Posted 24 March 2015 - 04:20 PM
Anyone?
MKlegoman357 #18
Posted 24 March 2015 - 04:26 PM
Do you really expect anyone to reply in one hour? Wait a day, well, at least 12 hours. I think that you should have just posted all these recent questions about coroutines in one thread, because you're addressing basically the same problem in each of the thread. I'm not aware of any coroutine manager and buffer which works together as an API on the forums.
Edited on 24 March 2015 - 03:26 PM
DannySMc #19
Posted 24 March 2015 - 04:29 PM
Do you really expect anyone to reply in one hour? Wait a day, well, at least 12 hours. I think that you should have just posted all these recent questions about coroutines in one thread, because you're addressing basically the same problem in each of the thread. I'm not aware of any coroutine manager and buffer which works together as an API on the forums.

Well I understand that but I am getting frustrated with not knowing how this works, I am just becoming impatient which is my fault, just want something that will work because I am so confused with all of this :/
Lyqyd #20
Posted 24 March 2015 - 04:45 PM
Threads merged.

We'd like to help you, but you keep jumping around. Let's start with the coroutine tutorial Bubba wrote, linked earlier in the thread. What, specifically, did you not understand in it? Feel free to ask a lot of questions in this thread, but do please try to make them as specific as you can. Maybe start from the beginning of the tutorial and post questions here about things you aren't grasping until we've gotten you through the whole tutorial. We can't help you unless we know specifically what parts of the system you don't understand.
CrazedProgrammer #21
Posted 24 March 2015 - 04:45 PM
No I seriously did read it, I understand it I just am not completely sure how to make like a manager? If you get what I mean? I shall have a look at the parallel API.
You can make a very simple coroutine manager by making a buffer and an update function.
Every time the update function is fired, the coroutines will resume until they call coroutine.yield.
If a coroutine is finished (dead) then it will be removed from the buffer.

buffer = { }
function update()
  local i = 1
  while i <= #buffer do
	local ok, err = coroutine.resume(buffer[i])
	-- if not ok then error(err) end
	if coroutine.status(buffer[i]) == "dead" then
	  table.remove(buffer, i)
	else
	  i = i + 1
	end
  end
end
-- Test program
function test()
  print("test 1")
  coroutine.yield()
  print("test 2")
end
table.insert(buffer, coroutine.create(test)) -- Adds the coroutine to the buffer
update() -- Prints test 1
os.sleep(1) -- Waits 1 second
update() -- Prints test 2
Edited on 24 March 2015 - 03:46 PM
DannySMc #22
Posted 24 March 2015 - 04:59 PM
Threads merged.

We'd like to help you, but you keep jumping around. Let's start with the coroutine tutorial Bubba wrote, linked earlier in the thread. What, specifically, did you not understand in it? Feel free to ask a lot of questions in this thread, but do please try to make them as specific as you can. Maybe start from the beginning of the tutorial and post questions here about things you aren't grasping until we've gotten you through the whole tutorial. We can't help you unless we know specifically what parts of the system you don't understand.

Sorry I just keep re-explaining what I need, so I shall try and do this as best as I can:

I am really keen to learn this but of course it's a little confusing. The idea I want is what Nova has, which is the ability to run apps in a coroutine (seperate file) and run functions as coroutines (like gophers api).
The tutorial is great and that and I understand coroutines. This is what I can't manage:

+ How do you index a coroutine? So if I make a coroutine and a buffer and name it "desktop", how can I index that name so I can run the coroutine/buffer later? So how would I index a coroutine (in a table) so when I use say process.getCount() it will come back with what is running and the names of them?

+ This also spans from the above question, but how would you run the coroutine? like how would I index it and link it to a coroutine? like can I do
 coroutine.resume(processes["desktop"]) 
?

+ How to make a buffer? Like I have no idea how the hell I do that, you could tell me I need to record what is written to the screen, but that makes NOO sense to me.. >.<

+ How do I make it so I can kill a coroutine? So use thread.kill(processes["desktop"]) etc?

+ How do I, when resuming an already run coroutine, redraw the buffer and go back to the while loop that waits for user interaction? (or have everything that they have already made still on the screen)?

+ How do I create a coroutine with buffer in the background, add it to everything and then go to it later when the user would be ready?

+ How would I run a program as a file and run it in the same script?

I will explain how I make programs, I am not sure if this is correct or how everyone does it but this is how I do it:
I make lots of functions like: home(), menu(), programs(), luaide() etc, and then run the functions when a program is run, and then when I want to run an external program to the actual OS/program file I use os.run() and then when it finishes it comes back to the OS/program. In every function is a while true do loop that takes input using os.pullEvent() and deals with it accordingly? So how do I make it use coroutines so instead of just switching between functions and losing everything I have just done but be able to go back and forth and keep all the content in there? and make it so I can actually index the coroutines to call later?

Edit: I have never used the parallel API so I have no idea how it works, I only use os.pullEvent()? :S

Edit2: This is why I am looking into an API because I will learn from testing it, not reading up about it, I need an actual coroutine manager/buffer api and test it so I can see how it works :)/>
Edited on 24 March 2015 - 04:18 PM
Kouksi44 #23
Posted 24 March 2015 - 05:35 PM
I think you should settle down a bit and try to accomplish what you are looking for step by step.

1. You can simply use the name of the coroutine as the index like so :
 processes[yourProcess.name]=yourProcess 
.
That way you can call your coroutine as following (assuming you called your routine "test" )
 coroutine.resume(prcoesses["test"],...) 

3. As far as I understand it a buffer is simply a table(object) you can redirect the output to just as you would do with other redirect objects like monitors. This means you have to implement all functions a redirect object needs (like print, write, setCursorPos,etc…). Just search for it on the forums and you will surely find some examples.

4. You can´t kill /stop a coroutine while it is executing code. Your kill() function can only remove the coroutine from the process table as soon as it has yielded and hence make it unusable.

Regarding your other questions about buffers : Maybe try to read through the code of some other buffers here on the forum ;)/>

If you don´t know how to use an Api use the wiki!
Edited on 24 March 2015 - 04:39 PM
GopherAtl #24
Posted 24 March 2015 - 06:01 PM
re: my APIs, mentioned earlier in the thread (in one of the previous threads before the merge?) and in the current thread title… I actually had a version of multishell which used my buffer and coroutine APIs, which itself is an example of how to use my coroutine api, goroutines, to handle multiple programs running at once. It didn't have a taskbar at all, instead using the ctrlkeys api to switch on ctrl+1, ctrl+2, etc, but it sounded like you'd figured that part out? It also included a pair of shell programs that, when run under my version of multishell, would let you launch programs in the foreground (so you could switch to them) and in the background (which effectively redirected them to nil, so you could NOT switch to them, or see their output, they just ran in the background) The multishell program is linked from the end of the first post in my APIs thread, and there's a separate thread in programs for my multishell, which I think is also linked in that first post, think you'd have to go to that thread to find the fg and bg companion programs.

Having said that, my multishell and goroutines api should probably be used as a reference rather than a foundation.The gorouitnes api was never tested very thoroughly, and even if people start testing and reporting issues now, I'm not likely to be working on fixing it up any time soon - it's an abandoned 3-year-old project, and not something I have even tried to use myself in years. Multishell is just entirely dependant on goroutines, so not recommended on that basis. I did use it myself for a while, and don't remember encountering serious issues with it.
DannySMc #25
Posted 24 March 2015 - 06:47 PM
re: my APIs, mentioned earlier in the thread (in one of the previous threads before the merge?) and in the current thread title… I actually had a version of multishell which used my buffer and coroutine APIs, which itself is an example of how to use my coroutine api, goroutines, to handle multiple programs running at once. It didn't have a taskbar at all, instead using the ctrlkeys api to switch on ctrl+1, ctrl+2, etc, but it sounded like you'd figured that part out? It also included a pair of shell programs that, when run under my version of multishell, would let you launch programs in the foreground (so you could switch to them) and in the background (which effectively redirected them to nil, so you could NOT switch to them, or see their output, they just ran in the background) The multishell program is linked from the end of the first post in my APIs thread, and there's a separate thread in programs for my multishell, which I think is also linked in that first post, think you'd have to go to that thread to find the fg and bg companion programs.

Having said that, my multishell and goroutines api should probably be used as a reference rather than a foundation.The gorouitnes api was never tested very thoroughly, and even if people start testing and reporting issues now, I'm not likely to be working on fixing it up any time soon - it's an abandoned 3-year-old project, and not something I have even tried to use myself in years. Multishell is just entirely dependant on goroutines, so not recommended on that basis. I did use it myself for a while, and don't remember encountering serious issues with it.

Haha my saviour :P/> and yeah okay, well I just wanted a simple taskbar and a way of getting a count of all the running functions (names). I had a few errors where I was adding thread.spawn(main()) etc but I was told to use thread.spawn( main ) instead? If I did that how would I "resume" it?

I think you should settle down a bit and try to accomplish what you are looking for step by step.

1. You can simply use the name of the coroutine as the index like so :
 processes[yourProcess.name]=yourProcess 
.
That way you can call your coroutine as following (assuming you called your routine "test" )
 coroutine.resume(prcoesses["test"],...) 

3. As far as I understand it a buffer is simply a table(object) you can redirect the output to just as you would do with other redirect objects like monitors. This means you have to implement all functions a redirect object needs (like print, write, setCursorPos,etc…). Just search for it on the forums and you will surely find some examples.

4. You can´t kill /stop a coroutine while it is executing code. Your kill() function can only remove the coroutine from the process table as soon as it has yielded and hence make it unusable.

Regarding your other questions about buffers : Maybe try to read through the code of some other buffers here on the forum ;)/>

If you don´t know how to use an Api use the wiki!
Ahh okay thank you that was quite helpful, and will have a look, do you know of any good buffers that are already made?
Lyqyd #26
Posted 24 March 2015 - 07:02 PM
Okay, those are some solid questions. Let's walk through them. We will assume that we start with a table to hold our coroutines:


local runningProcesses = {}

Now, you ask how you might add a program named "desktop", with a coroutine and a buffer. That might look like this:


local runningProcesses = {}

local process = {
  name = "desktop",
  thread = coroutine.create(function() return shell.run("desktop") end),
  buffer = buffer.new() --assuming a buffer API for now
}

table.insert(runningProcesses, process)

Your process.getCount function might look something like this:


function process.getCount()
  return #runningProcesses
end

--# or maybe this:

function process.getCount()
  local names = {}
  for _, proc in ipairs(runningProcesses) do
    table.insert(names, proc.name)
  end
  return names
end

To run a coroutine, your coroutine manager would need to gather events, then pass them on to the coroutines. The very simplest version of this might look something like the below. Be aware that this is not a correctly functioning manager, as it is missing a few crucial features you would want.


while true do
  local event = {os.pullEventRaw()}
  for _, proc in ipairs(runningProcesses) do
    local _old = term.redirect(proc.buffer)
    coroutine.resume(proc.thread, unpack(event))
    term.redirect(_old)
    proc.buffer.draw()
  end
end

A buffer is a set of tables that contains information about the contents of the screen. They nearly always include a valid terminal redirect. To use them, you redirect the screen to that buffer. Then the program that's currently running will end up using those terminal calls provided by the buffer, which means that it can record and keep track of what it is drawing to the screen. Depending on the buffer's implementation, it may immediately draw its contents to the screen as it receives term calls, or it may wait to be told to draw everything at once later on. To see an example of this, check out the window API included with CC, or check out GopherATL's redirect API, or my Framebuffer API, or any number of others.

Using our example above, a coroutine killing function might look like this:


function process.kill(name)
  for i = 1, #runningProcesses do
    if runningProcesses[i].name == name then
      table.remove(runningProcesses, i)
      return true
    end
  end
  return false
end

For #5, you'd just need to have a buffer that you can force to draw to the screen. Call its forced-screen-drawing function after you resume the coroutine and it yields (so that you draw the most-up-to-date display). I'm not sure what you mean by the other part of this question, about "going back to" a while loop.

For six, it depends on what you mean by "go to it", and how you know that the user is "ready", but I suspect you mean changing the currently-focused process in the coroutine manager, which is actually a fair bit of complexity. You'd need to be filtering events and you'd want to keep track of which process was "in focus" and send player input events only to that coroutine. You'd need some way to change which coroutine was in focus and make sure to clean up the value when removing a coroutine due to use input or a program ending. These sorts of features can end up adding a fair bit of complexity, so it would be best to see how parallel does event filtering, then try adding the other features to your own coroutine manager and get help here as you ran in to problems implementing it.

I'm not sure what you mean by the next question.

As for the way you're using functions as programs, that comes close to how you should be doing it. Think of coroutines as functions that you can resume where you left off, and that don't forget their state in doing so. You just have to use a coroutine manager to pass the events in as appropriate. I'm not sure how you were converting the programs in to functions, but Lua programs in CC are already written as a function body, so all it takes is a simple loadstring() to make a valid function out of a valid program. You can then use that function by passing it to coroutine.create to make a coroutine, which is much easier to do neat things with. You can also use an anonymous function calling shell.run as you can see above.