feat(graph): Add adapter to convert graphs to pipelines
Bridge the new graph abstraction with existing Stage-based pipeline system for backward compatibility. - Add GraphAdapter class to map nodes to Stage implementations - Handle effect intensity configuration (sets global effect state) - Map camera modes to Camera factory methods (feed, scroll, horizontal, etc.) - Auto-inject required dependencies (render, camera_update) via pipeline capabilities - Support for all major node types: source, camera, effect, position, display The adapter ensures that graphs seamlessly integrate with the existing pipeline architecture while providing a cleaner abstraction layer.
This commit is contained in:
161
engine/pipeline/graph_adapter.py
Normal file
161
engine/pipeline/graph_adapter.py
Normal file
@@ -0,0 +1,161 @@
|
||||
"""Adapter to convert Graph to Pipeline stages.
|
||||
|
||||
This module bridges the new graph-based abstraction with the existing
|
||||
Stage-based pipeline system for backward compatibility.
|
||||
"""
|
||||
|
||||
from typing import Dict, Any, List, Optional
|
||||
|
||||
from engine.pipeline.graph import Graph, NodeType
|
||||
from engine.pipeline.controller import Pipeline, PipelineConfig
|
||||
from engine.pipeline.core import PipelineContext
|
||||
from engine.pipeline.params import PipelineParams
|
||||
from engine.pipeline.adapters import (
|
||||
CameraStage,
|
||||
DataSourceStage,
|
||||
DisplayStage,
|
||||
EffectPluginStage,
|
||||
FontStage,
|
||||
MessageOverlayStage,
|
||||
PositionStage,
|
||||
ViewportFilterStage,
|
||||
create_stage_from_display,
|
||||
create_stage_from_effect,
|
||||
)
|
||||
from engine.pipeline.adapters.positioning import PositioningMode
|
||||
from engine.display import DisplayRegistry
|
||||
from engine.effects import get_registry
|
||||
from engine.data_sources.sources import EmptyDataSource, HeadlinesDataSource
|
||||
from engine.camera import Camera
|
||||
|
||||
|
||||
class GraphAdapter:
|
||||
"""Converts Graph to Pipeline with existing Stage classes."""
|
||||
|
||||
def __init__(self, graph: Graph):
|
||||
self.graph = graph
|
||||
self.pipeline: Optional[Pipeline] = None
|
||||
self.context: Optional[PipelineContext] = None
|
||||
|
||||
def build_pipeline(
|
||||
self, viewport_width: int = 80, viewport_height: int = 24
|
||||
) -> Pipeline:
|
||||
"""Build a Pipeline from the Graph."""
|
||||
# Create pipeline context
|
||||
self.context = PipelineContext()
|
||||
self.context.terminal_width = viewport_width
|
||||
self.context.terminal_height = viewport_height
|
||||
|
||||
# Create params
|
||||
params = PipelineParams(
|
||||
viewport_width=viewport_width,
|
||||
viewport_height=viewport_height,
|
||||
)
|
||||
self.context.params = params
|
||||
|
||||
# Create pipeline config
|
||||
config = PipelineConfig()
|
||||
|
||||
# Create pipeline
|
||||
self.pipeline = Pipeline(config=config, context=self.context)
|
||||
|
||||
# Map graph nodes to pipeline stages
|
||||
self._map_nodes_to_stages()
|
||||
|
||||
# Build pipeline
|
||||
self.pipeline.build()
|
||||
|
||||
return self.pipeline
|
||||
|
||||
def _map_nodes_to_stages(self) -> None:
|
||||
"""Map graph nodes to pipeline stages."""
|
||||
for name, node in self.graph.nodes.items():
|
||||
if not node.enabled:
|
||||
continue
|
||||
|
||||
stage = self._create_stage_from_node(name, node)
|
||||
if stage:
|
||||
self.pipeline.add_stage(name, stage)
|
||||
|
||||
def _create_stage_from_node(self, name: str, node) -> Optional:
|
||||
"""Create a pipeline stage from a graph node."""
|
||||
stage = None
|
||||
|
||||
if node.type == NodeType.SOURCE:
|
||||
source_type = node.config.get("source", "headlines")
|
||||
if source_type == "headlines":
|
||||
source = HeadlinesDataSource()
|
||||
elif source_type == "empty":
|
||||
source = EmptyDataSource(
|
||||
width=self.context.terminal_width,
|
||||
height=self.context.terminal_height,
|
||||
)
|
||||
else:
|
||||
source = EmptyDataSource(
|
||||
width=self.context.terminal_width,
|
||||
height=self.context.terminal_height,
|
||||
)
|
||||
stage = DataSourceStage(source, name=name)
|
||||
|
||||
elif node.type == NodeType.CAMERA:
|
||||
mode = node.config.get("mode", "scroll")
|
||||
speed = node.config.get("speed", 1.0)
|
||||
# Map mode string to Camera factory method
|
||||
mode_lower = mode.lower()
|
||||
if hasattr(Camera, mode_lower):
|
||||
camera_factory = getattr(Camera, mode_lower)
|
||||
camera = camera_factory(speed=speed)
|
||||
else:
|
||||
# Fallback to scroll mode
|
||||
camera = Camera.scroll(speed=speed)
|
||||
stage = CameraStage(camera, name=name)
|
||||
|
||||
elif node.type == NodeType.DISPLAY:
|
||||
backend = node.config.get("backend", "terminal")
|
||||
positioning = node.config.get("positioning", "mixed")
|
||||
display = DisplayRegistry.create(backend)
|
||||
if display:
|
||||
stage = DisplayStage(display, name=name, positioning=positioning)
|
||||
|
||||
elif node.type == NodeType.EFFECT:
|
||||
effect_name = node.config.get("effect", "")
|
||||
intensity = node.config.get("intensity", 1.0)
|
||||
effect = get_registry().get(effect_name)
|
||||
if effect:
|
||||
# Set effect intensity (modifies global effect state)
|
||||
effect.config.intensity = intensity
|
||||
# Effects typically depend on rendered output
|
||||
dependencies = {"render.output"}
|
||||
stage = EffectPluginStage(effect, name=name, dependencies=dependencies)
|
||||
|
||||
elif node.type == NodeType.RENDER:
|
||||
stage = FontStage(name=name)
|
||||
|
||||
elif node.type == NodeType.OVERLAY:
|
||||
stage = MessageOverlayStage(name=name)
|
||||
|
||||
elif node.type == NodeType.POSITION:
|
||||
mode_str = node.config.get("mode", "mixed")
|
||||
try:
|
||||
mode = PositioningMode(mode_str)
|
||||
except ValueError:
|
||||
mode = PositioningMode.MIXED
|
||||
stage = PositionStage(mode=mode, name=name)
|
||||
|
||||
return stage
|
||||
|
||||
|
||||
def graph_to_pipeline(
|
||||
graph: Graph, viewport_width: int = 80, viewport_height: int = 24
|
||||
) -> Pipeline:
|
||||
"""Convert a Graph to a Pipeline."""
|
||||
adapter = GraphAdapter(graph)
|
||||
return adapter.build_pipeline(viewport_width, viewport_height)
|
||||
|
||||
|
||||
def dict_to_pipeline(
|
||||
data: Dict[str, Any], viewport_width: int = 80, viewport_height: int = 24
|
||||
) -> Pipeline:
|
||||
"""Convert a dictionary to a Pipeline."""
|
||||
graph = Graph().from_dict(data)
|
||||
return graph_to_pipeline(graph, viewport_width, viewport_height)
|
||||
Reference in New Issue
Block a user