- Add reuse parameter to Display.init() for all backends - PygameDisplay: reuse existing SDL window via class-level flag - TerminalDisplay: skip re-init when reuse=True - WebSocketDisplay: skip server start when reuse=True - SixelDisplay, KittyDisplay, NullDisplay: ignore reuse (not applicable) - MultiDisplay: pass reuse to child displays - Update benchmark.py to reuse pygame display for effect benchmarks - Add test_websocket_e2e.py with e2e marker - Register e2e marker in pyproject.toml
124 lines
3.4 KiB
Python
124 lines
3.4 KiB
Python
"""
|
|
Display backend system with registry pattern.
|
|
|
|
Allows swapping output backends via the Display protocol.
|
|
Supports auto-discovery of display backends.
|
|
"""
|
|
|
|
from typing import Protocol
|
|
|
|
from engine.display.backends.kitty import KittyDisplay
|
|
from engine.display.backends.multi import MultiDisplay
|
|
from engine.display.backends.null import NullDisplay
|
|
from engine.display.backends.pygame import PygameDisplay
|
|
from engine.display.backends.sixel import SixelDisplay
|
|
from engine.display.backends.terminal import TerminalDisplay
|
|
from engine.display.backends.websocket import WebSocketDisplay
|
|
|
|
|
|
class Display(Protocol):
|
|
"""Protocol for display backends.
|
|
|
|
All display backends must implement:
|
|
- width, height: Terminal dimensions
|
|
- init(width, height, reuse=False): Initialize the display
|
|
- show(buffer): Render buffer to display
|
|
- clear(): Clear the display
|
|
- cleanup(): Shutdown the display
|
|
|
|
The reuse flag allows attaching to an existing display instance
|
|
rather than creating a new window/connection.
|
|
"""
|
|
|
|
width: int
|
|
height: int
|
|
|
|
def init(self, width: int, height: int, reuse: bool = False) -> None:
|
|
"""Initialize display with dimensions.
|
|
|
|
Args:
|
|
width: Terminal width in characters
|
|
height: Terminal height in rows
|
|
reuse: If True, attach to existing display instead of creating new
|
|
"""
|
|
...
|
|
|
|
def show(self, buffer: list[str]) -> None:
|
|
"""Show buffer on display."""
|
|
...
|
|
|
|
def clear(self) -> None:
|
|
"""Clear display."""
|
|
...
|
|
|
|
def cleanup(self) -> None:
|
|
"""Shutdown display."""
|
|
...
|
|
|
|
|
|
class DisplayRegistry:
|
|
"""Registry for display backends with auto-discovery."""
|
|
|
|
_backends: dict[str, type[Display]] = {}
|
|
_initialized = False
|
|
|
|
@classmethod
|
|
def register(cls, name: str, backend_class: type[Display]) -> None:
|
|
"""Register a display backend."""
|
|
cls._backends[name.lower()] = backend_class
|
|
|
|
@classmethod
|
|
def get(cls, name: str) -> type[Display] | None:
|
|
"""Get a display backend class by name."""
|
|
return cls._backends.get(name.lower())
|
|
|
|
@classmethod
|
|
def list_backends(cls) -> list[str]:
|
|
"""List all available display backend names."""
|
|
return list(cls._backends.keys())
|
|
|
|
@classmethod
|
|
def create(cls, name: str, **kwargs) -> Display | None:
|
|
"""Create a display instance by name."""
|
|
backend_class = cls.get(name)
|
|
if backend_class:
|
|
return backend_class(**kwargs)
|
|
return None
|
|
|
|
@classmethod
|
|
def initialize(cls) -> None:
|
|
"""Initialize and register all built-in backends."""
|
|
if cls._initialized:
|
|
return
|
|
|
|
cls.register("terminal", TerminalDisplay)
|
|
cls.register("null", NullDisplay)
|
|
cls.register("websocket", WebSocketDisplay)
|
|
cls.register("sixel", SixelDisplay)
|
|
cls.register("kitty", KittyDisplay)
|
|
cls.register("pygame", PygameDisplay)
|
|
|
|
cls._initialized = True
|
|
|
|
|
|
def get_monitor():
|
|
"""Get the performance monitor."""
|
|
try:
|
|
from engine.effects.performance import get_monitor as _get_monitor
|
|
|
|
return _get_monitor()
|
|
except Exception:
|
|
return None
|
|
|
|
|
|
__all__ = [
|
|
"Display",
|
|
"DisplayRegistry",
|
|
"get_monitor",
|
|
"TerminalDisplay",
|
|
"NullDisplay",
|
|
"WebSocketDisplay",
|
|
"SixelDisplay",
|
|
"MultiDisplay",
|
|
]
|