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

parallel.waitForAny

Started by discodancepant, 17 March 2015 - 01:13 AM
discodancepant #1
Posted 17 March 2015 - 02:13 AM
I have more of a request than a question.

I would like someone to explain to me (and anyone else who doesnt know) how to proplerly use the parallel api in a sitaution involving user input (such as os.pullEventRaw).

I have a simple program that i am developing for (advanced) turtles which needs to accept user input (via mouse) at the same time as running a prgram in the background, which includes displaying to the screen. At times the screen display will be "flashing" different colors due to a specific mode selected. The rate of the flashing in my program is controlled by the sleep() functon, which i am sure is the culprit of my problem.

Here is my current most basic code. When the "button" are clicked the are supposed to blink red and black.
Spoiler

t = {}
t[1] = {["sX"] = 3,["eX"] = 18,["sY"] = 3,["eY"] = 5,["BsX"] = 6,["BsY"] = 4,["text"] = "NOT SYNCED",["BsX2"] = 8,["BsY2"] = 4,["text2"] = "SYNCED",["G2G"] = true}
t[2] = {["sX"] = 22,["eX"] = 37,["sY"] = 3,["eY"] = 5,["BsX"] = 25,["BsY"] = 4,["text"] = "ELT ACTIVE",["BsX2"] = 24,["BsY2"] = 4,["text2"] = "ELT INACTIVE",["G2G"] = false}
t[3] = {["sX"] = 3,["eX"] = 18,["sY"] = 9,["eY"] = 11,["BsX"] = 8,["BsY"] = 10,["text"] = "PAUSED",["BsX2"] = 6,["BsY2"] = 10,["text2"] = "NOT PAUSED",["G2G"] = true}
tick = 0
function drawDesktop()
term.setBackgroundColor(128)
term.clear()
term.setTextColor(1)
for i = 1,3 do
  if t[i]["G2G"] == false then
   if tick >= 0 and tick <= 4 then
	term.setBackgroundColor(16384)
   else
	term.setBackgroundColor(32768)
   end
  else
   term.setBackgroundColor(8192)
  end
  for ii = t[i]["sY"],t[i]["eY"] do
   for iii = t[i]["sX"],t[i]["eX"] do
	term.setCursorPos(iii,ii)
	write(" ")
   end
  end
  if t[i]["G2G"] == false then
   term.setCursorPos(t[i]["BsX"],t[i]["BsY"])
   write(t[i]["text"])
  else
   term.setCursorPos(t[i]["BsX2"],t[i]["BsY2"])
   write(t[i]["text2"])
  end
end
term.setCursorPos(1,1)
return
end
function toggle(toggleInt)
if t[toggleInt]["G2G"] == false then
  t[toggleInt]["G2G"] = true
elseif t[toggleInt]["G2G"] == true then
  t[toggleInt]["G2G"] = false
end
return
end
function mouseInput()
local event, button, X, Y = os.pullEventRaw()
if event == "mouse_click" then
  for i=1,3 do
   if X >= t[i]["sX"] and X <= t[i]["eX"] and Y >= t[i]["sY"] and Y <= t[i]["eY"] and button ==1 then
	toggle(i)
   end
  end
end
end
function desktop()
drawDesktop()
sleep(.1)
tick = tick+1
if tick == 10 then
  tick = 0
end
end
while true do
parallel.waitForAny(mouseInput, desktop)
end

I hope you can help me understand this better. If you are going to say Google it … please don't. I have. It hasn't helped. Or maybe I'm not phrasing my problem in the correct way to get an answer from google.
HPWebcamAble #2
Posted 17 March 2015 - 02:37 AM
First of all:

term.setBackgroundColor(16384)

--# Instead of the number, you can use the colors API

term.setBackgroundColor(colors.red)

Now for your question

Basically, the parallel API runs several functions simultaneously.
Now, Lua doesn't actually support multiple threads, so they aren't ACTUALLY running at the same time, but you don't need to know how that works right now.

'parallel.waitForAny()' runs the functions until ONE stops
'parallel.waitForAll()' runs the functions until they all stop

So instead of doing this:

while true do
  parallel.waitForAny(mouseInput,desktop)
end

--# You would do this:

function mouseInput()
  while true do
    --# Existing code goes here
  end
end

parallel.waitForAny( mouseInput , desktop)

Personally, I would just use a single function, without the parallel API (Although it really works either way)

function drawDesktop()
  --# Draws the desktop
end

local timer = os.startTimer(0.1) --# Start a timer for 0.1 seconds. Triggers an event when it ends
while true do
  local event, p1, p2, p3, p4, p5 = os.pullEvent()
  if event == "timer" and p1 == timer then --# the variable 'timer' is a unique value to keep track of which timer was triggered
    drawDesktop()
    timer = os.startTimer(0.1)
  elseif event == "mouse_click" then
    --# Code to check if a button was clicked
  end
end
Bomb Bloke #3
Posted 17 March 2015 - 06:36 AM
You would likewise want to rig your desktop() function to loop itself.

The idea is that when a function started by parallel.waitForAny ends, it throws out the other functions it's running. Calling parallel.waitForAny() again with the same functions causes them to start from scratch. It is generally better to write your functions so that they will run until you're done with them, rather than restarting them over and over.

The functions don't actually run at the same time, but rather control passes between them whenever they yield (which they do whenever they need to pull events, eg because you called os.pullEventRaw(), or sleep() (which calls os.pullEvent()), etc). Thus any functions "running" that're halted by one of the other functions finishing, would effectively return at the point where they yielded last.

That said, despite your misuse of the parallel API, it looks the logic should work nearly as you intended it to anyways. Could you elaborate on the problem? It sounds like you're saying the whole screen is flashing different colours, but I can't see anything in there that'd cause that.
discodancepant #4
Posted 17 March 2015 - 11:56 AM
I think I misunderstood what parallel.waitForAll did. I thought it was the opposite of waitForAny in that it would not restart any function called until all the functions yield. When I changed my parallel use to all it worked fine.

After looking at your revised script I see that it would work the same as mine in theory. What are the advantage of not using parallel in your code:

function drawDesktop()
  --# Draws the desktop
end

local timer = os.startTimer(0.1) --# Start a timer for 0.1 seconds. Triggers an event when it ends
while true do
  local event, p1, p2, p3, p4, p5 = os.pullEvent()
  if event == "timer" and p1 == timer then --# the variable 'timer' is a unique value to keep track of which timer was triggered
    drawDesktop()
    timer = os.startTimer(0.1)
  elseif event == "mouse_click" then
    --# Code to check if a button was clicked
  end
end

The problem I had before is that the timer I was using didn't have enough time to run before my call to os.pullEvent yielded a result. The screen kept flashing and resetting. It never had a chance to change the color after my preset amount of time.
Bomb Bloke #5
Posted 17 March 2015 - 01:02 PM
Ah, so you're saying your colours weren't changing. Yeah, I can see how that would be the case.

Let's examine the order of execution. After your initial definitions were completed, you would run this:

while true do
parallel.waitForAny(mouseInput, desktop)
end

First, that started up the mouseInput function and executed this:

function mouseInput()
local event, button, X, Y = os.pullEventRaw()

At that point, the code yielded (in order to pull an event) and control was passed to the desktop function:

function desktop()
drawDesktop()
sleep(.1)

The sleep() function operates by rigging a timer, then waiting for a timer event to appear in the queue. It yielded in order to do so, and when the timer event occurred, control was passed back to the mouseInput function:

if event == "mouse_click" then
  for i=1,3 do
   if X >= t[i]["sX"] and X <= t[i]["eX"] and Y >= t[i]["sY"] and Y <= t[i]["eY"] and button ==1 then
        toggle(i)
   end
  end
end
end

That function, as it didn't yield again, completed. parallel.waitForAny discarded the state of the unfinished desktop function, and the while loop restarted both mouseInput and desktop from scratch. tick never incremented under this system.

With parallel.waitForAll, both functions have to execute to completion before the script continues on. So after mouseInput finishes and subsequently returns, desktop gets control again and runs this:

tick = tick+1
if tick == 10 then
  tick = 0
end
end

… and then your while loop starts both functions from scratch again.

Ditching the use of the parallel API, as HPWA suggests, has a couple of benefits. One, your code's shorter (you don't have the extra function declaration fluff), two, it runs faster (you're not constantly calling and re-calling functions). It also allows you to click faster than once per tenth of a second, though you could also achieve that by implementing individual while loops within the functions themselves (as was also suggested).

That said, there's quite a few things you could do to shrink this script. Beats me as to whether you're interested, but one example in particular that bugs me is that you could change your toggle(i) call to t[i]["G2G"] = not t[i]["G2G"], then delete the toggle function declaration completely.
discodancepant #6
Posted 18 March 2015 - 01:11 AM
Ditching the use of the parallel API, as HPWA suggests, has a couple of benefits. One, your code's shorter (you don't have the extra function declaration fluff), two, it runs faster (you're not constantly calling and re-calling functions). It also allows you to click faster than once per tenth of a second, though you could also achieve that by implementing individual while loops within the functions themselves (as was also suggested).

That said, there's quite a few things you could do to shrink this script. Beats me as to whether you're interested, but one example in particular that bugs me is that you could change your toggle(i) call to t["G2G"] = not t["G2G"], then delete the toggle function declaration completely.

I am currently building the framework for my program to run on the turtles. That boolean will probably become a number, because there are going to be 4 states as i continue, instead of two. However, I didn't realize you could simply invert a boolean with "not", so hey I've learned a lot. I plan to have around 30 turtles max running the program silmutaniously when i am finished, so any hints that would help me improve my code are welcome!

Thank you both for your answers! I wish on the pages that go over the parallel api that they would break it down as you have.

I ran a test program as a proof of concept and the results were interesting to me.
Here is the program:
Spoiler

function one()
	print("1")
	sleep(.1)
	print("2")
	sleep(.1)
	print("3")
	sleep(.1)
	print("4")
	sleep(.1)
	print("5")
	sleep(.1)
end
function two()
	print("one")
	sleep(.1)
	print("two")
	sleep(.1)
	print("three")
	sleep(.1)
	print("four")
	sleep(.1)
	print("five")
	sleep(.1)
end
parallel.waitForAll(one, two)
My results are:

1
one
two
2
3
three
four
4
five
5

I changed my sleep times from .1 to 1, and the results were the exact same. Then i ran the same program a few times consecutively and I had mixed results. Twice the program ran as expected, alternating evenly between the two functions, however it does occasionally result with a variation of the above values.