forked from genewildish/Mainline
feat(display): add reuse flag to Display protocol
- 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
This commit is contained in:
@@ -126,7 +126,14 @@ class KittyDisplay:
|
||||
self._initialized = False
|
||||
self._font_path = None
|
||||
|
||||
def init(self, width: int, height: int) -> None:
|
||||
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: Ignored for KittyDisplay (protocol doesn't support reuse)
|
||||
"""
|
||||
self.width = width
|
||||
self.height = height
|
||||
self._initialized = True
|
||||
|
||||
@@ -4,7 +4,10 @@ Multi display backend - forwards to multiple displays.
|
||||
|
||||
|
||||
class MultiDisplay:
|
||||
"""Display that forwards to multiple displays."""
|
||||
"""Display that forwards to multiple displays.
|
||||
|
||||
Supports reuse - passes reuse flag to all child displays.
|
||||
"""
|
||||
|
||||
width: int = 80
|
||||
height: int = 24
|
||||
@@ -14,11 +17,18 @@ class MultiDisplay:
|
||||
self.width = 80
|
||||
self.height = 24
|
||||
|
||||
def init(self, width: int, height: int) -> None:
|
||||
def init(self, width: int, height: int, reuse: bool = False) -> None:
|
||||
"""Initialize all child displays with dimensions.
|
||||
|
||||
Args:
|
||||
width: Terminal width in characters
|
||||
height: Terminal height in rows
|
||||
reuse: If True, use reuse mode for child displays
|
||||
"""
|
||||
self.width = width
|
||||
self.height = height
|
||||
for d in self.displays:
|
||||
d.init(width, height)
|
||||
d.init(width, height, reuse=reuse)
|
||||
|
||||
def show(self, buffer: list[str]) -> None:
|
||||
for d in self.displays:
|
||||
|
||||
@@ -6,12 +6,23 @@ import time
|
||||
|
||||
|
||||
class NullDisplay:
|
||||
"""Headless/null display - discards all output."""
|
||||
"""Headless/null display - discards all output.
|
||||
|
||||
This display does nothing - useful for headless benchmarking
|
||||
or when no display output is needed.
|
||||
"""
|
||||
|
||||
width: int = 80
|
||||
height: int = 24
|
||||
|
||||
def init(self, width: int, height: int) -> None:
|
||||
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: Ignored for NullDisplay (no resources to reuse)
|
||||
"""
|
||||
self.width = width
|
||||
self.height = height
|
||||
|
||||
|
||||
@@ -6,12 +6,16 @@ import time
|
||||
|
||||
|
||||
class PygameDisplay:
|
||||
"""Pygame display backend - renders to native window."""
|
||||
"""Pygame display backend - renders to native window.
|
||||
|
||||
Supports reuse mode - when reuse=True, skips SDL initialization
|
||||
and reuses the existing pygame window from a previous instance.
|
||||
"""
|
||||
|
||||
width: int = 80
|
||||
height: int = 24
|
||||
window_width: int = 800
|
||||
window_height: int = 600
|
||||
_pygame_initialized: bool = False
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@@ -76,20 +80,37 @@ class PygameDisplay:
|
||||
|
||||
return None
|
||||
|
||||
def init(self, width: int, height: int) -> None:
|
||||
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 pygame window instead of creating new
|
||||
"""
|
||||
self.width = width
|
||||
self.height = height
|
||||
|
||||
import os
|
||||
|
||||
os.environ["SDL_VIDEODRIVER"] = "x11"
|
||||
|
||||
try:
|
||||
import pygame
|
||||
except ImportError:
|
||||
return
|
||||
|
||||
if reuse and PygameDisplay._pygame_initialized:
|
||||
self._pygame = pygame
|
||||
self._initialized = True
|
||||
return
|
||||
|
||||
pygame.init()
|
||||
self._pygame = pygame
|
||||
pygame.display.set_caption("Mainline")
|
||||
|
||||
self._screen = pygame.display.set_mode((self.window_width, self.window_height))
|
||||
pygame.display.set_caption("Mainline")
|
||||
self._pygame = pygame
|
||||
PygameDisplay._pygame_initialized = True
|
||||
|
||||
font_path = self._get_font_path()
|
||||
if font_path:
|
||||
@@ -218,6 +239,18 @@ class PygameDisplay:
|
||||
self._screen.fill((0, 0, 0))
|
||||
self._pygame.display.flip()
|
||||
|
||||
def cleanup(self) -> None:
|
||||
if self._pygame:
|
||||
def cleanup(self, quit_pygame: bool = True) -> None:
|
||||
"""Cleanup display resources.
|
||||
|
||||
Args:
|
||||
quit_pygame: If True, quit pygame entirely. Set to False when
|
||||
reusing the display to avoid closing shared window.
|
||||
"""
|
||||
if quit_pygame and self._pygame:
|
||||
self._pygame.quit()
|
||||
PygameDisplay._pygame_initialized = False
|
||||
|
||||
@classmethod
|
||||
def reset_state(cls) -> None:
|
||||
"""Reset pygame state - useful for testing."""
|
||||
cls._pygame_initialized = False
|
||||
|
||||
@@ -271,7 +271,14 @@ class SixelDisplay:
|
||||
|
||||
return None
|
||||
|
||||
def init(self, width: int, height: int) -> None:
|
||||
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: Ignored for SixelDisplay
|
||||
"""
|
||||
self.width = width
|
||||
self.height = height
|
||||
self._initialized = True
|
||||
|
||||
@@ -6,17 +6,32 @@ import time
|
||||
|
||||
|
||||
class TerminalDisplay:
|
||||
"""ANSI terminal display backend."""
|
||||
"""ANSI terminal display backend.
|
||||
|
||||
Renders buffer to stdout using ANSI escape codes.
|
||||
Supports reuse - when reuse=True, skips re-initializing terminal state.
|
||||
"""
|
||||
|
||||
width: int = 80
|
||||
height: int = 24
|
||||
_initialized: bool = False
|
||||
|
||||
def init(self, width: int, height: int) -> None:
|
||||
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, skip terminal re-initialization
|
||||
"""
|
||||
from engine.terminal import CURSOR_OFF
|
||||
|
||||
self.width = width
|
||||
self.height = height
|
||||
print(CURSOR_OFF, end="", flush=True)
|
||||
|
||||
if not reuse or not self._initialized:
|
||||
print(CURSOR_OFF, end="", flush=True)
|
||||
self._initialized = True
|
||||
|
||||
def show(self, buffer: list[str]) -> None:
|
||||
import sys
|
||||
|
||||
@@ -86,12 +86,20 @@ class WebSocketDisplay:
|
||||
"""Check if WebSocket support is available."""
|
||||
return self._available
|
||||
|
||||
def init(self, width: int, height: int) -> None:
|
||||
"""Initialize display with dimensions and start server."""
|
||||
def init(self, width: int, height: int, reuse: bool = False) -> None:
|
||||
"""Initialize display with dimensions and start server.
|
||||
|
||||
Args:
|
||||
width: Terminal width in characters
|
||||
height: Terminal height in rows
|
||||
reuse: If True, skip starting servers (assume already running)
|
||||
"""
|
||||
self.width = width
|
||||
self.height = height
|
||||
self.start_server()
|
||||
self.start_http_server()
|
||||
|
||||
if not reuse or not self._server_running:
|
||||
self.start_server()
|
||||
self.start_http_server()
|
||||
|
||||
def show(self, buffer: list[str]) -> None:
|
||||
"""Broadcast buffer to all connected clients."""
|
||||
|
||||
Reference in New Issue
Block a user