4.6 KiB
name, description, compatibility, metadata
| name | description | compatibility | metadata | ||||
|---|---|---|---|---|---|---|---|
| mainline-display | Display backend implementation and the Display protocol | opencode |
|
What This Skill Covers
This skill covers Mainline's display backend system - how to implement new display backends and how the Display protocol works.
Key Concepts
Display Protocol
All backends implement a common Display protocol (in engine/display/__init__.py):
class Display(Protocol):
width: int
height: int
def init(self, width: int, height: int, reuse: bool = False) -> None:
"""Initialize the display"""
...
def show(self, buf: list[str], border: bool = False) -> None:
"""Display the buffer"""
...
def clear(self) -> None:
"""Clear the display"""
...
def cleanup(self) -> None:
"""Clean up resources"""
...
def get_dimensions(self) -> tuple[int, int]:
"""Return (width, height)"""
...
DisplayRegistry
Discovers and manages backends:
from engine.display import DisplayRegistry
display = DisplayRegistry.create("terminal") # or "websocket", "null", "multi"
Available Backends
| Backend | File | Description |
|---|---|---|
| terminal | backends/terminal.py | ANSI terminal output |
| websocket | backends/websocket.py | Web browser via WebSocket |
| null | backends/null.py | Headless for testing |
| multi | backends/multi.py | Forwards to multiple displays |
| moderngl | backends/moderngl.py | GPU-accelerated OpenGL rendering (optional) |
WebSocket Backend
- WebSocket server: port 8765
- HTTP server: port 8766 (serves client/index.html)
- Client has ANSI color parsing and fullscreen support
Multi Backend
Forwards to multiple displays simultaneously - useful for terminal + websocket.
Adding a New Backend
- Create
engine/display/backends/my_backend.py - Implement the Display protocol methods
- Register in
engine/display/__init__.py'sDisplayRegistry
Required methods:
init(width: int, height: int, reuse: bool = False)- Initialize displayshow(buf: list[str], border: bool = False)- Display bufferclear()- Clear screencleanup()- Clean up resourcesget_dimensions() -> tuple[int, int]- Get terminal dimensions
Optional methods:
title(text: str)- Set window titlecursor(show: bool)- Control cursor
Usage
python mainline.py --display terminal # default
python mainline.py --display websocket
python mainline.py --display moderngl # GPU-accelerated (requires moderngl)
Common Bugs and Patterns
BorderMode.OFF Enum Bug
Problem: BorderMode.OFF has enum value 1 (not 0), and Python enums are always truthy.
Incorrect Code:
if border:
buffer = render_border(buffer, width, height, fps, frame_time)
Correct Code:
from engine.display import BorderMode
if border and border != BorderMode.OFF:
buffer = render_border(buffer, width, height, fps, frame_time)
Why: Checking if border: evaluates to True even when border == BorderMode.OFF because enum members are always truthy in Python.
Context Type Mismatch
Problem: PipelineContext and EffectContext have different APIs for storing data.
PipelineContext: Usesset()/get()for servicesEffectContext: Usesset_state()/get_state()for state
Pattern for Passing Data:
# In pipeline setup (uses PipelineContext)
ctx.set("pipeline_order", pipeline.execution_order)
# In EffectPluginStage (must copy to EffectContext)
effect_ctx.set_state("pipeline_order", ctx.get("pipeline_order"))
Terminal Display ANSI Patterns
Screen Clearing:
output = "\033[H\033[J" + "".join(buffer)
Cursor Positioning (used by HUD effect):
\033[row;colH- Move cursor to row, column- Example:
\033[1;1H- Move to row 1, column 1
Key Insight: Terminal display joins buffer lines WITHOUT newlines, relying on ANSI cursor positioning codes to move the cursor to the correct location for each line.
EffectPluginStage Context Copying
Problem: When effects need access to pipeline services (like pipeline_order), they must be copied from PipelineContext to EffectContext.
Pattern:
# In EffectPluginStage.process()
# Copy pipeline_order from PipelineContext services to EffectContext state
pipeline_order = ctx.get("pipeline_order")
if pipeline_order:
effect_ctx.set_state("pipeline_order", pipeline_order)
This ensures effects can access ctx.get_state("pipeline_order") in their process method.