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

[solved] Coroutine might be yielding early?

Started by zxci, 27 November 2015 - 12:45 PM
zxci #1
Posted 27 November 2015 - 01:45 PM
I'm coding in my modemShark scanner functionality, and I've been stuck in the same place for two hours now, with zero progress made.

This code resides in a function(no args, for reasons). pMax, pMin, and modemSide are supplied externally. It uses pMin and pMax to send traffic over channels that are opened elsewhere in the program. For the sake of working with a normal CraftOS repeat program, traffic is being sent over 65533, so assume pMin and pMax are both 65533.

Spoiler

function scan()
				local pings = {n=(pMax - pMin)}
				local rChans = {}
				local i = 0
				for n=pMin,pMax do

								local x, y = term.getCursorPos()
								term.clearLine()
								term.setCursorPos(3,y)

								pings[n] = coroutine.create(function()
												--(mostly) standard rednet packet creation
												local nMessageID = math.random( 1, 2147483647 )										
												local nReplyChannel = n
												local tMessage = {
												nMessageID = nMessageID,
												nRecipient = n,
												message = "ayb",
												sProtocol = "abtu"}

												-- sending the packet
												peripheral.call( modemSide, "transmit", tMessage.nRecipient, nReplyChannel, tMessage

												print("sent message") == <<< this is a debug message that executes
												local event, mSide, mRCh, mSCh, mMsg, mDst = os.pullEvent("modem_message")
												print("got message") -- <<< this is a debug message that never executes

												if mMsg.nMessageID == nMessageID then coroutine.yield(true)
																else coroutine.yield(false)
												end
								end) -- end of coroutine
								print("Resuming co")
								local _, rsp = coroutine.resume(pings[n])
								if rsp == true then rChans[i] = n end
								i = i + 1
								sleep(.1)
				end

				print("--- CHANNELS THAT RESPONDED ---")

				if rChans[0] ~= nil then
								for i=0,#rChans do
												write(rChans[i] .. ", ")
								end
				else write("no responses across channel range")
				end

				local x, y = term.getCursorPos()
				term.setCursorPos(1,y)
				write("\n..>")
				sleep(10)
end
What ends up happening is that the program sends a packet. A repeater then sends the same packet back (twice, incidentally because the rednet's repeat() program wasn't designed to handle an incoming packet on 65533, headed for 65533 and not transmit it twice). I've verified with another computer that indeed the packets are being transmitted both ways. The program then SHOULD be waiting for a response, but I have no way of telling if it's actually doing that. All of the code up until:

local _, _, _, _, mMsg, _ = os.pullEvent("modem_message")
will execute flawlessly, and then it just dies. It definitely IS getting a response(two, infact) and they are on open modem channels, so I don't know what could be happening here.

Note that when I say it just dies, the program itself keeps running. The coroutine itself is what ceases to progress. It doesn't throw any errors, and any statements after that line of code above simply doesn't execute. However, the loop that created and resumed the coroutines will progress, and code after the loop is fine too.

Wat do?
Edited on 27 November 2015 - 02:27 PM
Lupus590 #2
Posted 27 November 2015 - 02:35 PM
rednet captures your modem_message before you get it and converts it. you should be waiting for a rednet_message
Edited on 27 November 2015 - 01:35 PM
Bomb Bloke #3
Posted 27 November 2015 - 02:40 PM
From bios.lua:

Spoiler
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 table.unpack( eventData )
end

os.pullEvent("modem_message") is hence much the same as coroutine.yield("modem_message"). You never resume the coroutines with the data of a modem_message event, and so the function you built your coroutines out of will never progress past the line where it requested one. Frankly I can't see why you're attempting to use coroutines here are all (BB's first rule of coroutines: No matter what you might think, you probably don't need to use coroutines).

Here's an untested re-write; please provide further details on your end goals if it doesn't suit your purposes:

Spoiler
local function scan()
	local tMessage = {message = "ayb", sProtocol = "abtu"}
	
	local ping, rChans = {}, {}

	for n = pMin, pMax do
		ping[n] = math.random( 1, 2147483647 )
		tMessage.nMessageID = ping[n]
		tMessage.nRecipient = n
		
		peripheral.call( modemSide, "transmit", n, n, tMessage ) -- You were missing a bracket here??
	end
	
	local myTimer = os.startTimer(5)
	
	while true do
		local myEvent = {os.pullEvent()}
		
		if myEvent[1] == "modem_message" and ping[myEvent[4]] and type(myEvent[5]) == "table" and myEvent[5].nMessageID == ping[myEvent[4]] then
			-- Event is a modem message, sender ID is defined in the ping table, message is in the form of a table, and there's an "nMessageID" key in that table with a value matching the one we recorded for that sender ID.
			rChans[#rChans + 1] = myEvent[4]
			
		elseif myEvent[1] == "timer" and myEvent[2] == myTimer then
			-- Timer has expired; if any senders have failed to respond at this point then they likely aren't going to at all.
			break
			
		end
	end
	
	print("--- CHANNELS THAT RESPONDED ---")

	if #rChans > 0 then
		for i = 1, #rChans do write(rChans[i] .. ", ") end
	else
		write("no responses across channel range")
	end

	write("\n..>")
	sleep(10)
end

rednet captures your modem_message before you get it and converts it. you should be waiting for a rednet_message

That in no way prevents you from capturing the modem_message event yourself, as the function that performs this task - rednet.run() - executes in a coroutine separate to the shell.
zxci #4
Posted 27 November 2015 - 02:49 PM
I'll give that a try after I get some sleep Bomb. That missing bracket was a result of me selecting too much when I was removing a print statement last-minute lol

My idea behind using coroutines (note: I am very fuzzy on coroutine knowledge) was that they would be able to run for indeterminate amounts of time without bogging down the program and then some part or another of my brain said "hey you can even use coroutines.yield() to pass flags!" and that led to me making a coroutine lol

Also, ya, Lupus I'm explicitly using modem_message to access the messageID. You can use os.pullEvent("modem_message") just fine and there are certain tasks only it can do as opposed to "rednet_message"



edit: I got impatient.
That worked the first try! It seems to do what I was trying to accomplish with my convoluted(I actually put a comment in there that I took out prior to posting that pointed out to myself how convoluted it was) method and it's easy on the eyes too LOL. I appreciate it.
Edited on 27 November 2015 - 02:26 PM