feat(graph): Integrate graph system with pipeline runner

Add support for loading pipelines from TOML graph configs in the
pipeline runner, maintaining full backward compatibility with presets.

- Add graph_config parameter to run_pipeline_mode() function
- Support both preset mode and graph mode with conditional logic
- Graph mode: loads from TOML file, uses graph-defined stages
- Preset mode: maintains existing behavior with manual stage building
- Handle items/context appropriately for each mode (graph uses own data sources)
- CLI display flag works in both modes

Backward compatible: graph_config defaults to None, so existing calls
to run_pipeline_mode(preset_name) continue to work unchanged.
This commit is contained in:
2026-03-21 19:26:45 -07:00
parent 406a58d292
commit 1a7da400e3

View File

@@ -104,8 +104,13 @@ def _handle_pipeline_mutation(pipeline: Pipeline, command: dict) -> bool:
return False return False
def run_pipeline_mode(preset_name: str = "demo"): def run_pipeline_mode(preset_name: str = "demo", graph_config: str | None = None):
"""Run using the new unified pipeline architecture.""" """Run using the new unified pipeline architecture.
Args:
preset_name: Name of the preset to use
graph_config: Path to a TOML graph configuration file (optional)
"""
import engine.effects.plugins as effects_plugins import engine.effects.plugins as effects_plugins
from engine.effects import PerformanceMonitor, set_monitor from engine.effects import PerformanceMonitor, set_monitor
@@ -117,6 +122,53 @@ def run_pipeline_mode(preset_name: str = "demo"):
monitor = PerformanceMonitor() monitor = PerformanceMonitor()
set_monitor(monitor) set_monitor(monitor)
# Check if graph config is provided
using_graph_config = graph_config is not None
if using_graph_config:
from engine.pipeline.graph_toml import load_pipeline_from_toml
print(f" \033[38;5;245mLoading graph from: {graph_config}\033[0m")
# Determine viewport size
viewport_width = 80
viewport_height = 24
if "--viewport" in sys.argv:
idx = sys.argv.index("--viewport")
if idx + 1 < len(sys.argv):
vp = sys.argv[idx + 1]
try:
viewport_width, viewport_height = map(int, vp.split("x"))
except ValueError:
print("Error: Invalid viewport format. Use WxH (e.g., 40x15)")
sys.exit(1)
# Load pipeline from graph config
try:
pipeline = load_pipeline_from_toml(
graph_config,
viewport_width=viewport_width,
viewport_height=viewport_height,
)
except Exception as e:
print(f" \033[38;5;196mError loading graph config: {e}\033[0m")
sys.exit(1)
# Set params for display
from engine.pipeline.params import PipelineParams
params = PipelineParams(
viewport_width=viewport_width, viewport_height=viewport_height
)
# Set display name from graph or CLI
display_name = "terminal" # Default for graph mode
if "--display" in sys.argv:
idx = sys.argv.index("--display")
if idx + 1 < len(sys.argv):
display_name = sys.argv[idx + 1]
else:
# Use preset-based pipeline
preset = get_preset(preset_name) preset = get_preset(preset_name)
if not preset: if not preset:
print(f" \033[38;5;196mUnknown preset: {preset_name}\033[0m") print(f" \033[38;5;196mUnknown preset: {preset_name}\033[0m")
@@ -196,10 +248,12 @@ def run_pipeline_mode(preset_name: str = "demo"):
print(f" \033[38;5;82mLoaded {len(items)} items\033[0m") print(f" \033[38;5;82mLoaded {len(items)} items\033[0m")
# CLI --display flag takes priority over preset # CLI --display flag takes priority
# Check if --display was explicitly provided # Check if --display was explicitly provided
display_name = preset.display
display_explicitly_specified = "--display" in sys.argv display_explicitly_specified = "--display" in sys.argv
if not using_graph_config:
# Preset mode: use preset display as default
display_name = preset.display
if display_explicitly_specified: if display_explicitly_specified:
idx = sys.argv.index("--display") idx = sys.argv.index("--display")
if idx + 1 < len(sys.argv): if idx + 1 < len(sys.argv):
@@ -212,6 +266,10 @@ def run_pipeline_mode(preset_name: str = "demo"):
print( print(
" \033[38;5;245mTip: Use --display null for headless mode (useful for testing/capture)\033[0m" " \033[38;5;245mTip: Use --display null for headless mode (useful for testing/capture)\033[0m"
) )
else:
# Graph mode: display_name already set above
if not display_explicitly_specified:
print(f" \033[38;5;245mUsing default display: {display_name}\033[0m")
display = DisplayRegistry.create(display_name) display = DisplayRegistry.create(display_name)
if not display and not display_name.startswith("multi"): if not display and not display_name.startswith("multi"):
@@ -245,6 +303,9 @@ def run_pipeline_mode(preset_name: str = "demo"):
effect_registry = get_registry() effect_registry = get_registry()
# Only build stages from preset if not using graph config
# (graph config already has all stages defined)
if not using_graph_config:
# Create source stage based on preset source type # Create source stage based on preset source type
if preset.source == "pipeline-inspect": if preset.source == "pipeline-inspect":
from engine.data_sources.pipeline_introspection import ( from engine.data_sources.pipeline_introspection import (
@@ -271,7 +332,9 @@ def run_pipeline_mode(preset_name: str = "demo"):
from engine.pipeline.adapters import DataSourceStage from engine.pipeline.adapters import DataSourceStage
list_source = ListDataSource(items, name=preset.source) list_source = ListDataSource(items, name=preset.source)
pipeline.add_stage("source", DataSourceStage(list_source, name=preset.source)) pipeline.add_stage(
"source", DataSourceStage(list_source, name=preset.source)
)
# Add camera state update stage if specified in preset (must run before viewport filter) # Add camera state update stage if specified in preset (must run before viewport filter)
camera = None camera = None
@@ -307,6 +370,8 @@ def run_pipeline_mode(preset_name: str = "demo"):
"camera_update", CameraClockStage(camera, name="camera-clock") "camera_update", CameraClockStage(camera, name="camera-clock")
) )
# Only build stages from preset if not using graph config
if not using_graph_config:
# Add FontStage for headlines/poetry (default for demo) # Add FontStage for headlines/poetry (default for demo)
if preset.source in ["headlines", "poetry"]: if preset.source in ["headlines", "poetry"]:
from engine.pipeline.adapters import FontStage, ViewportFilterStage from engine.pipeline.adapters import FontStage, ViewportFilterStage
@@ -318,7 +383,9 @@ def run_pipeline_mode(preset_name: str = "demo"):
pipeline.add_stage("font", FontStage(name="font")) pipeline.add_stage("font", FontStage(name="font"))
else: else:
# Fallback to simple conversion for other sources # Fallback to simple conversion for other sources
pipeline.add_stage("render", SourceItemsToBufferStage(name="items-to-buffer")) pipeline.add_stage(
"render", SourceItemsToBufferStage(name="items-to-buffer")
)
# Add camera stage if specified in preset (after font/render stage) # Add camera stage if specified in preset (after font/render stage)
if camera: if camera:
@@ -328,7 +395,8 @@ def run_pipeline_mode(preset_name: str = "demo"):
effect = effect_registry.get(effect_name) effect = effect_registry.get(effect_name)
if effect: if effect:
pipeline.add_stage( pipeline.add_stage(
f"effect_{effect_name}", create_stage_from_effect(effect, effect_name) f"effect_{effect_name}",
create_stage_from_effect(effect, effect_name),
) )
# Add message overlay stage if enabled # Add message overlay stage if enabled
@@ -831,7 +899,13 @@ def run_pipeline_mode(preset_name: str = "demo"):
ctx = pipeline.context ctx = pipeline.context
ctx.params = params ctx.params = params
ctx.set("display", display) ctx.set("display", display)
# For graph mode, items might not be defined - use empty list if needed
if not using_graph_config:
ctx.set("items", items) ctx.set("items", items)
else:
# Graph-based pipelines typically use their own data sources
# But we can set an empty list for compatibility
ctx.set("items", [])
ctx.set("pipeline", pipeline) ctx.set("pipeline", pipeline)
ctx.set("pipeline_order", pipeline.execution_order) ctx.set("pipeline_order", pipeline.execution_order)
ctx.set("camera_y", 0) ctx.set("camera_y", 0)