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

Timed coroutines

Started by BlueMond, 07 August 2014 - 02:23 PM
BlueMond #1
Posted 07 August 2014 - 04:23 PM
I do not often work with coroutines and am fairly new to them so I mostly do not understand how they work. I am trying to create a program which requires the main thread to be running a constant event pull on a specific event while a coroutine is ran that sleeps for a specified time and then runs a function when it wakes up. The problem is whenever I put sleep() into a coroutine, the coroutine stops running completely. Can someone tell me how to make a working timed coroutine?
Lyqyd #2
Posted 07 August 2014 - 05:32 PM
Please post your current code. You're probably not managing the coroutines correctly.
LeonTheMisfit #3
Posted 07 August 2014 - 06:40 PM
If you're actually trying to code the coroutines yourself perhaps you should have a look at the Parallel API. From your description and without seeing your code it sounds like maybe this is something you should consider if you haven't already.
Engineer #4
Posted 07 August 2014 - 07:36 PM
If you're actually trying to code the coroutines yourself perhaps you should have a look at the Parallel API. From your description and without seeing your code it sounds like maybe this is something you should consider if you haven't already.
I prefer not to use the parallel API, because you wont learn anything from it and that will result into not learning Lua completely. My believe is that ComputerCraft is there for both educatinal and amusement purposes.
BlueMond #5
Posted 07 August 2014 - 07:48 PM

local version = 1.2
local filename = "music"
local paste = "CSEcjQ7n"
local cr1 = nil

local function update()
    local url = "http://pastebin.com/raw.php?i="..paste
    local temp = http.get(url)
    local ver = temp.readLine()
    if tonumber(string.sub(ver, 17)) ~= version then
        fs.delete(filename)
        shell.run("pastebin get "..paste.." "..filename)
        shell.run(filename)
        return true
    end
    return false
end

local test = update()
if test == true then
    error()
end
rednet.open("top")
local times = {}
times.blocks = 345
times.far = 174
times.mall = 197
times.strad = 188
times.wait = 238

local songs = {"blocks", "far", "mall", "strad", "wait"}
local CurrentSong = 0

local function clearSong()
    term.setCursorPos(2,4)
    write("											    ")
end

local function writeSong()
    clearSong()
    term.setCursorPos(17,4)
    write("-"..songs[CurrentSong].."-")
end

local function pasteScreen()
    shell.run("clear")
    print("version: "..version)
    print("+----------~Computercraft Music Player~----------+")
    print("|											    |")
    print("|	   Current Song:						    |")
    print("|											    |")
    print("|											    |")
    print("|	   <-Previous	  >Play	   Next->	   |")
    print("|											    |")
    print("+------------------------------------------------+")
end

local function previousSong()
    if CurrentSong == 1 or CurrentSong == 0 then
        CurrentSong = 5
    else
        CurrentSong = CurrentSong - 1
    end
    writeSong()
    rednet.broadcast(songs[CurrentSong])
end

local function nextSong()
    if CurrentSong == 5 or CurrentSong == 0 then
        CurrentSong = 1
    else
        CurrentSong = CurrentSong + 1
    end
    writeSong()
    rednet.broadcast(songs[CurrentSong])
end

local function togglePlay()
    if CurrentSong == 0 then
        CurrentSong = 1
    end
    cr1(times[songs[CurrentSong]])
    writeSong()
    rednet.broadcast(songs[CurrentSong])
end

local function span(time)
    sleep(time)
    nextSong()
end
cr1 = coroutine.wrap(span)

pasteScreen()
while true do
    local e, button, x, y = os.pullEvent()
    if e == "mouse_click" then
        if y == 6 then
            if x >= 9 and x <= 18 then
                previousSong()
            elseif x >= 25 and x <= 29 then
                togglePlay()
            elseif x >= 37 and x <= 42 then
                nextSong()
            end
        end
    end
end

I probably would use the parallel api but I don't know how to use it and I couldn't find anything that explained it well enough.

Also, I have the coroutine being ran in togglePlay() only atm. I was just testing it first to see if it worked but the coroutine halts as soon as it hits the sleep()..
Edited on 07 August 2014 - 05:46 PM
BlueMond #6
Posted 07 August 2014 - 08:05 PM
As a smaller example:

local function test(x)
	 print("Sleeping for "..x)
	 sleep(x)
	 print("Slept for "..x)
end
local cr1 = coroutine.wrap(test)
cr1(5)

when I run something like this it will print "Sleeping for 5" and then the program ends..
Lyqyd #7
Posted 07 August 2014 - 08:27 PM
Sleep starts a timer and then immediately yields to wait for the timer event to come in. The timer event fires after the specified amount of time. The coroutine that sleep was called from receives the timer event and then continues.

Your code starts the timer from your coroutine, immediately yields to wait for the event, and then the rest of your script continues. You don't need a separate coroutine for this. Just start the timer and then handle the event in your main loop.
BlueMond #8
Posted 07 August 2014 - 08:41 PM
Ok, thanks.
0099 #9
Posted 07 August 2014 - 09:05 PM
As a smaller example:

local function test(x)
	 print("Sleeping for "..x)
	 sleep(x)
	 print("Slept for "..x)
end
local cr1 = coroutine.wrap(test)
cr1(5)

when I run something like this it will print "Sleeping for 5" and then the program ends..

Generally, all your CraftOS environment is a big coroutine (or a stack of coroutines inside each other). If you call coroutine.yield(), CraftOS interprets it as an instruction to pull an event. All function that is desiged to wait for something, use this system.

os.sleep() registers a timer and calls os.pullEvent("timer").
os.pullEvent() calls os.pullEventRaw() several times until it returns "timer" event.
os.pullEventRaw() just calls coroutine.yield().

If you use your own coroutines (maybe coroutine stack), then you have to transfer control all the way up to system-controlled level, and after this you need to transfer event information all the way down to pullEvent(), which will recognise timer event and send it to sleep(), which will recognise its own timer and return to your coroutine.


local function test(x)
	 print("Sleeping for "..x)
	 sleep(x)
	 print("Slept for "..x)
end
local cr1 = coroutine.create(test)
local args = {5}
while coroutine.status(cr1) ~= "dead" do
	 local info = { coroutine.resume(cr1, unpack(args)) } -- info table may contain some information about what event to expect
	 if coroutine.status(cr1) ~= "dead" the
		  args = { coroutine.yield(unpack(info)) } -- send information to the upper level and receive a response
	 end
end

This is complicated, but Parallel API does it for you. As long as all your coroutine-related work is done using Parallel API, it all works fine. What parallel function does, is it creates a coroutine for each of its arguments, runs all of them until each of them requests an event, then pulls an event from the upper levels (which can be managed by parallel function too) and tells all its coroutines about that event. This process is repeated until the coroutines "die".

You don't need to worry about using this API, as long as you understand what happens inside. This API is just a part of CraftOS, like any other, and it's designed to easily operate other parts of the OS. Lua itself has nothing to do with coroutine-stack-based architecture, but I think this architecture is great!
BlueMond #10
Posted 08 August 2014 - 08:02 PM
That explanation helps me to understand coroutines a lot better. Thanks for the help!