From 06eaf109f0b59a4faa0a911d6daacf3b416f584c Mon Sep 17 00:00:00 2001 From: David Phillips Date: Sat, 13 Mar 2021 21:09:55 +1300 Subject: 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. --- ui_remote.py | 80 +++++++++++++++++++++++++++++++++++++++--------------------- 1 file 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__": -- cgit v1.1