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

Coroutine

Started by KaoS, 16 August 2012 - 08:38 AM
KaoS #1
Posted 16 August 2012 - 10:38 AM
Well this is embarrassing :(/>/> to be honest I have recently taken a look at the lua 5.1 manual and I just cannot understand the coroutines AT ALL, I tried writing one but it did nothing at all, (I think the while true loop in the function may have broken it, not sure) but I would really love to learn it, anyone prepared to explain? I know Pharap knows this stuff
ardera #2
Posted 16 August 2012 - 11:02 AM
The Coroutine is the Thread API.
coroutine.create(function)
is the function to create a new Thread
coroutine.resume(thread)
resumes/startes a thread,
coroutine.state(thread)
returns the state of the coroutine (see the manual)
coroutine.yield(string)
stops the actual Thread and waites for a event
The whole System is a Thread, its called the Main Thread, but you can create new Threads that work parallel to all other things.
The parallel API uses the coroutine API to run functions parallel.

BUT: I think coroutine.resume receives other args too, such as events, but Im not sure

PS.: a thread is not a string, not a number, not a table, and not nil, It has its own type: "Thread". And you can't create Threads without the coroutine.create function
KaoS #3
Posted 16 August 2012 - 11:06 AM
Ok, so you say

mycoroutine=coroutine.create(print("test"))
then when you say

coroutine.resume(mycoroutine)
it prints 'test'?

what is the point of that, can you include a loop and have it running while you run other programs?

and I think any other args you put into resume get put into the function somehow
BigSHinyToys #4
Posted 16 August 2012 - 11:13 AM
This is more complex example of coroutines this script is intended for use with a windoes api
Spoiler

--[[
   Windows style OS
   With multi tasking
   By Big Shiny Toys
]]--
local tFunck = {
taskMan = function()
  local mywin = newWin("Task Man",20,12,1,1,"slim") -- "classic" "slim"
  local h = 1
  while true do
   local i,i1,i2,i3,i4,i5 = coroutine.yield()
   wSetCursorPos(mywin,1,1)
   wPrint(mywin,"tasks :"..tostring(#routines).." win :"..tostring(#windata))
   for i = 1, #routines do
	h = h +1
	wSetCursorPos(mywin,1,h)
	wPrint(mywin,i.." :"..coroutine.status(routines[i]))
	h = h +1
	wSetCursorPos(mywin,1,h)
	wPrint(mywin,tostring(coroutine.running(routines[i])))
   end
   h = 1
  end
end,
move = function()
  while true do
   local win = 1
   local i,i1,i2,i3,i4,i5 = coroutine.yield()
   if i == "char" and i1 == "w" then
	break
   end
   local winX,winY = location(win)
   if i == "key" then
	if i1 == 200 then -- up key
	 winY = winY-1
	end
	if i1 == 208 then -- down key
	 winY = winY+1
	end
	if i1 == 203 then -- left key
	 winX = winX-1
	end
	if i1 == 205 then -- right key
	 winX = winX+1
	end
	moveWin(win,winX,winY)
   end
  end
end,
cmd = function()
  local wn = newWin("CMD",20,12,4,4,"slim") -- "classic" "slim"
  wSetCursorPos(wn,1,1)
  wPrint(wn,"test")
  test = wRead(wn)
  wSetCursorPos(wn,1,2)
  wPrint(wn,test)
end
}
test = nil
--  start of OS --
windata = {} -- windata[1] = {} -- windata[1][1] = "Task Manager" windata[1][2] = xpos
local focused = 0
routines = {}
local border = ""
local border2 = ""
function newWin(name,xsiz,ysiz,xpos,ypos,style)
if style == "classic" or style == "slim" then
elseif style == nil then
  style = "classic"
end
if xpos == nil or ypos == nil then
  xpos,ypos = 1,1
end
if xsiz == nil or ysiz == nil then
  xsiz,ysiz = 20,4
end
local maxn = #windata + 1
windata[maxn] = {}
windata[maxn][1] = tostring(name)
windata[maxn][2] = xpos
windata[maxn][3] = ypos
windata[maxn][4] = xsiz
windata[maxn][5] = ysiz
windata[maxn][6] = {}
-- windata[maxn][6][1] = {} -- y lines needs work
for y = 1, windata[maxn][5] do
  windata[maxn][6][y] = {}
  for x = 1, windata[maxn][4] do
   windata[maxn][6][y][x] = " "
  end
end
-- windata[maxn][6][1][1] = {} -- x lines needs work
windata[maxn][7] = style
windata[maxn][8] = 1 -- curX
windata[maxn][9] = 1 -- curY
return(maxn)
end
function wRead(win)
local temp = {}
local sIn = ""
local it,it1,it2,it3,it4,it5
while true do
  it,it1,it2,it3,it4,it5 = coroutine.yield()
  if i == "char" then
   table.insert(temp,il)
  end
  if i == "key" then
   if il == 28 then
	break
   end
   if il == 14 then
	if #temp < 0 then
	else
	 table.remove(temp)
	end
   end
  end
  for i = 1, #temp do
   sIn = sIn..temp[i]
  end
end
return sIn
end
function wSetCursorPos(win,ax,ay)
windata[win][8] = ax
windata[win][9] = ay
end
function wPrint(win,inval)
for i = 1,string.len(inval) do
  windata[win][6][windata[win][9]][windata[win][8]] = string.sub(inval,i,i)
  windata[win][8] = windata[win][8]+1
  if windata[win][8] == windata[win][4]+1 then
   windata[win][8] = 1
   windata[win][9] = windata[win][9]+1
  end
end
end
local function draw(win)
local function safeWrite(this)
  local termX,termY = term.getSize()
  local curX,curY = term.getCursorPos()
  if termX-curX-1 < 0 then
   return
  else
   write (string.sub(this,1,termX-curX-1))
   return
  end
end
local wasx,wasy = term.getCursorPos()
local termX,termY = term.getSize()
if win == nil then
  return
end
border = ""
border2 = ""
for q = 1, windata[win][4]+2 do
  if q == 1 or q == windata[win][4]+2 then
   border = border.."+"
  else
   border = border.."-"
  end
end
for q = 1,windata[win][4]+2 do
  if q == 1 or q == windata[win][4]+2 then
   border2 = border2.."|"
  else
   border2 = border2.." "
  end
end
if windata[win][7] == "classic" then
  for i = 1, windata[win][5]+4 do
   term.setCursorPos(windata[win][2],windata[win][3]+i-1)
   if i == 1 or i == 3 or i == windata[win][5]+4 then
	safeWrite(border) -- here
   else
	safeWrite(border2) -- here
   end
  end
  term.setCursorPos(windata[win][2]+1,windata[win][3]+1)
  safeWrite(string.sub(windata[win][1],1,windata[win][4]-1))
  for y = 1, #windata[win][6] do
   for x = 1, #windata[win][6][y] do
	term.setCursorPos(windata[win][2]+x,windata[win][3]+y+2)
	safeWrite(tostring(windata[win][6][y][x]))
   end
  end
  term.setCursorPos(wasx,wasy)
elseif windata[win][7] == "slim" then
  for i = 1, windata[win][5]+2 do
   term.setCursorPos(windata[win][2],windata[win][3]+i-1)
   if i == 1 or i == windata[win][5]+2 then
	safeWrite(border) -- here
   else
	safeWrite(border2) -- here
   end
  end
  term.setCursorPos(windata[win][2]+2,windata[win][3])
  safeWrite(string.sub(windata[win][1],1,windata[win][4]-1)) -- here
  for y = 1, #windata[win][6] do
   for x = 1, #windata[win][6][y] do
	term.setCursorPos(windata[win][2]+x,windata[win][3]+y)
	safeWrite(tostring(windata[win][6][y][x]))
   end
  end
  term.setCursorPos(wasx,wasy)
end
end
local function run(fFun)
local a = #routines+1
routines[a] = coroutine.create(tFunck[fFun])
end
function location(win)
if win == nil then
  return
else
  return windata[win][2],windata[win][3]
end
end
function moveWin(win,posx,posy)
if win == nil or posx == nil or posy == nil then
  return
else
  windata[win][2],windata[win][3] = posx,posy
end
end
--[[
local mywin = newWin("Hello World",40,7,1,1,"slim")
local mywin2 = newWin("Hello World",10,2,1,1,"slim")
local sel = 3
local tempX,tempY = location(mywin)
draw(mywin)
]]--
-- boot here --
run("taskMan")
run("move")
run("cmd")
-- end boot --
while true do
local e,e1,e2,e3,e4,e5 = os.pullEvent()
term.clear()
term.setCursorPos(1,1)
if e == "char" and e1 == "q" or e1 == "Q" then
  break
end
for i = 1, #routines do
  coroutine.resume(routines[i],e,e1,e2,e3,e4,e5)
end
for p = 1, #windata do
  draw(p)
end
end
-- end os --
--[[
while true do --  test scrip
e,e1,e2,e3,e4,e5 = os.pullEvent()
if e == "char" then
  if e1 == "w" then
   sel = sel+1
   if sel < 1 then sel = 3 end
   if sel > 3 then sel = 1 end
   tempX,tempY = location(sel)
  end
end
if e == "key" then
  if e1 == 200 then -- up
   tempY = tempY-1
  end
  if e1 == 208 then -- down
   tempY = tempY+1
  end
  if e1 == 203 then -- left
   tempX = tempX-1
  end
  if e1 == 205 then -- right
   tempX = tempX+1
  end
end
if sel == 3 then
else
  moveWin(sel,tempX,tempY)
end
term.clear()
draw(mywin2)
draw(mywin)
end
]]--
ardera #5
Posted 16 August 2012 - 11:14 AM
Ok, so you say

mycoroutine=coroutine.create(print("test"))
then when you say

coroutine.resume(mycoroutine)
it prints 'test'?
No, because if you run a function (add the () to a function name to run it)) it will get the return of a function, not the function. if you want to print test in a coroutine then here is the code:

function theprint()
  print("test")
end
local a=coroutine.create(theprint) --will give the function itself as a param, not the return
coroutine.resume(a)  --will start the Thread a
what is the point of that, can you include a loop and have it running while you run other programs?
right, you can terminate the shell, and terminate the bios and the Thread will run more.
and I think any other args you put into resume get put into the function somehow
yes, that can be
KaoS #6
Posted 16 August 2012 - 11:16 AM
Interesting, I'm guessing that you could then do


mycoroutine=coroutine.create(loadstring("print('test')"))
ardera #7
Posted 16 August 2012 - 11:17 AM
Yes thats right

PS.: Nice picture :(/>/>
KaoS #8
Posted 16 August 2012 - 11:19 AM
I got that far before but if I made the simple functions:


local function go()
turtle.forward()
turtle.back()
end
local function mover()
while true do
turtle.forward()
turtle.back()
end
end

and called

co=coroutine.create(go)
coroutine.resume(co)
it moved forwards and ended successfully
and

co=coroutine.create(mover)
coroutine.resume(co)
just returned success and did nothing
KaoS #9
Posted 16 August 2012 - 11:21 AM
Yes thats right

PS.: Nice picture :(/>/>

haha, thanks
ardera #10
Posted 16 August 2012 - 11:28 AM
I got that far before but if I made the simple functions:


local function go()
turtle.forward()
turtle.back()
end
local function mover()
while true do
turtle.forward()
turtle.back()
end
end

and called

co=coroutine.create(go)
coroutine.resume(co)
it moved forwards and ended successfully
and

co=coroutine.create(mover)
coroutine.resume(co)
just returned success and did nothing
Try to clear the co var, like co=nil and then try again… I don't know why it should stop the Thread…
KaoS #11
Posted 16 August 2012 - 11:30 AM
I did that, repeatedly restarted the PC. it didn't stop the thread, it claimed it was complete… that's what got me
Pharap #12
Posted 16 August 2012 - 11:35 AM
I got that far before but if I made the simple functions:


local function go()
turtle.forward()
turtle.back()
end
local function mover()
while true do
turtle.forward()
turtle.back()
end
end

and called

co=coroutine.create(go)
coroutine.resume(co)
it moved forwards and ended successfully
and

co=coroutine.create(mover)
coroutine.resume(co)
just returned success and did nothing

Perhaps because you're using co instead of mover?
Or maybe because you are trying to do an action then reverse it, it happened so fast it wasn't noticeable. Try making the turtle loop back on itself via turning (eg forward, right, forward, right etc) that way if something goes wrong it won't screw up. Also, make the coroutines do different things, like have one moving, one printing.
KaoS #13
Posted 16 August 2012 - 11:37 AM
yeah but the first one when called just moved forward, that is noticeable, the second one maybe you're right, but it ended the thread, said it ended successfully…. makes 0 sense
Pharap #14
Posted 16 August 2012 - 11:40 AM
yeah but the first one when called just moved forward, that is noticeable, the second one maybe you're right, but it ended the thread, said it ended successfully…. makes 0 sense
You do realise you never actually started mover right?

co=coroutine.create(go)
coroutine.resume(co)

co=coroutine.create(mover)
coroutine.resume(co)
second one resumes co, not mover.
KaoS #15
Posted 16 August 2012 - 11:43 AM
no, take another look, I restarted the PC and then ran


co=coroutine.create(mover)
coroutine.resume(co)

which redefines co as the coroutine/thread of mover
Pharap #16
Posted 16 August 2012 - 11:46 AM
no, take another look, I restarted the PC and then ran


co=coroutine.create(mover)
coroutine.resume(co)

which redefines co as the coroutine/thread of mover
What are you using to test it's return value?
KaoS #17
Posted 16 August 2012 - 11:49 AM
you just call it in the lua prompt and it prints out all output or you can run it like this

for _,w in pairs({coroutine.resume(co)}) do
print(w)
end
I haven't tried the second method but it's something I thought about
Pharap #18
Posted 16 August 2012 - 11:53 AM
you just call it in the lua prompt and it prints out all output or you can run it like this

for _,w in pairs({coroutine.resume(co)}) do
print(w)
end
I haven't tried the second method but it's something I thought about
In that case I know your problem.
The mover function goes too long without yielding, thus it encounters an error (which coroutine what tell you about, hence you should test the function before using it in a coroutine.)
KaoS #19
Posted 16 August 2012 - 11:57 AM
yes but there is a long delay in moving forwards and backwards isn't there? long enough to prevent a yield error and that still doesn't explain why the first function only moves forward
Pharap #20
Posted 16 August 2012 - 12:00 PM
yes but there is a long delay in moving forwards and backwards isn't there? long enough to prevent a yield error and that still doesn't explain why the first function only moves forward
Hang on, I have just realised the obvious.
THERE IS NO TURTLE.BACK()
KaoS #21
Posted 16 August 2012 - 12:05 PM
um dude…. there is…. I am running CC 1.3 and it works, I just did it right now
Pharap #22
Posted 16 August 2012 - 12:08 PM
um dude…. there is…. I am running CC 1.3 and it works, I just did it right now
In that case your coroutines are just being weird.
KaoS #23
Posted 16 August 2012 - 12:12 PM
hahaha, test this for me

function mover()
while true do
turtle.forward()
turtle.back()
end
end
then call mover() and the turtle will go back and forth until you terminate it, then try

test=coroutine.create(mover)
and resume test, all you will get back as output will be

true
turtle_response
and it will not move at all

I find that strange
BigSHinyToys #24
Posted 16 August 2012 - 12:41 PM
you are missing () from your turtle.back command
This is a demo of multi tasking using coroutines It is simple demo

local function mover()
    while true do
	    sleep(0.1) -- this creats a yeild point
	    turtle.forward()
	    turtle.back()
    end
end
local function userInput()
    while true do
	    term.clear()
	    term.setCursorPos(1,1)
	    local input = read()
    end
end
local function rednetCon()
    while true do
	    local event,id,sender = os.pullEvent()
	    if event == "rednet_message" then
		    turtle.turnLeft()
		    local curX,curY = term.getCurosrPos()
		    term.setCursorPos(1,3)
		    print("rednet message")
		    term.setCursorPos(curX,curY)
	    end
    end
end
rednet.open("right")
local threads = {}
table.insert(threads,coroutine.create(mover))
table.insert(threads,coroutine.create(userInput))
table.insert(threads,coroutine.create(rednetCon))
while true do
    local tEvents = {os.pullEvent()}
    for i = 1,#threads do
	    coroutine.resume(threads[i],unpack(tEvents))
    end
end

the above allows you to type in is also waiting for rednet and moving at the same time it it the power of coroutines to do lots of stuff at ounce.
KaoS #25
Posted 16 August 2012 - 01:24 PM
why the while true loop?

I had the brackets on the test, it moved back and forward…
KaoS #26
Posted 16 August 2012 - 01:45 PM
strange, while taking a look at this it only worked when you add the parameter you did, you can do it like this

coroutine.resume(co,unpack({os.pullEvent()}))
does anyone know why?
MysticT #27
Posted 16 August 2012 - 02:07 PM
Ok, this is pretty complex, but I'll try my best to explain it.
First of all, coroutines are not threads, they don't run at the same time. They work like this:
You create a coroutine, then start/resume it and it runs until the function (the one you used to create the coroutine) ends or it yields (using coroutine.yield).
If the function ended (successfully or not) the coroutine is "deleted", and it's status is "dead".
If it yielded, it returns to where you called coroutine.resume, returning all the arguments you passed to the yield call. Now, the coroutine is waiting to continue, wich happens when you call resume again.

Example:

local function f1()
  print("Hello")
end

local function f2()
  while true do
	print("Hello!")
	coroutine.yield()
	print("Some other message here...")
  end
end

local co = coroutine.create(f1)
coroutine.resume(co)

co = coroutine.create(f2)
coroutine.resume(co)

print("Hello to you")
sleep(1)

while coroutine.status(co) ~= "dead" do
  coroutine.resume(co)
  sleep(1)
end
Now, what will happen here is that the first function runs successfully and ends, so it returns. Then, the second function will yield, going back to the resume call, so you'll see the "Hello to you" message, and then it is run in a loop that keeps resuming it until it's dead (wich will never happen). When it's resumed, it will continue where it yielded and print the second message.
The output should be:

Hello
Hello!
Hello to you
Some other message here...
Hello!
Some other message here...
Hello!
Some other message here...
...

Now, the problem with your function is that the turtle functions yield to wait for a "turtle_response" event, so it returns to where you resumed it, and you never resume it again.

Hope you understand :(/>/>
KaoS #28
Posted 16 August 2012 - 02:34 PM
AH!!! finally, I did not know that turtle functions yield :(/>/> thanks a million
Lyqyd #29
Posted 16 August 2012 - 03:09 PM
yes but there is a long delay in moving forwards and backwards isn't there? long enough to prevent a yield error and that still doesn't explain why the first function only moves forward
Hang on, I have just realised the obvious.
THERE IS NO TURTLE.BACK()

What are you talking about? turtle.back() exists.

Edit: Mobile version is terrible at showing that there is a second page; disregard.
Pinkishu #30
Posted 16 August 2012 - 03:20 PM
Hmm I wonder if turtle.native.back() yields
KaoS #31
Posted 16 August 2012 - 03:22 PM
I know nothing about the native commands, I saw something about them in the BIOS but didn't investigate
Cloudy #32
Posted 16 August 2012 - 06:37 PM
Hmm I wonder if turtle.native.back() yields

Nope!
KaoS #33
Posted 17 August 2012 - 04:27 PM
yeah, it's actually quite interesting, if you make a function that uses native movements to make a turtle move in a loop without any sleep(0.1) and then launch it with the coroutines it works and eventually aborts due to too much time without yielding but the turtle keeps moving, you can shut the turtle down and it keeps moving… very strange right?
Pinkishu #34
Posted 17 August 2012 - 05:48 PM
yeah, it's actually quite interesting, if you make a function that uses native movements to make a turtle move in a loop without any sleep(0.1) and then launch it with the coroutines it works and eventually aborts due to too much time without yielding but the turtle keeps moving, you can shut the turtle down and it keeps moving… very strange right?
haha well IIRC the native commands queue-up so if you call it 10 times it wll move 10 times now since it probably moves slower than once every 0.1 seconds they pile up
interesting that it keeps going even after shutdown though
Cranium #35
Posted 17 August 2012 - 05:50 PM
I might be wrong, but maybe it's because Lua was told to stop, but Java wasn't? I think Java controls the actual position, but Lua interprets the commands?
KaoS #36
Posted 17 August 2012 - 05:50 PM
restart, whatever, it will keep looping no matter what… very strange
Cloudy #37
Posted 17 August 2012 - 06:41 PM
It won't loop indefinitely. The queue is 100 elements long - you're not able to put any more tasks in it than there is space. You can save the game and reload, and it will continue where it was.
Cloudy #38
Posted 01 September 2012 - 10:56 AM
WAIT A SECOND!!!

Thank you cloudy, you are now my hero lol, with this we can make functions that continue after exit, I will never lose a turtle in a mine again B)/>/> I can't believe I never thought of this

Except stuff that use logic will cause a blocker. And I accidentally deleted your post (who's idea was it to put quote next to delete) - but it's quoted there and I'll restore it when I get to my computer if I can :)/>/>
KaoS #39
Posted 01 September 2012 - 11:11 AM
hahaha, nice one.

you can queue up 100 moves and use an os.pullEvent() to track how far it is (because it returns 'turtle_response' every time it moves) and if you restart then it just stops the GUI but keeps moving, make a loop and your turtle will return, then in the turtle startup you tell it to set a timer for 5 seconds and keep pulling events until the timer expires, if it gets any turtle_response events then it knows it is still moving and can try work out what it going on etc, the awesomeness abounds :)/>/>