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

[Solved] "Non-blocking" os.pullEventRaw()

Started by RunasSudo-AWOLindefinitely, 15 January 2013 - 10:20 PM
RunasSudo-AWOLindefinitely #1
Posted 15 January 2013 - 11:20 PM
I'm coding a "threading" API, which wraps around the coroutine API (like parallel), but I need a way (to implement coroutine.yield(event) functionality used by sleep and pullEvent) while running other coroutines.
Code (only run function is relevant):
Spoiler

local threads = {}
Thread = {}
Thread.resume = function(self)
self.running = true
end
Thread.pause = function(self)
self.running = false
end
function createThread(func)
local thr = {}
thr.__index = Thread

thr.func = func
thr.running = false
thr.waitingFor = nil
local cor = coroutine.create(func)
thr.cor = cor

setmetatable(thr, thr)

threads[#threads + 1] = thr

return thr
end
local function countAlive()
local alive = 0
for i = 1, #threads do
  local thread = threads[i]
  if thread ~= nil then
   if coroutine.status(thread.cor) ~= "dead" and thread.running == true then
	alive = alive + 1
   end
  end
end

return alive
end
function run()
local eventData = {}
while countAlive() > 0 do
  for i = 1, #threads do
   local thread = threads[i]
   if thread ~= nil then
	if (thread.waitingFor == nil or thread.waitingFor == eventData[1]) and coroutine.status(thread.cor) ~= "dead" and thread.running == true then
	 local success, message = coroutine.resume(thread.cor, unpack(eventData))
	 if success == false then
	  error(message)
	 end
	 thread.waitingFor = message
	end
   end
  end
  eventData = { os.pullEventRaw() }
end
end
The problem is that the run() function hangs/blocks at the os.pullEventRaw line, preventing other coroutines from running.
As far as I can tell, I'm doing this in the same way as the parallel API, but I can't work out what I'm doing wrong.

(Forum indentation is (still readable, but) kinda derpy, sorry!)

EDIT: I'm currently doing a os.startTimer(0.5) before pulling an event as a workaround, but I'm wondering if there's a better way (parallel doesn't seem to do this)
Edited on 16 January 2013 - 10:49 PM
Lyqyd #2
Posted 16 January 2013 - 06:58 AM
What do you mean it hangs/blocks? What code are you running in this? What behavior are you expecting, and what behavior are you observing instead? If I understand you correctly, the "problem" is that it's waiting for events correctly.
RunasSudo-AWOLindefinitely #3
Posted 16 January 2013 - 12:09 PM
What do you mean it hangs/blocks? What code are you running in this? What behavior are you expecting, and what behavior are you observing instead? If I understand you correctly, the "problem" is that it's waiting for events correctly.
Err…. You do understand the meaning of "block", right? I mean that instead of waiting until an event is fired, it checks if an event has been fired since the last call, returns it if it has, or returns nil if there has been no event.

http://stackoverflow...-in-programming

EDIT: To clarify, os.pullEventRaw() performs as expected in this code (it blocks), but I don't want it to perform as such.
I want the while true loop to continue regardless of whether an event has been fired.
Edited on 16 January 2013 - 11:10 AM
MysticT #4
Posted 16 January 2013 - 02:19 PM
That's the way ComputerCraft works, you yield to wait for an event to allow other computers to run. Making an infinite loop that doesn't yield until there's an event wouldn't let other computers work.
Your workaround (starting a timer to fire an event) should work (you could also queue a fake event so it doesn't wait for the timer), but note that it would make computers lag a lot (if there's more than one computer in the world).
Why would you want to do it anyway? there's no need to, you just have to make the "threads" work like that (wait for an event then act accordingly).
RunasSudo-AWOLindefinitely #5
Posted 16 January 2013 - 02:38 PM
That's the way ComputerCraft works, you yield to wait for an event to allow other computers to run. Making an infinite loop that doesn't yield until there's an event wouldn't let other computers work.
I'm not sure you understand what I want to do.
When one of my threads runs coroutine.yield("mouse_click") for example, my API needs to not resume it again until it has pulled a mouse_click event. In the meantime, however, it should still be running other coroutines. This implementation, however, does not let any other coroutines run until it has pulled said event.

I have a window manager that spawns two threads. One updates a clock, and one waits for a mouse_click, then exits the program. With this implementation, it does not run the clock-updating function until an event has been pulled. Not what I want to happen. When I use the parallel API to run both functions at the same time, it works.
RunasSudo-AWOLindefinitely #6
Posted 16 January 2013 - 02:40 PM
there's no need to, you just have to make the "threads" work like that (wait for an event then act accordingly).
Err…….. Just.. What? That makes no sense…
My thread calls os.pullEvent("mouse_click"), which calls coroutine.yield("mouse_click"), which yields the coroutine, sending it back to my thread manager (code in the OP). There's no way I can "make the threads … wait for an event then act accordingly" without implementing the functionality in my thread manager.
MysticT #7
Posted 16 January 2013 - 03:14 PM
I'm not sure you understand what I want to do.
When one of my threads runs coroutine.yield("mouse_click") for example, my API needs to not resume it again until it has pulled a mouse_click event. In the meantime, however, it should still be running other coroutines. This implementation, however, does not let any other coroutines run until it has pulled said event.

I have a window manager that spawns two threads. One updates a clock, and one waits for a mouse_click, then exits the program. With this implementation, it does not run the clock-updating function until an event has been pulled. Not what I want to happen. When I use the parallel API to run both functions at the same time, it works.
Well, you said that you wanted to run the loop even when there's no events:
EDIT: To clarify, os.pullEventRaw() performs as expected in this code (it blocks), but I don't want it to perform as such.
I want the while true loop to continue regardless of whether an event has been fired.
(Also the topic title is "Non-blocking" os.pullEventRaw()) That's why I said that.
For what I see, that code should work. Can we see the code you use to start the threads and what they run maybe?
RunasSudo-AWOLindefinitely #8
Posted 16 January 2013 - 04:26 PM
For what I see, that code should work. Can we see the code you use to start the threads and what they run maybe?
"That code" being with or without the workaround? Because I'm looking for a better way than the workaround I have. Anyway:

drawThread, inputThread = nil, nil
function draw()
  -- Do some stuff that updates the clock widget
end
function processInput()
  os.pullEvent("mouse_click")
  drawThread:pause() -- This will cause the program to exit (all threads dead)
end
drawThread = threading.createThread(draw)
inputThread = threading.createThread(processInput)
drawThread:resume()
inputThread:resume()
threading.run()
Something along the lines of that.
Without the workaround, the clock doesn't update until I click the mouse, and then I need to click the mouse again for the program to close.
GopherAtl #9
Posted 16 January 2013 - 05:36 PM
it's not clear to me that you're doing anything wrong from the code. It looks, as you said, effectively like parallel. All coroutines should run every time you get an event, except those coroutines waiting for a specific, other event type. You say you want a timer to be updating; if your timer coroutine is not setting timers itself every time it updates, then that is most likely the issue. Things don't just run in cc, they run in response to events. No events, no running. It's the only reasonable way to emulate a lot of computers on one computer (the mc server) without either using extreme amounts of CPU time or putting tight clamps on how much fast each of the computers is allowed to run.
Lyqyd #10
Posted 16 January 2013 - 06:41 PM
What do you mean it hangs/blocks? What code are you running in this? What behavior are you expecting, and what behavior are you observing instead? If I understand you correctly, the "problem" is that it's waiting for events correctly.
Err…. You do understand the meaning of "block", right? I mean that instead of waiting until an event is fired, it checks if an event has been fired since the last call, returns it if it has, or returns nil if there has been no event.

http://stackoverflow...-in-programming

EDIT: To clarify, os.pullEventRaw() performs as expected in this code (it blocks), but I don't want it to perform as such.
I want the while true loop to continue regardless of whether an event has been fired.

So what you're saying is that you want your loop to busy-spin when there are no events to pass to your coroutines, passing them no events and causing unexpected behavior in everything not specifically written for your coroutine manager? This sounds like a brilliant plan so far.

That's the way ComputerCraft works, you yield to wait for an event to allow other computers to run. Making an infinite loop that doesn't yield until there's an event wouldn't let other computers work.
I'm not sure you understand what I want to do.
When one of my threads runs coroutine.yield("mouse_click") for example, my API needs to not resume it again until it has pulled a mouse_click event. In the meantime, however, it should still be running other coroutines. This implementation, however, does not let any other coroutines run until it has pulled said event.

I have a window manager that spawns two threads. One updates a clock, and one waits for a mouse_click, then exits the program. With this implementation, it does not run the clock-updating function until an event has been pulled. Not what I want to happen. When I use the parallel API to run both functions at the same time, it works.

No, we understand what you want to do perfectly, you're just going about it completely incorrectly. You are still able to run other coroutines, when events come in. That's exactly how it's supposed to work. You aren't supposed to stupidly busy-spin in case something wants to run, but didn't bother asking to run. That's what things ask to run for. That's what timers and the ability to queue events are meant for. Why isn't your clock coroutine queuing events for itself if it wants to run? This is not a feature that a coroutine manager should have, nor is it one that it should desire to have.

there's no need to, you just have to make the "threads" work like that (wait for an event then act accordingly).
Err…….. Just.. What? That makes no sense…
My thread calls os.pullEvent("mouse_click"), which calls coroutine.yield("mouse_click"), which yields the coroutine, sending it back to my thread manager (code in the OP). There's no way I can "make the threads … wait for an event then act accordingly" without implementing the functionality in my thread manager.

Sure, but you're implementing the functionality the wrong way. Your clock coroutine should be setting up the events it wants to receive, not expecting the coroutine manager to resume it without events to give it. That makes no sense, and runs completely counter to the way the rest of the ComputerCraft system is written.
RunasSudo-AWOLindefinitely #11
Posted 16 January 2013 - 08:25 PM
Uuuuuuhhhhhhh…. Okay… I'm going to assume you mean
"your clock coroutine should be queueing events (os.startTimer) and pulling them, not expecting the coroutine manager to run it whenever it gets a chance"
If that was what you meant: Thanks for your (definitely not at all slightly confusing) answer.
If that wasn't what you meant: Sorry, I don't understand what you mean.

And with the "This is not a feature that a coroutine manager should have, nor is it one that it should desire to have", it's just that's how Threads work in Java. So I thought I might try to implement it that way. Evidently (if you did mean what I think you meant - see above), things work completely different in Lua/CC.

new Thread(new Runnable() {
  void run() {
    while (true) { update(); }
  }
}).start();
No event queueing needed.

EDIT: Oh, it's also how the parallel API works (I'm pretty sure), so yeah.
Edited on 16 January 2013 - 07:28 PM
NeverCast #12
Posted 16 January 2013 - 08:29 PM
Just need to check something, you do realize that only one coroutine every runs at one time, correct? And that what ever calls resume, will continue to be executed on a yield, right?
Just making sure because there can often be a lot of confusion about cooperative multi-tasking.
RunasSudo-AWOLindefinitely #13
Posted 16 January 2013 - 08:33 PM
Just need to check something, you do realize that only one coroutine every runs at one time, correct? And that what ever calls resume, will continue to be executed on a yield, right?
Uuuuh… Yeah. I do know how co-operative multitasking works.
I fail to see how that was relevant to the question……
NeverCast #14
Posted 16 January 2013 - 08:37 PM
As long as your threading manager is calling the resumes, then the yields will always go back to the threading manager, so you should keep a table that maps the threads to the filter, and when you yield your threading manager to get events, you then dispatch them to the threads in your table.
NeverCast #15
Posted 16 January 2013 - 08:39 PM
After actually looking at your code, no, there isn't a way to passively wait for an event to come in. That's the unfortunate thing. The idea is that your coroutines don't need to do anything at all while there are no events available. If your coroutines need to be executing code, they should do so before they call yield. Only call yield when you need an event. That's the point of it :)/>
RunasSudo-AWOLindefinitely #16
Posted 16 January 2013 - 09:21 PM
After actually looking at your code, no, there isn't a way to passively wait for an event to come in. That's the unfortunate thing. The idea is that your coroutines don't need to do anything at all while there are no events available. If your coroutines need to be executing code, they should do so before they call yield. Only call yield when you need an event. That's the point of it :)/>/>
The point is that if I didn't call yield, I'd get a "too long without yielding" error (my clock routine just wants to run and run and run). So I kinda have to.
GopherAtl #17
Posted 16 January 2013 - 10:25 PM
the reason you'd get a "too long without yielding" error is because no to coroutines can ever run at the same time. When your coroutine takes 7 seconds without yielding and gets a "too long without yielding" error, every single coroutine on every computer in the entire CC world was just frozen, unable to run, for 7 seconds, because your coroutine did not yield.
RunasSudo-AWOLindefinitely #18
Posted 16 January 2013 - 11:23 PM
the reason you'd get a "too long without yielding" error is because no to coroutines can ever run at the same time. When your coroutine takes 7 seconds without yielding and gets a "too long without yielding" error, every single coroutine on every computer in the entire CC world was just frozen, unable to run, for 7 seconds, because your coroutine did not yield.
I understand that… Hence the "so I kinda have to". Also, notice that the topic is tagged "Solved".
Draradech #19
Posted 16 January 2013 - 11:26 PM
The point is that if I didn't call yield, I'd get a "too long without yielding" error (my clock routine just wants to run and run and run). So I kinda have to.
No, it doesn't. It wants to run once a second (or minute or whatever your clock is counting) to update the clock, then wait for a timer event.
RunasSudo-AWOLindefinitely #20
Posted 16 January 2013 - 11:30 PM
The point is that if I didn't call yield, I'd get a "too long without yielding" error (my clock routine just wants to run and run and run). So I kinda have to.
No, it doesn't. It wants to run once a second (or minute or whatever your clock is counting) to update the clock, then wait for a timer event.
Oh, I'm sorry, I wasn't aware that while true loops automatically implemented that functionality! </sarcasm>

To clarify: the clock function also does the drawing of… everything. 1FPS is a pre-tty s**t speed to run a GUI at.
Cloudy #21
Posted 16 January 2013 - 11:38 PM
You're a dick to people who try and help you. I was going to help you but now I'm not. If you were so sure of what you were saying why did you ask for help in the first place if you'd just argue with people who tell you the truth?

And if you think 1FPS is a crappy speed for a text GUI to update then you're missing the point.
RunasSudo-AWOLindefinitely #22
Posted 16 January 2013 - 11:48 PM
You're a dick to people who try and help you. I was going to help you but now I'm not. If you were so sure of what you were saying why did you ask for help in the first place if you'd just argue with people who tell you the truth?

And if you think 1FPS is a crappy speed for a text GUI to update then you're missing the point.
I've marked the topic as "Solved", because I thought Lyqyd's response that I should fire an event, instead of automatically running the coroutine, to be sufficient.

I'm sorry, but I'm just getting frustrated because people keep telling me what to think, and that information is either wrong, or I have a reason I wanted to do it the way I did it.

"It wants to run once a second (or minute or whatever your clock is counting) to update the clock, then wait for a timer event."
I'm sorry if I'm coming across as being a dick, I was just really annoyed that this guy thought he knew my own code better than me. Perhaps it was a misunderstanding, but outright stating "No it doesn't" and even bolding the "wait for a timer event" just seemed like he was claiming to know what my code did (without ever seeing it).

And "you're just going about it completely incorrectly" doesn't seem like a very nice way to say "there's a better way to do it" or "your code would hold up other programs".
Edited on 16 January 2013 - 10:53 PM
Draradech #23
Posted 16 January 2013 - 11:57 PM
Of course I don't know your code better than you. I just assumed your clock function was updating "the clock". (And as such, redrawing the screen of course - the clock being the fastest changing thing on it). The point still stands, your functions need to know when they want to run next and set a timer for it (or wait for another event, if they only want to react on that regardless of time). Your threading api cannot magically know when to call it next, and "always" is not the way cooperative multitasking works.

Anyway I didn't want to annoy you and I'm sorry that I did.
ChunLing #24
Posted 17 January 2013 - 08:38 AM
I think that now is the perfect time for me to chip in :P/>

The thread is marked solved because the OP doesn't want any more help, if I understand all this correctly. That seems to me like an appropriate way to communicate that help is no longer desired. It would probably be better to ignore further offers of help rather than reply to them, whether in a polite or irritable tone (naturally, the OP should ignore this "help" as well). It is true that sometimes threads devolve into a discussion of the merits of various approaches to a problem that holds no real interest to the OP. This is no worse than if I comment on a thread that briefly interested me and it continues to show up in my content page long after the discussion has drifted away from points I consider interesting. I don't bother to do anything about those threads, and I don't see how it would matter if I were the OP of such a thread, once it is no longer of interest to me it is…no longer of interest to me.

In the case of an Ask a Pro thread, it is much more likely to die off naturally if the OP ceases to post. So there is that.
Lyqyd #25
Posted 17 January 2013 - 08:47 AM
ChunLing, your post has the distinct flavor of troll, whether intended or not. Let's keep that out of Ask a Pro, please. This thread has obviously outlived its usefulness. Locked.