diff options
author | David Phillips <david@yeah.nah.nz> | 2021-03-13 21:09:55 +1300 |
---|---|---|
committer | David Phillips <david@yeah.nah.nz> | 2021-03-13 21:10:56 +1300 |
commit | 06eaf109f0b59a4faa0a911d6daacf3b416f584c (patch) | |
tree | c7dbd45d167b0a222ab10937939c357c839e7d80 | |
parent | 3c8502c381dc18cadfa36f9db4603915bd8c311f (diff) | |
download | altimeter-06eaf109f0b59a4faa0a911d6daacf3b416f584c.tar.xz |
ui_remote: make event sending async
This makes the UI a bit snappier, as we no longer have to interrupt for each
event, just on groups of them.
-rwxr-xr-x | ui_remote.py | 80 |
1 files changed, 52 insertions, 28 deletions
diff --git a/ui_remote.py b/ui_remote.py index c4f5c99..a13a5e0 100755 --- a/ui_remote.py +++ b/ui_remote.py @@ -7,35 +7,51 @@ # never be allowed to happen in normal use. # Useful nonetheless. # -# FIXME allow smarter queueing of UI events in this script so they can be -# batched into the same (slow) interrupt-and-continue window. -# import atexit import curses import pygdbmi.gdbcontroller +import queue +import threading import sys -def interrupt_run_continue(mi, command): - mi.write('interrupt') - mi.write(command) - mi.write('continue &') +def command_consumer(quit_event, command_queue, gdbmi): + while not quit_event.is_set(): + # block until a command is ready to send + # non-blocking read so we can respond to quit_event as needed + command = None + try: + command = command_queue.get(block=True, timeout=0.1) + except queue.Empty: + continue + # stop the remote program while run the command and run any other + # commands in the queue since `interrupt` is expensive + gdbmi.write('interrupt') + gdbmi.write(command) + try: + while command := command_queue.get(block=False): + gdbmi.write(command) + except queue.Empty: + gdbmi.write('continue &') + + +def curses_init(): + stdscr = curses.initscr() + curses.cbreak() + curses.noecho() + return stdscr -def main(): - def curses_init(): - stdscr = curses.initscr() - curses.cbreak() - curses.noecho() - return stdscr - - def curses_end(): - curses.nocbreak() - stdscr.keypad(False) - curses.echo() - curses.endwin() +def curses_end(stdscr): + curses.nocbreak() + stdscr.keypad(False) + curses.echo() + curses.endwin() + + +def main(): # Give pygdbmi hardcoded GDB, ELF paths, remote. Good enough for now mi = pygdbmi.gdbcontroller.GdbController( command=["/usr/bin/avr-gdb", '--interpreter=mi3']) @@ -44,31 +60,39 @@ def main(): mi.write('continue &') # atexit covers crash cases which is nice - atexit.register(curses_end) stdscr = curses_init() + atexit.register(lambda: curses_end(stdscr)) + + # queue and thread so we can push UI events to the remote asynchronously + quit_event = threading.Event() + command_queue = queue.Queue(maxsize=1024) + gdb_handler_thread = threading.Thread( + target=command_consumer, + args=(quit_event, command_queue, mi)) + gdb_handler_thread.start() stdscr.addstr(0, 0, "UI remote control via GDB. Press q to quit") stdscr.addstr(1, 0, "w: up s: down a: hold d: short press") + global running running = True while running: - stdscr.addstr(2, 0, "ready") stdscr.clrtoeol() key = stdscr.getch() - stdscr.addstr(2, 0, "busy") stdscr.clrtoeol() stdscr.refresh() if key == ord('q'): running = False + quit_event.set() elif key == ord('w'): - interrupt_run_continue(mi, 'call up()') + command_queue.put('call up()') elif key == ord('s'): - interrupt_run_continue(mi, 'call down()') + command_queue.put('call down()') elif key == ord('a'): - interrupt_run_continue(mi, 'call hold()') + command_queue.put('call hold()') elif key == ord('d'): - interrupt_run_continue(mi, 'call press()') - else: - stdscr.addstr(1, 0, str(key)) + command_queue.put('call press()') + + gdb_handler_thread.join() if __name__ == "__main__": |