forked from genewildish/Mainline
feat: Add gallery presets, MultiDisplay support, and viewport tests
- Add ~20 gallery presets covering sources, effects, cameras, displays - Add MultiDisplay support with --display multi:terminal,pygame syntax - Fix ViewportFilterStage to recompute layout on viewport_width change - Add benchmark.py module for hook-based performance testing - Add viewport resize tests to test_viewport_filter_performance.py
This commit is contained in:
@@ -116,6 +116,20 @@ def run_pipeline_mode(preset_name: str = "demo"):
|
||||
display_name = sys.argv[idx + 1]
|
||||
|
||||
display = DisplayRegistry.create(display_name)
|
||||
if not display and not display_name.startswith("multi"):
|
||||
print(f" \033[38;5;196mFailed to create display: {display_name}\033[0m")
|
||||
sys.exit(1)
|
||||
|
||||
# Handle multi display (format: "multi:terminal,pygame")
|
||||
if not display and display_name.startswith("multi"):
|
||||
parts = display_name[6:].split(
|
||||
","
|
||||
) # "multi:terminal,pygame" -> ["terminal", "pygame"]
|
||||
display = DisplayRegistry.create_multi(parts)
|
||||
if not display:
|
||||
print(f" \033[38;5;196mFailed to create multi display: {parts}\033[0m")
|
||||
sys.exit(1)
|
||||
|
||||
if not display:
|
||||
print(f" \033[38;5;196mFailed to create display: {display_name}\033[0m")
|
||||
sys.exit(1)
|
||||
|
||||
73
engine/benchmark.py
Normal file
73
engine/benchmark.py
Normal file
@@ -0,0 +1,73 @@
|
||||
"""
|
||||
Benchmark module for performance testing.
|
||||
|
||||
Usage:
|
||||
python -m engine.benchmark # Run all benchmarks
|
||||
python -m engine.benchmark --hook # Run benchmarks in hook mode (for CI)
|
||||
python -m engine.benchmark --displays null --iterations 20
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Run performance benchmarks")
|
||||
parser.add_argument(
|
||||
"--hook",
|
||||
action="store_true",
|
||||
help="Run in hook mode (fail on regression)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--displays",
|
||||
default="null",
|
||||
help="Comma-separated list of displays to benchmark",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--iterations",
|
||||
type=int,
|
||||
default=100,
|
||||
help="Number of iterations per benchmark",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
# Run pytest with benchmark markers
|
||||
pytest_args = [
|
||||
"-v",
|
||||
"-m",
|
||||
"benchmark",
|
||||
]
|
||||
|
||||
if args.hook:
|
||||
# Hook mode: stricter settings
|
||||
pytest_args.extend(
|
||||
[
|
||||
"--benchmark-only",
|
||||
"--benchmark-compare",
|
||||
"--benchmark-compare-fail=min:5%", # Fail if >5% slower
|
||||
]
|
||||
)
|
||||
|
||||
# Add display filter if specified
|
||||
if args.displays:
|
||||
pytest_args.extend(["-k", args.displays])
|
||||
|
||||
# Add iterations
|
||||
if args.iterations:
|
||||
# Set environment variable for benchmark tests
|
||||
import os
|
||||
|
||||
os.environ["BENCHMARK_ITERATIONS"] = str(args.iterations)
|
||||
|
||||
# Run pytest
|
||||
import subprocess
|
||||
|
||||
result = subprocess.run(
|
||||
[sys.executable, "-m", "pytest", "tests/test_benchmark.py"] + pytest_args,
|
||||
cwd=None, # Current directory
|
||||
)
|
||||
sys.exit(result.returncode)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -147,6 +147,31 @@ class DisplayRegistry:
|
||||
|
||||
cls._initialized = True
|
||||
|
||||
@classmethod
|
||||
def create_multi(cls, names: list[str]) -> "Display | None":
|
||||
"""Create a MultiDisplay from a list of backend names.
|
||||
|
||||
Args:
|
||||
names: List of display backend names (e.g., ["terminal", "pygame"])
|
||||
|
||||
Returns:
|
||||
MultiDisplay instance or None if any backend fails
|
||||
"""
|
||||
from engine.display.backends.multi import MultiDisplay
|
||||
|
||||
displays = []
|
||||
for name in names:
|
||||
backend = cls.create(name)
|
||||
if backend:
|
||||
displays.append(backend)
|
||||
else:
|
||||
return None
|
||||
|
||||
if not displays:
|
||||
return None
|
||||
|
||||
return MultiDisplay(displays)
|
||||
|
||||
|
||||
def get_monitor():
|
||||
"""Get the performance monitor."""
|
||||
|
||||
@@ -437,8 +437,9 @@ class ViewportFilterStage(Stage):
|
||||
viewport_width = ctx.params.viewport_width if ctx.params else 80
|
||||
camera_y = ctx.get("camera_y", 0)
|
||||
|
||||
# Recompute layout only when item count changes
|
||||
if len(data) != self._cached_count:
|
||||
# Recompute layout when item count OR viewport width changes
|
||||
cached_width = getattr(self, "_cached_width", None)
|
||||
if len(data) != self._cached_count or cached_width != viewport_width:
|
||||
self._layout = []
|
||||
y = 0
|
||||
from engine.render.blocks import estimate_block_height
|
||||
@@ -454,6 +455,7 @@ class ViewportFilterStage(Stage):
|
||||
self._layout.append((y, h))
|
||||
y += h
|
||||
self._cached_count = len(data)
|
||||
self._cached_width = viewport_width
|
||||
|
||||
# Find items visible in [camera_y - buffer, camera_y + viewport_height + buffer]
|
||||
buffer_zone = viewport_height
|
||||
|
||||
Reference in New Issue
Block a user