From 88b3b3eeea15f3d30871f4dc293363ecfc164057 Mon Sep 17 00:00:00 2001 From: Danny Date: Mon, 7 Sep 2026 16:04:42 -0700 Subject: [PATCH] Cache the Colorssss (cherry picked from commit 29c408d5f9a7e6661f0f8316783be6c0957654e5) --- selfdrive/ui/lib/starpilot_theme.py | 7 ++ selfdrive/ui/tests/test_starpilot_theme.py | 108 +++++++++++++++++++++ 2 files changed, 115 insertions(+) create mode 100644 selfdrive/ui/tests/test_starpilot_theme.py diff --git a/selfdrive/ui/lib/starpilot_theme.py b/selfdrive/ui/lib/starpilot_theme.py index bf7aa1746a..dc83d99cb7 100644 --- a/selfdrive/ui/lib/starpilot_theme.py +++ b/selfdrive/ui/lib/starpilot_theme.py @@ -6,6 +6,7 @@ from pathlib import Path import pyray as rl from openpilot.common.basedir import BASEDIR +from openpilot.system.ui.lib.application import gui_app ACTIVE_THEME_COLORS_PATH = Path(BASEDIR) / "starpilot/assets/active_theme/colors/colors.json" STOCK_THEME_COLORS_PATH = Path(BASEDIR) / "starpilot/assets/stock_theme/colors/colors.json" @@ -21,6 +22,7 @@ _FALLBACK_THEME_COLORS = { } _THEME_COLOR_CACHE: dict[str, object] = { + "frame": None, "stamp": None, "colors": None, } @@ -89,7 +91,12 @@ def _build_color(entry: object, fallback: tuple[int, int, int, int]) -> rl.Color def _load_theme_colors() -> dict[str, rl.Color]: + frame = gui_app.frame + if frame == _THEME_COLOR_CACHE["frame"] and _THEME_COLOR_CACHE["colors"] is not None: + return _THEME_COLOR_CACHE["colors"] # type: ignore[return-value] + stamp = (_file_stamp(STOCK_THEME_COLORS_PATH), _file_stamp(ACTIVE_THEME_COLORS_PATH)) + _THEME_COLOR_CACHE["frame"] = frame if stamp == _THEME_COLOR_CACHE["stamp"] and _THEME_COLOR_CACHE["colors"] is not None: return _THEME_COLOR_CACHE["colors"] # type: ignore[return-value] diff --git a/selfdrive/ui/tests/test_starpilot_theme.py b/selfdrive/ui/tests/test_starpilot_theme.py new file mode 100644 index 0000000000..7e91c73644 --- /dev/null +++ b/selfdrive/ui/tests/test_starpilot_theme.py @@ -0,0 +1,108 @@ +import importlib.util +import json +from pathlib import Path +import sys +from types import ModuleType + +MODULE_PATH = Path(__file__).resolve().parents[1] / "lib" / "starpilot_theme.py" +SPEC = importlib.util.spec_from_file_location("starpilot_theme_under_test", MODULE_PATH) +assert SPEC is not None and SPEC.loader is not None + + +class FakeGuiApp: + def __init__(self): + self._frame = 0 + + @property + def frame(self): + return self._frame + + +application_module_name = "openpilot.system.ui.lib.application" +application_module = sys.modules.get(application_module_name) +installed_fake_application = application_module is None +if application_module is None: + application_module = ModuleType(application_module_name) + application_module.gui_app = FakeGuiApp() + sys.modules[application_module_name] = application_module + +starpilot_theme = importlib.util.module_from_spec(SPEC) +SPEC.loader.exec_module(starpilot_theme) +if installed_fake_application: + sys.modules.pop(application_module_name, None) + + +def _reset_theme_cache(): + starpilot_theme._THEME_COLOR_CACHE.update(frame=None, stamp=None, colors=None) + + +def _track_file_stamps(monkeypatch): + file_stamps = [] + original_file_stamp = starpilot_theme._file_stamp + + def file_stamp(path): + file_stamps.append(path) + return original_file_stamp(path) + + monkeypatch.setattr(starpilot_theme, "_file_stamp", file_stamp) + return file_stamps + + +def _color_tuple(color): + return color.r, color.g, color.b, color.a + + +def test_theme_files_are_checked_once_per_render_frame(tmp_path, monkeypatch): + stock_path = tmp_path / "stock.json" + active_path = tmp_path / "active.json" + stock_path.write_text(json.dumps({"LaneLines": {"red": 1, "green": 2, "blue": 3}})) + active_path.write_text(json.dumps({})) + monkeypatch.setattr(starpilot_theme, "STOCK_THEME_COLORS_PATH", stock_path) + monkeypatch.setattr(starpilot_theme, "ACTIVE_THEME_COLORS_PATH", active_path) + monkeypatch.setattr(starpilot_theme.gui_app, "_frame", 10) + file_stamps = _track_file_stamps(monkeypatch) + _reset_theme_cache() + + assert _color_tuple(starpilot_theme.get_theme_color("LaneLines")) == (1, 2, 3, 178) + assert _color_tuple(starpilot_theme.get_theme_color("Path")) == (48, 255, 156, 255) + assert len(file_stamps) == 2 + + monkeypatch.setattr(starpilot_theme.gui_app, "_frame", 11) + starpilot_theme.get_theme_color("LeadMarker") + assert len(file_stamps) == 4 + + +def test_theme_file_changes_are_seen_on_the_next_frame(tmp_path, monkeypatch): + stock_path = tmp_path / "stock.json" + active_path = tmp_path / "active.json" + stock_path.write_text(json.dumps({})) + active_path.write_text(json.dumps({"Path": {"red": 1, "green": 2, "blue": 3, "alpha": 4}})) + monkeypatch.setattr(starpilot_theme, "STOCK_THEME_COLORS_PATH", stock_path) + monkeypatch.setattr(starpilot_theme, "ACTIVE_THEME_COLORS_PATH", active_path) + monkeypatch.setattr(starpilot_theme.gui_app, "_frame", 20) + _reset_theme_cache() + + assert _color_tuple(starpilot_theme.get_theme_color("Path")) == (1, 2, 3, 4) + active_path.write_text(json.dumps({"Path": {"red": 5, "green": 6, "blue": 7, "alpha": 8}})) + assert _color_tuple(starpilot_theme.get_theme_color("Path")) == (1, 2, 3, 4) + + monkeypatch.setattr(starpilot_theme.gui_app, "_frame", 21) + assert _color_tuple(starpilot_theme.get_theme_color("Path")) == (5, 6, 7, 8) + + +def test_frame_counter_reset_checks_theme_files_again(tmp_path, monkeypatch): + stock_path = tmp_path / "stock.json" + active_path = tmp_path / "active.json" + stock_path.write_text(json.dumps({})) + active_path.write_text(json.dumps({})) + monkeypatch.setattr(starpilot_theme, "STOCK_THEME_COLORS_PATH", stock_path) + monkeypatch.setattr(starpilot_theme, "ACTIVE_THEME_COLORS_PATH", active_path) + monkeypatch.setattr(starpilot_theme.gui_app, "_frame", 30) + file_stamps = _track_file_stamps(monkeypatch) + _reset_theme_cache() + + starpilot_theme.get_theme_color("Path") + monkeypatch.setattr(starpilot_theme.gui_app, "_frame", 0) + starpilot_theme.get_theme_color("Path") + + assert len(file_stamps) == 4