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

Prevent event eating actions from eating events...

Started by Guido, 25 September 2016 - 01:08 PM
Guido #1
Posted 25 September 2016 - 03:08 PM
Hello Everyone,

I'm having trouble with losing messages (read: events). I tried building confirmations and resends. Also run rednet.receive parallel.

After a lot of reading about coroutines I found a post about "call's that eat events". Named examples were sleep, turtle.forward, etc.). With new inspiration for googling I got back to the rednet.receive wiki page. It says the solution is in coroutines and parallel. I don't see the solution yet. Since both coroutines and parallel are just fast switching mechanisms, this means the turtle is still moving and eating my precious events.

In the meantime I decided to one main event processing function with uses os.pullEvent without an filter. I thought that might be better than several calls to os.pullEvent with a specific filter.

Question 1: I guess there is a commen architecture for receiving messages and moving at the same time. Could someone head me in the right direction?

Quiestion 2: Is there a list of event eating call somewhere?

Thanks in advance!!
Lyqyd #2
Posted 25 September 2016 - 05:24 PM
Please post your code.

1. The usual way is to have a "communications" coroutine and a "movements" coroutine. The former is used only for rednet, the latter is used for everything else.

2. Not really.
Guido #3
Posted 25 September 2016 - 07:47 PM
Let me see if I understand, and give an example to future searchers for the same solution:


-- do stuff to initialize the program

function CoEvents()
  while true do
	local event, p1, p2, p3, p4, p5 = os.pullEvent()
	-- process the event here
  end
end

function CoTasks()
  while true do
    local task = GetNextThingToDo()

    if task ~= nil then
      DoTheTask( task )
    else
      coroutine.yield( 'Nothing to do' )
    end
  end
end

local coEvent = coroutine.create(CoEvents)
local coTask  = coroutine.create(CoTasks)

while true do
  coroutine.resume(coEvent)
  coroutine.resume(coTask)
end

The above is not what you wrote, since my first function captures all events, not just the modem events. I don't want to miss any event (timers, etc), but maybe I'm overseeing something.

edit: outline code for better reading
Edited on 25 September 2016 - 05:50 PM
KingofGamesYami #4
Posted 25 September 2016 - 08:01 PM
That won't work; the computercraft event system is far more complicated. Unless what you are doing requires customized control of coroutines, I would advise you to use parallel.
Guido #5
Posted 25 September 2016 - 08:14 PM
Would the parallel solution look something like:


-- do stuff to initialize the program
function ParallelEvents()
  while true do
	local event, p1, p2, p3, p4, p5 = os.pullEvent()
	-- process the event here
  end
end

function ParallelTasks()
  while true do
	local task = GetNextThingToDo()
	if task ~= nil then
	  DoTheTask( task )
	else
	  -- I cannot use times here because it would pull events too, right?
	  coroutine.yield()
	end
  end
end

parallel.waitForAny( ParallelEvents, ParallelTasks )

edit: again outline code for better reading
Edited on 25 September 2016 - 06:16 PM
KingofGamesYami #6
Posted 25 September 2016 - 08:21 PM
Yes, that would indeed work. There are some minor differences between that and the system I would use, but that's a minor issue and I am on mobile RN.
Bomb Bloke #7
Posted 26 September 2016 - 12:57 AM
Since both coroutines and parallel are just fast switching mechanisms, this means the turtle is still moving and eating my precious events.

First you need to understand that each ComputerCraft system runs its code within a coroutine - one single Lua VM handles every computer / turtle within the one Minecraft world.

When you call os.pullEvent(), really you're causing your current coroutine to yield; more specifically, it's a wrapper for coroutine.yield(), and it calls that function for you. ComputerCraft resumes that coroutine when something happens - we call the data passed in at the time of resumption "an event".

An "event eater" is a function that yields over and over until resumed with a certain event. If it's resumed with an event that doesn't concern it, it ignores it and simply yields again. For example, turtle.forward() yields until resumed with a certain turtle_response event - if it's resumed with a rednet_message event before that time, you won't have an opportunity to act on it before the forward function essentially throws it away. Under normal circumstances, you can't switch to a rednet.receive() call while a turtle.forward() call is executing.

When you use the parallel API to run multiple functions in separate coroutines (running within the coroutine you started off with), any events your main coroutine is resumed with are then passed on to each of the child coroutines. If one of the child coroutines calls an "event eater" function (eg sleep(), turtle.forward(), whatever), this will not prevent the parent from resuming the other child coroutine with a copy of the same event data.

So, if you call turtle movement functions within one function, and call rednet-based functions within another, the parallel API can be used to run those functions within separate coroutines - each gets resumed with all event data they request, and you can switch from one to the other while either turtle movement calls or the rednet message receiver are yielding within each.

Spoiler
local commands = {}

local function getMessages()
	while true do
		local senderID, message = rednet.receive()

		if senderID == whatever then
			commands[#commands + 1] = message
		end
	end
end

local function doWork()
	while true do
		if #commands > 0 then
			-- Act on commands[1]
			-- Make the turtle move around or whatever
			
			table.remove(commands, 1)  -- Remove first entry in table, next entry up will be pulled down into first index
		else
			os.pullEvent("rednet_receive")  -- Yield until something might be added to the command list
		end
	end
end

parallel.waitForAny(getMessages, doWork)

Is there a list of event eating call somewhere?

The rule is that any function which waits for anything is yielding in order to do it, and will therefore pull and discard all events until it gets the one which signals what it's waiting for is ready.

For example, turtle movements take time (and wait for turtle_response events which confirm/deny whether the movements completed successfully), sleeping takes time (and waits for a timer event signalling that the duration is up), waiting for a rednet message may potentially take a lot of time (and waits for either a rednet_message OR a timer event)…

Some peripherals from third-party mods also trigger yields when you call their functions, but the above three are the ones most ComputerCraft coders are likely to run into.
Edited on 25 September 2016 - 11:07 PM
Guido #8
Posted 26 September 2016 - 10:03 PM
Thanks Bob for this information, I wasn't aware events were sent to all threads. Just two more quenstions and I'm off again working on my project:

1) People have one parallel function for rednet messages (or modem messages), that means other messages could be eaten. Like timer events. Why not one function that reads all events?

2) I would like to send a confirmation when a message is received. Is rednet.send() or modem.transmit() yielding? Not sure what the easiest way is to find out, a small script goes parallel, one just logging all events, the other testen the function call…
KingofGamesYami #9
Posted 26 September 2016 - 10:36 PM
1) os.pullEvent without a parameter. Typically, people find it easier to work with functions that block until completion. I have created an API which allows you to use a single function, rather than multiple coroutines (Well, it does use coroutines itself but…)


2) No, they do not.
Edited on 26 September 2016 - 08:43 PM
Bomb Bloke #10
Posted 27 September 2016 - 12:38 AM
People have one parallel function for rednet messages (or modem messages), that means other messages could be eaten. Like timer events. Why not one function that reads all events?

os.pullEvent without a parameter. Typically, people find it easier to work with functions that block until completion. I have created an API which allows you to use a single function, rather than multiple coroutines (Well, it does use coroutines itself but…)

Indeed, if you can use os.pullEvent() to handle multiple event types, that's the way to go. For example, instead of calling rednet.receive(), you might use os.pullEvent() and check to see if the event type is "rednet_message".

Catch is this isn't always an option - there's no way to make a turtle move without also having the function yield until it gets a turtle_response event, for example. This is when the parallel API comes in handy.

Is rednet.send() or modem.transmit() yielding? Not sure what the easiest way is to find out, a small script goes parallel, one just logging all events, the other testen the function call…

This code snippet can be used for testing functions you're suspicious of. Simply modify myFunc to call whatever it is you want to test.

In addition to the ones already mentioned, most functions in the commands, http, and gps APIs yield. Outside of those it's pretty rare.
Edited on 26 September 2016 - 10:38 PM