aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Phillips <david@yeah.nah.nz>2021-03-13 21:09:55 +1300
committerDavid Phillips <david@yeah.nah.nz>2021-03-13 21:10:56 +1300
commit06eaf109f0b59a4faa0a911d6daacf3b416f584c (patch)
treec7dbd45d167b0a222ab10937939c357c839e7d80
parent3c8502c381dc18cadfa36f9db4603915bd8c311f (diff)
downloadaltimeter-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-xui_remote.py80
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__":