Map: the rough first pass is off, behind a switch rather than a deletion
_COARSE_PREVIEW turns off the two-pass draw: every view now waits for the whole picture, standing on the last frame's ground reprojected until it lands, or on nothing where the view has run past that too. None of the machinery goes. Both passes still draw, render_ground still takes coarse=, and the abandonment point between them is still where a held pan key gets off — what the switch decides is only whether a moved view is offered the first pass, which is why it is read where the raster is asked for and not inside the renderer. Turning it back on is one line. The abandonment test needed to say what it means to survive the change: it asserted no finished pass appeared in a list it read after the loop closed, which quietly depended on the *next* view's draw not getting a scheduler step in time. It reads the list where the abandoned key's draw returns now, and asks only about that key. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
b4db55961f
commit
c79ade3fab
3 files changed
+73
-17
No files matched your search
@@ -376,9 +376,10 @@ def render_ground(
|
||||
boundaries and the through-roads (:data:`_COARSE_ROAD_PRIORITY`) — no buildings, no back
|
||||
streets, no text at all. It is the same picture at a lower resolution of detail, and
|
||||
it is a quarter of the work: on the PicoCalc, 249 ms against 962 at zoom 13, 120 ms
|
||||
against 529 at zoom 16. The map draws one of these before the full frame so a view
|
||||
that has run past its last ground has *something* true on it within a blink, and so
|
||||
that a pan being held has a unit of work small enough to abandon (see
|
||||
against 529 at zoom 16. Drawn before the full frame it gives a view that has run past
|
||||
its last ground *something* true within a blink, and gives a pan being held a unit of
|
||||
work small enough to abandon — which is what the map asks for when
|
||||
:data:`~meshterm.ui.map_screen._COARSE_PREVIEW` is on (see
|
||||
:meth:`meshterm.ui.map_screen.MapScreen._draw_ground`).
|
||||
|
||||
Args:
|
||||
|
||||
+26
-10
@@ -88,6 +88,19 @@ _TILE_CACHE_SCREENS = 2
|
||||
#: history for a pan away and back to be instant.
|
||||
_MIN_TILE_CACHE = 8
|
||||
|
||||
#: Whether a moved view is answered with the **rough first pass** before its finished
|
||||
#: frame — the ground fills, the watercourses and the through-roads, in a quarter of the
|
||||
#: time (see :func:`~meshterm.ui.map_render.render_ground`). Currently **off**: every view
|
||||
#: waits for the whole picture, and what it stands on until then is the last frame's
|
||||
#: ground reprojected (:class:`~meshterm.ui.map_render.Ghost`), or nothing where the view
|
||||
#: has run past that too.
|
||||
#:
|
||||
#: The switch is here rather than in the renderer because it is a decision about *when to
|
||||
#: ask*, not about what can be drawn: both passes still work, both are still tested, and
|
||||
#: the abandonment point between them (:meth:`MapScreen._draw_ground`) is still where a
|
||||
#: held pan key gets off. Turning it back on is this line.
|
||||
_COARSE_PREVIEW = False
|
||||
|
||||
#: How many pans in one direction it takes to read as a heading rather than a nudge.
|
||||
_PREFETCH_MOMENTUM = 2
|
||||
|
||||
@@ -375,11 +388,12 @@ class MapScreen(Screen):
|
||||
edge you are panning onto — instead of the map blanking to black between every
|
||||
keypress and flashing back when the frame lands (JP, 2026-08-09).
|
||||
|
||||
The last of those is where the map used to go black *and stay black*: four coarse
|
||||
pan steps are 120% of the screen, so nothing drawn is under the view any more and
|
||||
there is no ground to reproject. What fixes that is not this method but the one it
|
||||
schedules — see :meth:`_draw_ground`, which now lands a rough picture in a quarter
|
||||
of the time rather than nothing at all for a whole raster.
|
||||
The last of those is where the map goes black *and stays black*: four coarse pan
|
||||
steps are 120% of the screen, so nothing drawn is under the view any more and
|
||||
there is no ground to reproject. The answer to that is not this method but the one
|
||||
it schedules — :meth:`_draw_ground` can land a rough picture in a quarter of the
|
||||
time rather than nothing at all for a whole raster, which is
|
||||
:data:`_COARSE_PREVIEW`, currently off.
|
||||
"""
|
||||
key = self._ground_key(vp)
|
||||
if self._frame_key == key and self._frame is not None:
|
||||
@@ -468,7 +482,7 @@ class MapScreen(Screen):
|
||||
self._frame_key, self._frame_coarse = key, False
|
||||
return
|
||||
self._drawing = key
|
||||
preview = not self._ground_drawn(key)
|
||||
preview = _COARSE_PREVIEW and not self._ground_drawn(key)
|
||||
asyncio.ensure_future(self._draw_ground(key, vp, tiles, markers, find, preview))
|
||||
|
||||
async def _draw_ground(
|
||||
@@ -488,7 +502,8 @@ class MapScreen(Screen):
|
||||
frame (measured worst-case delay ~50 ms, against the ~1 s of a blocking draw).
|
||||
|
||||
**Two passes, because a whole frame is too big a thing to wait for or to throw
|
||||
away.** A coarse pass (ground, water, through-roads; see
|
||||
away** — where ``preview`` asks for both, which :data:`_COARSE_PREVIEW` currently
|
||||
does not. A coarse pass (ground, water, through-roads; see
|
||||
:func:`~meshterm.ui.map_render.render_ground`) is a quarter of the work — 249 ms
|
||||
against 962 at zoom 13 on the PicoCalc — and it lands first, so a view that has
|
||||
run past every scrap of drawn ground shows something true within a blink instead
|
||||
@@ -514,9 +529,10 @@ class MapScreen(Screen):
|
||||
tiles: The decoded tiles it draws from.
|
||||
markers: The nodes to overlay, snapshotted at request time.
|
||||
find: The live find filter, likewise.
|
||||
preview: Whether to draw the coarse pass first. Skipped where this view's
|
||||
ground is already drawn in full (see :meth:`_ground_drawn`): there a
|
||||
coarse picture would *remove* detail the reader can already see.
|
||||
preview: Whether to draw the coarse pass first. Off wholesale while
|
||||
:data:`_COARSE_PREVIEW` is; and skipped anyway where this view's ground is
|
||||
already drawn in full (see :meth:`_ground_drawn`), since there a coarse
|
||||
picture would *remove* detail the reader can already see.
|
||||
"""
|
||||
if preview and await self._pass(key, vp, tiles, markers, find, coarse=True):
|
||||
if self._wanted is not None:
|
||||
|
||||
+43
-4
@@ -2083,17 +2083,21 @@ def test_map_abandons_the_finished_pass_for_a_view_that_moved(monkeypatch) -> No
|
||||
|
||||
monkeypatch.setattr(ms, "render_ground", render)
|
||||
|
||||
async def drive() -> None:
|
||||
async def drive() -> tuple:
|
||||
screen.render_body(80)
|
||||
while screen._drawing is not None:
|
||||
await asyncio.sleep(0)
|
||||
passes.clear()
|
||||
screen._drawing = ("k",)
|
||||
await screen._draw_ground(("k",), screen._viewport, {}, [], "", True)
|
||||
# Read here, not after the loop closes: the newer view's own draw is a task of its
|
||||
# own by now, and what it goes on to ask for is that view's business, not this
|
||||
# one's. The question is only what the *abandoned* key got.
|
||||
return list(passes), screen._frame_coarse
|
||||
|
||||
asyncio.run(drive())
|
||||
assert False not in passes, "a finished pass was drawn for a view already left behind"
|
||||
assert screen._frame_coarse is True
|
||||
stale, rough_on_screen = asyncio.run(drive())
|
||||
assert stale == [True], "a finished pass was drawn for a view already left behind"
|
||||
assert rough_on_screen is True, "the abandoned view left no rough picture on screen"
|
||||
|
||||
|
||||
def test_map_skips_the_coarse_pass_over_a_picture_already_drawn(monkeypatch) -> None: # noqa: ANN001
|
||||
@@ -2123,6 +2127,41 @@ def test_map_skips_the_coarse_pass_over_a_picture_already_drawn(monkeypatch) ->
|
||||
assert coarse_asked == [False], "a drawn frame was replaced by a coarser one"
|
||||
|
||||
|
||||
def test_the_rough_pass_is_a_switch_the_map_reads(monkeypatch) -> None: # noqa: ANN001
|
||||
"""Both passes work; whether a moved view is offered the first one is a knob.
|
||||
|
||||
Off, every view waits for the whole picture and stands on the last frame's ground
|
||||
until it lands. The machinery is :meth:`MapScreen._draw_ground`'s either way — what
|
||||
the switch decides is the *asking*, which is why it is read where the raster is
|
||||
requested and not inside the renderer.
|
||||
"""
|
||||
from meshterm.ui import map_screen as ms
|
||||
|
||||
def first_draw() -> list:
|
||||
"""The passes a view with nothing drawn under it asks for."""
|
||||
asked: list = []
|
||||
monkeypatch.setattr(
|
||||
ms, "render_ground",
|
||||
lambda vp, tiles, m, **k: (asked.append(k.get("coarse", False))
|
||||
or (["x"], None)),
|
||||
)
|
||||
screen = _map_over(_StubSource())
|
||||
|
||||
async def drive() -> None:
|
||||
screen.render_body(80)
|
||||
while screen._drawing is not None:
|
||||
await asyncio.sleep(0)
|
||||
|
||||
asyncio.run(drive())
|
||||
return asked
|
||||
|
||||
monkeypatch.setattr(ms, "_COARSE_PREVIEW", True)
|
||||
assert first_draw() == [True, False], "the rough pass did not come first when asked for"
|
||||
|
||||
monkeypatch.setattr(ms, "_COARSE_PREVIEW", False)
|
||||
assert first_draw() == [False], "a rough pass was drawn with the switch off"
|
||||
|
||||
|
||||
def test_map_keeps_its_detail_while_a_find_is_typed(monkeypatch) -> None: # noqa: ANN001
|
||||
"""Typing a node name moves the view nowhere, so the streets must not flatten.
|
||||
|
||||
|
||||
Reference in new issue
Block a user