from __future__ import annotations import argparse from pathlib import Path import time from .engine import OutputSettings, RealtimeEngine from .mapping_xml import load_mapping from .models import PatternParams, SceneState from .patterns import pattern_ids from .renderer import FastRenderer def main() -> None: parser = argparse.ArgumentParser(description="Benchmark the Infinity_Vis_1 render core.") parser.add_argument( "--mapping", type=Path, default=Path(__file__).resolve().parents[2] / "sample_data" / "infinity_mirror_mapping_clean.xml", help="Path to the XML mapping file.", ) parser.add_argument("--frames", type=int, default=600, help="Frames per pattern.") parser.add_argument("--pattern", type=str, default="", help="Benchmark a single pattern id.") parser.add_argument( "--mode", type=str, default="render", choices=["render", "engine"], help="Benchmark only the renderer or the new runtime engine loop.", ) args = parser.parse_args() mapping = load_mapping(args.mapping) renderer = FastRenderer(mapping) stats = renderer.stats() patterns = [args.pattern] if args.pattern else pattern_ids() print(f"Mapping: {mapping.name}") print(f"Tiles: {stats.tile_count} | Controllers: {stats.controller_count} | LEDs: {stats.total_leds}") for pattern_id in patterns: scene = SceneState(pattern_id=pattern_id, params=PatternParams(), tempo_bpm=120.0) if args.mode == "engine": fps, preview_frames, changed_frames = _benchmark_engine(mapping, scene, max(1, args.frames)) print( f"{pattern_id:16s} {fps:9.1f} engine fps " f"| preview {preview_frames:4d} | changed controllers {changed_frames:4d}" ) else: fps = _benchmark_renderer(renderer, scene, max(1, args.frames)) print(f"{pattern_id:16s} {fps:9.1f} render fps") def _benchmark_renderer(renderer: FastRenderer, scene: SceneState, frames: int) -> float: start = time.perf_counter() for frame_index in range(frames): renderer.render(scene, timestamp=start + frame_index / 60.0) elapsed = time.perf_counter() - start return frames / elapsed if elapsed > 0 else 0.0 def _benchmark_engine(mapping, scene: SceneState, frames: int) -> tuple[float, int, int]: engine = RealtimeEngine( mapping, scene=scene, settings=OutputSettings(output_fps=60.0, preview_fps=12.0, delta_sending=True, keepalive_seconds=0.35), ) start = time.perf_counter() preview_frames = 0 changed_frames = 0 for frame_index in range(frames): result = engine.tick(now=start + frame_index / 60.0) if result.preview_snapshot is not None: preview_frames += 1 changed_frames += len(result.changed_payloads) elapsed = time.perf_counter() - start fps = frames / elapsed if elapsed > 0 else 0.0 return fps, preview_frames, changed_frames if __name__ == "__main__": main()