From 9ceda9b87304e5508e59df3bc994d3a3fcc32523 Mon Sep 17 00:00:00 2001 From: Dash Date: Sat, 17 Oct 2026 19:57:28 +0200 Subject: [PATCH] ui/curses: wait for keys instead of polling The main loop slept 100 ms whenever no key was pending, so keys took up to a tick to register and every screen woke ten times a second. Each view now declares how often it needs idle(): the game view keeps its 100 ms tick, while menus wait for a key. getch() blocks for that long, so keys register at once, and over 15 s in the roster menu the process goes from about 300 wake-ups to 6. --- pqcli/ui/curses/__init__.py | 13 +++++++++---- pqcli/ui/curses/views/base_view.py | 3 +++ pqcli/ui/curses/views/game_view/__init__.py | 2 ++ 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/pqcli/ui/curses/__init__.py b/pqcli/ui/curses/__init__.py index e87be75..1d40bec 100644 --- a/pqcli/ui/curses/__init__.py +++ b/pqcli/ui/curses/__init__.py @@ -2,7 +2,6 @@ import argparse import curses import os import signal -import time from pathlib import Path from types import FrameType @@ -54,7 +53,6 @@ class CursesUserInterface(BaseUserInterface): super().__init__(roster, player, args, data) os.environ.setdefault("ESCDELAY", "25") self._screen = curses.initscr() - self._screen.nodelay(True) self._screen.keypad(True) curses.noecho() curses.curs_set(0) @@ -87,6 +85,7 @@ class CursesUserInterface(BaseUserInterface): self._roster_view.on_quit += self._quit self._view: BaseView = self._roster_view self._view.start() + self._wait_for_keys() def run(self) -> None: try: @@ -98,8 +97,7 @@ class CursesUserInterface(BaseUserInterface): while True: key = self._screen.getch() - if key == curses.ERR: - time.sleep(0.1) + if key == curses.ERR: # the view's idle interval passed self._view.idle() continue @@ -250,6 +248,13 @@ class CursesUserInterface(BaseUserInterface): self._view.stop() self._view = view self._view.start() + self._wait_for_keys() + + def _wait_for_keys(self) -> None: + # getch() blocks until a key comes or the view's idle interval + # passes, so menus sit still and keys never wait for a tick. + interval = self._view.idle_interval + self._screen.timeout(-1 if interval is None else interval) def _create_character( self, diff --git a/pqcli/ui/curses/views/base_view.py b/pqcli/ui/curses/views/base_view.py index c9d4c7e..e530b5c 100644 --- a/pqcli/ui/curses/views/base_view.py +++ b/pqcli/ui/curses/views/base_view.py @@ -2,6 +2,9 @@ import curses class BaseView: + # How often idle() runs, in milliseconds; None waits for a key. + idle_interval: int | None = None + def __init__(self, screen: curses.window) -> None: self.screen = screen diff --git a/pqcli/ui/curses/views/game_view/__init__.py b/pqcli/ui/curses/views/game_view/__init__.py index 2f7983a..dceb7e8 100644 --- a/pqcli/ui/curses/views/game_view/__init__.py +++ b/pqcli/ui/curses/views/game_view/__init__.py @@ -42,6 +42,8 @@ class PageScrollable(Protocol): class GameView(BaseView): + idle_interval = 100 + def __init__( self, screen: curses.window,