3

An answer to another question suggests that supplying a timeout of 0.0 to read-event (as the optional SECONDS parameter) will always return nil immediately, and, if I'm reading the code in Emacs' keyboard.c correctly, even a (very) very small non-zero timeout could return nil without checking everywhere for an existing event.

Because input-pending-p can return false positives, calling read-event with a non-zero value for SECONDS to try to get that (nonexistant event) could cause a delay and still return nil. Practically, it seems like using a millisecond or two for SECONDS should be fine, but it does feel less tidy than if read-event returned any available events even with a timout of 0.0. Short of a locall Emacs build, is there a solution a particularly particular user might prefer?

The code @phils gave with his answer demonstrates this is not correct if "unread" events are available in any of 3 queues. I've added code that suggests yet another event-queue is checked by input-pending-p that is skipped by read-event when SECONDS is 0.0.

My polling loop below is more complicated than I'd like, but I wanted to record all the relative event times. The quit key (C-g) will terminate.

The code starts calling read-event with a SECONDS value of 0.0, increasing it after 5 seconds. It records when input-pending-p first returns true, and then the time and SECONDS value when read-event first returns non-nil.

For me, pressing a key before 5 seconds is up demonstrates that input-pending-p can be true, but until SECONDS is increased to at least a little above 0.0, read-event consistently returns nil.

(let* ((input-at) (read-at) (wait-to-try 0.0) (deadline (+ (float-time) 5.0)) (past-due))
  (while 't
    (setq past-due (- (float-time) deadline))
    (when (and (not input-at) (input-pending-p)) (setq input-at past-due))
    (when (not read-at)
      (when (> past-due 0.0) (setq wait-to-try (* 0.001 past-due)))
      (when (read-event nil nil wait-to-try) (setq read-at past-due)))
    (message "past: %f  in-p: %s  in-at: %s  wait: %s  read-at %s"
      past-due (input-pending-p) input-at wait-to-try read-at)
    (redisplay)))
  • 1
    I suppose you could use `(or (read-event nil nil 0) (and (input-pending-p) (read-event nil nil 0.1)))`? Or maybe just the `and` form. But I guess that's what you were playing with to begin with, when discussing the possibility of false-positives incurring the full delay. Hopefully someone with more knowledge than me will chip in. – phils Sep 29 '22 at 13:20

1 Answers1

0

Edit: With the new information and test case provided in the question, it's clear that this answer is incorrect (or at least inadequate). Clearly there are indeed additional input event queue(s) which do not get the opportunity to feed event-read while elisp is being actively executed, until some kind of waiting occurs.

Original answer follows...


If the event is already existing/pending, I don't think you need to deal with timeouts at all?

(let ((unread-command-events '(42)))
  (read-event))
=> 42

An answer to another question suggests that supplying a timeout of zero to read-event (as the optional SECONDS parameter) will always return nil immediately

That's certainly not what I'm seeing in a cursory test:

(let ((unread-command-events '(1))
      (unread-input-method-events '(2))
      (unread-post-input-method-events '(3)))
  (list (read-event nil nil 0)
        (read-event nil nil 0)
        (read-event nil nil 0)))

=> (3 1 2)
phils
  • 48,657
  • 3
  • 76
  • 115
  • Do events normally flow to `unread-command-events` ? From the [docs](https://www.gnu.org/software/emacs/manual/html_node/elisp/Event-Input-Misc.html) I thought this was a sort of "pushback stack", and events would only go on that list if a function read it from the standard queue, then decided it didn't want it. –  Sep 26 '22 at 03:49
  • Beyond the fact that there are at least three different "unread events" lists, I'm not certain of the details; but I would have expected the result to be the same regardless -- if there's an event waiting in a queue and you call `read-event` I would *expect* the waiting event to be returned in all cases. – phils Sep 28 '22 at 09:58
  • Yeah, that's what I expect, but I don't think it's the case. I ended up rewriting what I was doing to use a timer instead and add a little dynamics into the mix anyway, but I'd still like to understand... –  Sep 28 '22 at 13:53
  • If you can provide an example of it working differently, that would no doubt help. – phils Sep 28 '22 at 21:34
  • Ah! I missed your test after reading your earlier comment. Awesome! I’ll cook something up. Thanks! –  Sep 28 '22 at 22:10
  • Nice one. With some extra messaging I can see that the three unread event lists I mentioned seem to remain empty while a key press within that first 5 seconds is pending, so it looks like your original understanding is correct -- such events are waiting in some other event queue which cannot be processed while other elisp is being actively executed. – phils Sep 29 '22 at 12:17
  • I suspect `keyboard.c` is the code to read if you really want to get into the details (`input-pending-p` takes us to `get_input_pending`, at which point things immediately get low-level, and entirely outside of my area of knowledge). – phils Sep 29 '22 at 12:22
  • Thanks - I appreciate the feedback and investigations! –  Sep 29 '22 at 12:35