step 6: add stable overlays and live update stress tests
This commit is contained in:
@@ -1,8 +1,11 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from threading import Thread
|
||||
|
||||
import pytest
|
||||
|
||||
import dpg_map as dpgm
|
||||
from dpg_map.commands import CommandKind
|
||||
from dpg_map.exceptions import CoordinateError
|
||||
from dpg_map.overlays import TrajectoryOverlay
|
||||
from dpg_map.state import DirtyFlags, create_map_state, get_map_state
|
||||
@@ -61,3 +64,39 @@ def test_layer_state_tracks_visibility_and_overlay_membership() -> None:
|
||||
|
||||
assert state.layers["fleet"].overlay_tags == set()
|
||||
assert "vehicle" not in state.overlays
|
||||
|
||||
|
||||
def test_threaded_marker_updates_coalesce_without_touching_view_or_drag_state() -> None:
|
||||
create_map_state(tag="threaded-marker", center=(47.0, 2.0), zoom=9)
|
||||
dpgm.add_marker("vehicle", lat=47.0, lon=2.0, map_tag="threaded-marker")
|
||||
state = get_map_state("threaded-marker")
|
||||
state.command_queue.drain()
|
||||
state.dirty = DirtyFlags.NONE
|
||||
state.interaction.active_drag = True
|
||||
state.interaction.last_mouse_position = (100.0, 100.0)
|
||||
before_center = state.center
|
||||
before_zoom = state.zoom
|
||||
|
||||
def update_worker(offset: float) -> None:
|
||||
for index in range(100):
|
||||
dpgm.update_marker(
|
||||
"vehicle",
|
||||
lat=47.0 + offset,
|
||||
lon=2.0 + index * 0.00001,
|
||||
map_tag="threaded-marker",
|
||||
)
|
||||
|
||||
threads = [Thread(target=update_worker, args=(worker * 0.0001,)) for worker in range(4)]
|
||||
for thread in threads:
|
||||
thread.start()
|
||||
for thread in threads:
|
||||
thread.join()
|
||||
|
||||
commands = state.command_queue.drain()
|
||||
|
||||
assert state.center == before_center
|
||||
assert state.zoom == before_zoom
|
||||
assert state.interaction.active_drag is True
|
||||
assert state.interaction.last_mouse_position == (100.0, 100.0)
|
||||
assert state.dirty == DirtyFlags.OVERLAYS
|
||||
assert [command.kind for command in commands] == [CommandKind.UPDATE_OVERLAY]
|
||||
|
||||
@@ -1,10 +1,50 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
import dpg_map as dpgm
|
||||
from dpg_map.commands import CommandKind, MapCommand
|
||||
from dpg_map.renderer import drain_renderer_commands
|
||||
from dpg_map.renderer import MapRenderer, drain_renderer_commands
|
||||
from dpg_map.state import DirtyFlags, create_map_state
|
||||
|
||||
|
||||
class FakeDpg:
|
||||
def __init__(self) -> None:
|
||||
self.items: set[str | int] = set()
|
||||
self.deleted: list[tuple[str | int, bool]] = []
|
||||
self.drawn: list[tuple[str, str | int]] = []
|
||||
|
||||
def does_item_exist(self, tag: str | int) -> bool:
|
||||
return tag in self.items
|
||||
|
||||
def add_draw_layer(self, *, parent: str | int, tag: str | int) -> None:
|
||||
_ = parent
|
||||
self.items.add(tag)
|
||||
|
||||
def delete_item(self, tag: str | int, *, children_only: bool = False) -> None:
|
||||
self.deleted.append((tag, children_only))
|
||||
|
||||
def draw_rectangle(self, *args: Any, parent: str | int, **kwargs: Any) -> None:
|
||||
_ = (args, kwargs)
|
||||
self.drawn.append(("rectangle", parent))
|
||||
|
||||
def draw_image(self, *args: Any, parent: str | int, **kwargs: Any) -> None:
|
||||
_ = (args, kwargs)
|
||||
self.drawn.append(("image", parent))
|
||||
|
||||
def draw_text(self, *args: Any, parent: str | int, **kwargs: Any) -> None:
|
||||
_ = (args, kwargs)
|
||||
self.drawn.append(("text", parent))
|
||||
|
||||
def draw_circle(self, *args: Any, parent: str | int, **kwargs: Any) -> None:
|
||||
_ = (args, kwargs)
|
||||
self.drawn.append(("circle", parent))
|
||||
|
||||
def draw_polyline(self, *args: Any, parent: str | int, **kwargs: Any) -> None:
|
||||
_ = (args, kwargs)
|
||||
self.drawn.append(("polyline", parent))
|
||||
|
||||
|
||||
def test_renderer_command_drain_preserves_structural_order_and_coalesces() -> None:
|
||||
state = create_map_state(tag="renderer-drain")
|
||||
state.dirty = DirtyFlags.NONE
|
||||
@@ -28,3 +68,50 @@ def test_renderer_command_drain_preserves_structural_order_and_coalesces() -> No
|
||||
assert commands[2].payload == {"tag": "a", "v": 2}
|
||||
assert state.dirty & DirtyFlags.VIEW
|
||||
assert state.dirty & DirtyFlags.OVERLAYS
|
||||
|
||||
|
||||
def test_overlay_draw_clears_only_overlay_layer() -> None:
|
||||
state = create_map_state(tag="overlay-draw", center=(47.0, 2.0), zoom=8)
|
||||
dpgm.add_marker(
|
||||
"vehicle",
|
||||
lat=47.0,
|
||||
lon=2.0,
|
||||
show_label=True,
|
||||
label="Vehicle",
|
||||
map_tag="overlay-draw",
|
||||
)
|
||||
fake = FakeDpg()
|
||||
fake.items.add(state.drawlist_tag)
|
||||
renderer = MapRenderer(state, fake)
|
||||
|
||||
renderer._draw_tile_layer(
|
||||
visible_tiles=[], width=400, height=300, attribution="Tiles", tile_size=256
|
||||
)
|
||||
fake.deleted.clear()
|
||||
with state.lock:
|
||||
overlays = tuple(state.overlays.values())
|
||||
layers = {name: (layer.show, layer.z_index) for name, layer in state.layers.items()}
|
||||
|
||||
renderer._draw_overlay_layer(
|
||||
overlays=overlays,
|
||||
layers=layers,
|
||||
center=state.center,
|
||||
zoom=state.zoom,
|
||||
width=400,
|
||||
height=300,
|
||||
tile_size=256,
|
||||
)
|
||||
|
||||
assert fake.deleted == [("overlay-draw##layer-overlays", True)]
|
||||
assert ("circle", "overlay-draw##layer-overlays") in fake.drawn
|
||||
assert ("text", "overlay-draw##layer-overlays") in fake.drawn
|
||||
|
||||
|
||||
def test_overlay_update_drain_sets_only_overlay_dirty() -> None:
|
||||
state = create_map_state(tag="overlay-dirty")
|
||||
state.dirty = DirtyFlags.NONE
|
||||
state.command_queue.put(MapCommand(CommandKind.UPDATE_OVERLAY, state.tag, {"tag": "a"}))
|
||||
|
||||
drain_renderer_commands(state)
|
||||
|
||||
assert state.dirty == DirtyFlags.OVERLAYS
|
||||
|
||||
Reference in New Issue
Block a user