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

Mix usage of coroutine and turtle?

Started by hyamw, 01 March 2013 - 05:45 PM
hyamw #1
Posted 01 March 2013 - 06:45 PM
I'm trying to create a mining program. when I was using turtle function within a coroutine function, I found that the turtle function is not working.
Then I create a simple code to test it:

local func = coroutine.create(function()
print("1")
turtle.turnRight()
print("2")
coroutine.yield()
print("3")
turtle.up()
print("4")
coroutine.yield()
print("5")
turtle.down()
end)

local function s()
print(coroutine.status(func))
coroutine.resume(func)
print(coroutine.status(func))
coroutine.resume(func)
print(coroutine.status(func))
coroutine.resume(func)
print(coroutine.status(func))
end
s()

The output is:

1
suspended
suspended
suspended
dead

If I comment turtle functions. the output will be "1 2 3 4" as what I expected.
Is that means I cannot use turtle functions inside coroutine?
Lyqyd #2
Posted 01 March 2013 - 08:26 PM
Split into new topic.

Turtle movement calls rely on events to determine the success status of the movement attempt. You aren't passing these events in. Your over-simplification is causing this code not to work. Take a look at how the parallel API handles event distribution between coroutines.
hyamw #3
Posted 02 March 2013 - 05:03 AM
Thanks for your reply. I checked the parallel API, and modified my test code. There is still a problem.
os.pullEventRaw() is blocking the execution…




local function turn()
print("1")
turtle.turnLeft()
print("2")
coroutine.yield()
print("3")
turtle.up()
print("4")
coroutine.yield()
print("5")
turtle.down()
print("6")
end

local s = coroutine.create(function()
turn()
end)

local function runUntilLimit( routine )
    local tFilter = nil
    local eventData = {}
    while true do
     local r = routine
if r then
if tFilter == nil or tFilter == eventData[1] or eventData[1] == "terminate" then
local ok, param = coroutine.resume( r, unpack(eventData) )
if not ok then
error( param )
else
tFilter = param
end
if coroutine.status( r ) == "dead" then
routine = nil
return
end
end
end
eventData = { os.pullEventRaw() }
    end
end

runUntilLimit(s)

Now the I have output "1 2" which means that turtle.turnLeft() is passed but coroutine.yield() is blocked….
it's blocked by os.pullEventRaw() function after breaks from coroutine.yield()

how to detect that if i need to call os.pullEventRaw() function?
Lyqyd #4
Posted 02 March 2013 - 08:48 AM
Take out the raw yields in your coroutine. You yield to wait for an event, so this is again a case of your code working perfectly well, doing exactly what you told it to. You simply don't realize that what you're telling it to do and what you want/expect it to do are very different things. Did you try pressing keys when it paused to wait for events after the turn? Generating events should resume the coroutine as expected.
hyamw #5
Posted 02 March 2013 - 01:37 PM
Yes, when the execution is blocked. I need to press key to continue. But the issue is like what you said, the turtle movement relys on the events.
If I removed the os.pullEventRaw() function call. The program will crash with only output "1" (the infinite loop make the crash). Function execution is blocked inside the turtle function.

BTW, I tried another test with parallel.waitForAny function, it has the same issue (I need to press any key to continue execution)

local t = function()
    print("1")
    coroutine.yield()
    print("2")
    coroutine.yield()
    print("3")
end
parallel.waitForAny(t)
Lyqyd #6
Posted 02 March 2013 - 01:55 PM
That is expected behavior. If you tell it to yield (and thereby wait for events), of course it will yield and wait for events. You did exactly the opposite of what I suggested you do. You removed everything except the coroutine.yield calls in your coroutine. Since their unnecessary presence is causing the behavior you're objecting to, they are the very thing you needed to remove! Why do you insist on using the raw coroutine.yield calls? You clearly do not understand what they actually do, and you seem to be unwilling to listen.
hyamw #7
Posted 02 March 2013 - 01:59 PM
As mentioned above, os.pullEventRaw() need an event to continue, so I tried to add an dummy event myself.
I used os.startTimer to create a 10 timer events every seconds. Finally, it makes the programs execution done.
But I don't like this way. It's something like a patch.
Is there any other way to solve this issue?
Lyqyd #8
Posted 02 March 2013 - 02:02 PM
There is apparently no way to resolve your refusal to use coroutines correctly. If you start using them correctly, the issue will not exist.

Edit: I'm feeling merciful:


local function turn()
    print("1")
    turtle.turnLeft()
    print("2")
    turtle.up()
    print("3")
    turtle.down()
    print("4")
end

local s = coroutine.create(turn)

local function runUntilLimit( routine )
    local tFilter = nil
    local eventData = {}
    while true do
        local r = routine
        if r then
            if tFilter == nil or tFilter == eventData[1] or eventData[1] == "terminate" then
                local ok, param = coroutine.resume( r, unpack(eventData) )
                if not ok then
                    error( param )
                else
                    tFilter = param
                end
                if coroutine.status( r ) == "dead" then
                    routine = nil
                    return
                end
            end
        end
        eventData = { os.pullEventRaw() }
    end
end

runUntilLimit(s)
hyamw #9
Posted 02 March 2013 - 02:10 PM
I'm sorry that I misunderstood your words.
here is the reason why I want to use coroutine.yield()

I have a very complex function call (with lot of sub function calls inside), but I want to break the function call at any time if I received a message from controller side.
I can do it by checking a flag and return the function, but that means I need to duplicate the code everywhere. It makes the code looks more complex.
So I'm thinking about using coroutine.yield() function to achieve the same result, I only need to check the result in the main while loop, and simply call coroutine.yield() function inside sub functions where I need to.

The 1st solution should be something like this:

function c()
	-- ....Do something
	if stop_actioin then
		return
	end
	-- ....Do something
	if stop_actioin then
		return
	end
	....
end
function b()
	-- ....Do something
	if stop_actioin then
		return
	end
	-- ....Do something
	if stop_actioin then
		return
	end
	....
end
function a()
	-- ....Do something
	if stop_actioin then
		return
	end
	-- Call sub functions
	b()
	if stop_actioin then
		return
	end
	-- Call sub functions
	c()
	if stop_actioin then
		return
	end
	....
end
a()

The 2nd solution would be:

function c()
	-- ....Do something
	coroutine.yield()
	-- ....Do something
	coroutine.yield()
	....
end
function b()
	-- ....Do something
   coroutine.yield()
	-- ....Do something
   coroutine.yield()
	....
end
local a = coroutine.create(function ()
	-- ....Do something
   coroutine.yield()
	-- Call sub functions
	b()
   coroutine.yield()
	-- Call sub functions
	c()
   coroutine.yield()
	....
end
)
while true do
	coroutine.resume(a)
	if stop_action then
		break
	end
end

I can also call some common functions in the main loop instead of call them everywhere
hyamw #10
Posted 02 March 2013 - 02:20 PM
Anyway I found another solution which I think is better:

local function turn()
print("1")
turtle.turnLeft()
print("2")
coroutine.yield()
print("3")
turtle.up()
print("4")
coroutine.yield()
print("5")
turtle.down()
print("6")
end
local s = coroutine.create(function()
turn()
end)
local function runUntilLimit( routine )
    local tFilter = nil
    local eventData = {}
    while true do
	 local r = routine
  local skipEvent = true
  if r then
   if tFilter == nil or tFilter == eventData[1] or eventData[1] == "terminate" then
    local ok, param = coroutine.resume( r, unpack(eventData) )
    if not ok then
	 error( param )
    else
	 tFilter = param
    end
   
    if param ~= nil then
	 skipEvent = false
    end
    if coroutine.status( r ) == "dead" then
	 routine = nil
	 return
    end
   end
  end
  if not skipEvent then
   eventData = { os.pullEventRaw() }
  else
   eventData = {}
  end
    end
end
runUntilLimit(s)

I realized that I only need to call os.pullEventRaw() when coroutine.resume returns a param is not nil. At least the turtle movement function will return a "turtle_response" as param.
Lyqyd #11
Posted 02 March 2013 - 02:25 PM
Why not just put the message checking logic in the control loop itself, since the mining program is already going to yield every time the turtle moves? You don't need additional yields in the code when you aren't waiting for events.
hyamw #12
Posted 02 March 2013 - 02:33 PM
Why not just put the message checking logic in the control loop itself, since the mining program is already going to yield every time the turtle moves? You don't need additional yields in the code when you aren't waiting for events.
Ah, you are right!!!!!!!!!
I didn't notice that at all.
In a normal way, the turtle movement function always returns until the it's finished. I didn't realize that it has yield inside….
Thank you very much for your help!