Major changes: - Pipeline architecture with capability-based dependency resolution - Effects plugin system with performance monitoring - Display abstraction with multiple backends (terminal, null, websocket) - Camera system for viewport scrolling - Sensor framework for real-time input - Command-and-control system via ntfy - WebSocket display backend for browser clients - Comprehensive test suite and documentation Issue #48: ADR for preset scripting language included This commit consolidates 110 individual commits into a single feature integration that can be reviewed and tested before further refinement.
6.7 KiB
Mainline Pipeline
Architecture Overview
The Mainline pipeline uses a Stage-based architecture with capability-based dependency resolution. Stages declare capabilities (what they provide) and dependencies (what they need), and the Pipeline resolves dependencies using prefix matching.
Source Stage → Render Stage → Effect Stages → Display Stage
↓
Camera Stage (provides camera.state capability)
Capability-Based Dependency Resolution
Stages declare capabilities and dependencies:
- Capabilities: What the stage provides (e.g.,
source,render.output,display.output,camera.state) - Dependencies: What the stage needs (e.g.,
source,render.output,camera.state)
The Pipeline resolves dependencies using prefix matching:
"source"matches"source.headlines","source.poetry", etc."camera.state"matches the camera state capability provided byCameraClockStage- This allows flexible composition without hardcoding specific stage names
Minimum Capabilities
The pipeline requires these minimum capabilities to function:
"source"- Data source capability (provides raw items)"render.output"- Rendered content capability"display.output"- Display output capability"camera.state"- Camera state for viewport filtering
These are automatically injected if missing by the ensure_minimum_capabilities() method.
Stage Registry
The StageRegistry discovers and registers stages automatically:
- Scans
engine/stages/for stage implementations - Registers stages by their declared capabilities
- Enables runtime stage discovery and composition
Stage-Based Pipeline Flow
flowchart TD
subgraph Stages["Stage Pipeline"]
subgraph SourceStage["Source Stage (provides: source.*)"]
Headlines[HeadlinesSource]
Poetry[PoetrySource]
Pipeline[PipelineSource]
end
subgraph RenderStage["Render Stage (provides: render.*)"]
Render[RenderStage]
Canvas[Canvas]
Camera[Camera]
end
subgraph EffectStages["Effect Stages (provides: effect.*)"]
Noise[NoiseEffect]
Fade[FadeEffect]
Glitch[GlitchEffect]
Firehose[FirehoseEffect]
Hud[HudEffect]
end
subgraph DisplayStage["Display Stage (provides: display.*)"]
Terminal[TerminalDisplay]
Pygame[PygameDisplay]
WebSocket[WebSocketDisplay]
Null[NullDisplay]
end
end
subgraph Capabilities["Capability Map"]
SourceCaps["source.headlines<br/>source.poetry<br/>source.pipeline"]
RenderCaps["render.output<br/>render.canvas"]
EffectCaps["effect.noise<br/>effect.fade<br/>effect.glitch"]
DisplayCaps["display.output<br/>display.terminal"]
end
SourceStage --> RenderStage
RenderStage --> EffectStages
EffectStages --> DisplayStage
SourceStage --> SourceCaps
RenderStage --> RenderCaps
EffectStages --> EffectCaps
DisplayStage --> DisplayCaps
style SourceStage fill:#f9f,stroke:#333
style RenderStage fill:#bbf,stroke:#333
style EffectStages fill:#fbf,stroke:#333
style DisplayStage fill:#bfb,stroke:#333
Stage Adapters
Existing components are wrapped as Stages via adapters:
Source Stage Adapter
- Wraps
HeadlinesDataSource,PoetryDataSource, etc. - Provides
source.*capabilities - Fetches data and outputs to pipeline buffer
Render Stage Adapter
- Wraps
StreamController,Camera,render_ticker_zone - Provides
render.outputcapability - Processes content and renders to canvas
Effect Stage Adapter
- Wraps
EffectChainand individual effect plugins - Provides
effect.*capabilities - Applies visual effects to rendered content
Display Stage Adapter
- Wraps
TerminalDisplay,PygameDisplay, etc. - Provides
display.*capabilities - Outputs final buffer to display backend
Pipeline Mutation API
The Pipeline supports dynamic mutation during runtime:
Core Methods
add_stage(name, stage, initialize=True)- Add a stageremove_stage(name, cleanup=True)- Remove a stage and rebuild execution orderreplace_stage(name, new_stage, preserve_state=True)- Replace a stageswap_stages(name1, name2)- Swap two stagesmove_stage(name, after=None, before=None)- Move a stage in execution orderenable_stage(name)/disable_stage(name)- Enable/disable stages
Safety Checks
can_hot_swap(name)- Check if a stage can be safely hot-swappedcleanup_stage(name)- Clean up specific stage without removing it
WebSocket Commands
The mutation API is accessible via WebSocket for remote control:
{"action": "remove_stage", "stage": "stage_name"}
{"action": "swap_stages", "stage1": "name1", "stage2": "name2"}
{"action": "enable_stage", "stage": "stage_name"}
{"action": "cleanup_stage", "stage": "stage_name"}
Camera Modes
The Camera supports the following modes:
- FEED: Single item view (static or rapid cycling)
- SCROLL: Smooth vertical scrolling (movie credits style)
- HORIZONTAL: Left/right movement
- OMNI: Combination of vertical and horizontal
- FLOATING: Sinusoidal/bobbing motion
- BOUNCE: DVD-style bouncing off edges
- RADIAL: Polar coordinate scanning (radar sweep)
Note: Camera state is provided by CameraClockStage (capability: camera.state) which updates independently of data flow. The CameraStage applies viewport transformations (capability: camera).
Animation & Presets
flowchart LR
subgraph Preset["Preset"]
PP[PipelineParams]
AC[AnimationController]
end
subgraph AnimationController["AnimationController"]
Clock[Clock]
Events[Events]
Triggers[Triggers]
end
subgraph Triggers["Trigger Types"]
TIME[TIME]
FRAME[FRAME]
CYCLE[CYCLE]
COND[CONDITION]
MANUAL[MANUAL]
end
PP --> AC
Clock --> AC
Events --> AC
Triggers --> Events
Camera Modes State Diagram
stateDiagram-v2
[*] --> Vertical
Vertical --> Horizontal: mode change
Horizontal --> Omni: mode change
Omni --> Floating: mode change
Floating --> Trace: mode change
Trace --> Vertical: mode change
state Vertical {
[*] --> ScrollUp
ScrollUp --> ScrollUp: +y each frame
}
state Horizontal {
[*] --> ScrollLeft
ScrollLeft --> ScrollLeft: +x each frame
}
state Omni {
[*] --> Diagonal
Diagonal --> Diagonal: +x, +y each frame
}
state Floating {
[*] --> Bobbing
Bobbing --> Bobbing: sin(time) for x,y
}
state Trace {
[*] --> FollowPath
FollowPath --> FollowPath: node by node
}