## Summary Fixed critical performance issue where demo/poetry presets would hang for 10+ seconds due to FontStage rendering all 1438+ headline items instead of just the visible ~5 items. ## Changes ### Core Fix: ViewportFilterStage - New pipeline stage that filters items to only those fitting in the viewport - Reduces 1438 items → ~5 items (288x reduction) before FontStage - Prevents expensive PIL font rendering operations on items that won't be displayed - Located: engine/pipeline/adapters.py:348-403 ### Pipeline Integration - Updated app.py to add ViewportFilterStage before FontStage for headlines/poetry sources - Ensures correct data flow: source → viewport_filter → font → camera → effects → display - ViewportFilterStage depends on 'source' capability, providing pass-through filtering ### Display Protocol Enhancement - Added is_quit_requested() and clear_quit_request() method signatures to Display protocol - Documented as optional methods for backends supporting keyboard input - Already implemented by pygame backend, now formally part of protocol ### Debug Infrastructure - Added MAINLINE_DEBUG_DATAFLOW environment variable logging throughout pipeline - Logs stage input/output types and data sizes to stderr (when flag enabled) - Verified working: 1438 → 5 item reduction shown in debug output ### Performance Testing - Added pytest-benchmark (v5.2.3) as dev dependency for statistical benchmarking - Created comprehensive performance regression tests (tests/test_performance_regression.py) - Tests verify: - ViewportFilterStage filters 2000 items efficiently (<1ms) - FontStage processes filtered items quickly (<50ms) - 288x performance improvement ratio maintained - Pipeline doesn't hang with large datasets - All 523 tests passing, including 7 new performance tests ## Performance Impact **Before:** FontStage renders all 1438 items per frame → 10+ second hang **After:** FontStage renders ~5 items per frame → sub-second execution Real-world impact: Demo preset now responsive and usable with news sources. ## Testing - Unit tests: 523 passed, 16 skipped - Regression tests: Catch performance degradation with large datasets - E2E verification: Debug logging confirms correct pipeline flow - Benchmark suite: Statistical performance tracking enabled
111 lines
2.3 KiB
TOML
111 lines
2.3 KiB
TOML
[project]
|
|
name = "mainline"
|
|
version = "0.1.0"
|
|
description = "Terminal news ticker with Matrix aesthetic"
|
|
readme = "README.md"
|
|
requires-python = ">=3.10"
|
|
authors = [
|
|
{ name = "Mainline", email = "mainline@example.com" }
|
|
]
|
|
license = { text = "MIT" }
|
|
classifiers = [
|
|
"Development Status :: 4 - Beta",
|
|
"Environment :: Console",
|
|
"License :: OSI Approved :: MIT License",
|
|
"Programming Language :: Python :: 3",
|
|
"Programming Language :: Python :: 3.10",
|
|
"Programming Language :: Python :: 3.11",
|
|
"Programming Language :: Python :: 3.12",
|
|
"Topic :: Terminals",
|
|
]
|
|
|
|
dependencies = [
|
|
"feedparser>=6.0.0",
|
|
"Pillow>=10.0.0",
|
|
"pyright>=1.1.408",
|
|
"numpy>=1.24.0",
|
|
]
|
|
|
|
[project.optional-dependencies]
|
|
mic = [
|
|
"sounddevice>=0.4.0",
|
|
"numpy>=1.24.0",
|
|
]
|
|
websocket = [
|
|
"websockets>=12.0",
|
|
]
|
|
sixel = [
|
|
"Pillow>=10.0.0",
|
|
]
|
|
pygame = [
|
|
"pygame>=2.0.0",
|
|
]
|
|
browser = [
|
|
"playwright>=1.40.0",
|
|
]
|
|
dev = [
|
|
"pytest>=8.0.0",
|
|
"pytest-benchmark>=4.0.0",
|
|
"pytest-cov>=4.1.0",
|
|
"pytest-mock>=3.12.0",
|
|
"ruff>=0.1.0",
|
|
]
|
|
|
|
[project.scripts]
|
|
mainline = "engine.app:main"
|
|
|
|
[build-system]
|
|
requires = ["hatchling"]
|
|
build-backend = "hatchling.build"
|
|
|
|
[dependency-groups]
|
|
dev = [
|
|
"pytest>=8.0.0",
|
|
"pytest-benchmark>=4.0.0",
|
|
"pytest-cov>=4.1.0",
|
|
"pytest-mock>=3.12.0",
|
|
"ruff>=0.1.0",
|
|
]
|
|
|
|
[tool.pytest.ini_options]
|
|
testpaths = ["tests"]
|
|
python_files = ["test_*.py"]
|
|
python_functions = ["test_*"]
|
|
addopts = [
|
|
"--strict-markers",
|
|
"--tb=short",
|
|
"-v",
|
|
]
|
|
markers = [
|
|
"benchmark: marks tests as performance benchmarks (may be slow)",
|
|
"e2e: marks tests as end-to-end tests (require network/display)",
|
|
"integration: marks tests as integration tests (require external services)",
|
|
"ntfy: marks tests that require ntfy service",
|
|
]
|
|
filterwarnings = [
|
|
"ignore::DeprecationWarning",
|
|
]
|
|
|
|
[tool.coverage.run]
|
|
source = ["engine"]
|
|
branch = true
|
|
|
|
[tool.coverage.report]
|
|
exclude_lines = [
|
|
"pragma: no cover",
|
|
"def __repr__",
|
|
"raise AssertionError",
|
|
"raise NotImplementedError",
|
|
"if __name__ == .__main__.:",
|
|
"if TYPE_CHECKING:",
|
|
"@abstractmethod",
|
|
]
|
|
|
|
[tool.ruff]
|
|
line-length = 88
|
|
target-version = "py310"
|
|
|
|
[tool.ruff.lint]
|
|
select = ["E", "F", "W", "I", "N", "UP", "B", "C4", "SIM"]
|
|
ignore = ["E501", "SIM105", "N806", "B007", "SIM108"]
|