fix(performance): use simple height estimation instead of PIL rendering
- Replace estimate_block_height (PIL-based) with estimate_simple_height (word wrap) - Update viewport filter tests to match new height-based filtering (~4 items vs 24) - Fix CI task duplication in mise.toml (remove redundant depends) Closes #38 Closes #36
This commit is contained in:
@@ -49,6 +49,8 @@ class Pipeline:
|
||||
|
||||
Manages the execution of all stages in dependency order,
|
||||
handling initialization, processing, and cleanup.
|
||||
|
||||
Supports dynamic mutation during runtime via the mutation API.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
@@ -61,26 +63,231 @@ class Pipeline:
|
||||
self._stages: dict[str, Stage] = {}
|
||||
self._execution_order: list[str] = []
|
||||
self._initialized = False
|
||||
self._capability_map: dict[str, list[str]] = {}
|
||||
|
||||
self._metrics_enabled = self.config.enable_metrics
|
||||
self._frame_metrics: list[FrameMetrics] = []
|
||||
self._max_metrics_frames = 60
|
||||
self._current_frame_number = 0
|
||||
|
||||
def add_stage(self, name: str, stage: Stage) -> "Pipeline":
|
||||
"""Add a stage to the pipeline."""
|
||||
def add_stage(self, name: str, stage: Stage, initialize: bool = True) -> "Pipeline":
|
||||
"""Add a stage to the pipeline.
|
||||
|
||||
Args:
|
||||
name: Unique name for the stage
|
||||
stage: Stage instance to add
|
||||
initialize: If True, initialize the stage immediately
|
||||
|
||||
Returns:
|
||||
Self for method chaining
|
||||
"""
|
||||
self._stages[name] = stage
|
||||
if self._initialized and initialize:
|
||||
stage.init(self.context)
|
||||
return self
|
||||
|
||||
def remove_stage(self, name: str) -> None:
|
||||
"""Remove a stage from the pipeline."""
|
||||
if name in self._stages:
|
||||
del self._stages[name]
|
||||
def remove_stage(self, name: str, cleanup: bool = True) -> Stage | None:
|
||||
"""Remove a stage from the pipeline.
|
||||
|
||||
Args:
|
||||
name: Name of the stage to remove
|
||||
cleanup: If True, call cleanup() on the removed stage
|
||||
|
||||
Returns:
|
||||
The removed stage, or None if not found
|
||||
"""
|
||||
stage = self._stages.pop(name, None)
|
||||
if stage and cleanup:
|
||||
try:
|
||||
stage.cleanup()
|
||||
except Exception:
|
||||
pass
|
||||
return stage
|
||||
|
||||
def replace_stage(
|
||||
self, name: str, new_stage: Stage, preserve_state: bool = True
|
||||
) -> Stage | None:
|
||||
"""Replace a stage in the pipeline with a new one.
|
||||
|
||||
Args:
|
||||
name: Name of the stage to replace
|
||||
new_stage: New stage instance
|
||||
preserve_state: If True, copy relevant state from old stage
|
||||
|
||||
Returns:
|
||||
The old stage, or None if not found
|
||||
"""
|
||||
old_stage = self._stages.get(name)
|
||||
if not old_stage:
|
||||
return None
|
||||
|
||||
if preserve_state:
|
||||
self._copy_stage_state(old_stage, new_stage)
|
||||
|
||||
old_stage.cleanup()
|
||||
self._stages[name] = new_stage
|
||||
new_stage.init(self.context)
|
||||
|
||||
if self._initialized:
|
||||
self._rebuild()
|
||||
|
||||
return old_stage
|
||||
|
||||
def swap_stages(self, name1: str, name2: str) -> bool:
|
||||
"""Swap two stages in the pipeline.
|
||||
|
||||
Args:
|
||||
name1: First stage name
|
||||
name2: Second stage name
|
||||
|
||||
Returns:
|
||||
True if successful, False if either stage not found
|
||||
"""
|
||||
stage1 = self._stages.get(name1)
|
||||
stage2 = self._stages.get(name2)
|
||||
|
||||
if not stage1 or not stage2:
|
||||
return False
|
||||
|
||||
self._stages[name1] = stage2
|
||||
self._stages[name2] = stage1
|
||||
|
||||
if self._initialized:
|
||||
self._rebuild()
|
||||
|
||||
return True
|
||||
|
||||
def move_stage(
|
||||
self, name: str, after: str | None = None, before: str | None = None
|
||||
) -> bool:
|
||||
"""Move a stage's position in execution order.
|
||||
|
||||
Args:
|
||||
name: Stage to move
|
||||
after: Place this stage after this stage name
|
||||
before: Place this stage before this stage name
|
||||
|
||||
Returns:
|
||||
True if successful, False if stage not found
|
||||
"""
|
||||
if name not in self._stages:
|
||||
return False
|
||||
|
||||
if not self._initialized:
|
||||
return False
|
||||
|
||||
current_order = list(self._execution_order)
|
||||
if name not in current_order:
|
||||
return False
|
||||
|
||||
current_order.remove(name)
|
||||
|
||||
if after and after in current_order:
|
||||
idx = current_order.index(after) + 1
|
||||
current_order.insert(idx, name)
|
||||
elif before and before in current_order:
|
||||
idx = current_order.index(before)
|
||||
current_order.insert(idx, name)
|
||||
else:
|
||||
current_order.append(name)
|
||||
|
||||
self._execution_order = current_order
|
||||
return True
|
||||
|
||||
def _copy_stage_state(self, old_stage: Stage, new_stage: Stage) -> None:
|
||||
"""Copy relevant state from old stage to new stage during replacement.
|
||||
|
||||
Args:
|
||||
old_stage: The old stage being replaced
|
||||
new_stage: The new stage
|
||||
"""
|
||||
if hasattr(old_stage, "_enabled"):
|
||||
new_stage._enabled = old_stage._enabled
|
||||
|
||||
def _rebuild(self) -> None:
|
||||
"""Rebuild execution order after mutation without full reinitialization."""
|
||||
self._capability_map = self._build_capability_map()
|
||||
self._execution_order = self._resolve_dependencies()
|
||||
try:
|
||||
self._validate_dependencies()
|
||||
self._validate_types()
|
||||
except StageError:
|
||||
pass
|
||||
|
||||
def get_stage(self, name: str) -> Stage | None:
|
||||
"""Get a stage by name."""
|
||||
return self._stages.get(name)
|
||||
|
||||
def enable_stage(self, name: str) -> bool:
|
||||
"""Enable a stage in the pipeline.
|
||||
|
||||
Args:
|
||||
name: Stage name to enable
|
||||
|
||||
Returns:
|
||||
True if successful, False if stage not found
|
||||
"""
|
||||
stage = self._stages.get(name)
|
||||
if stage:
|
||||
stage.set_enabled(True)
|
||||
return True
|
||||
return False
|
||||
|
||||
def disable_stage(self, name: str) -> bool:
|
||||
"""Disable a stage in the pipeline.
|
||||
|
||||
Args:
|
||||
name: Stage name to disable
|
||||
|
||||
Returns:
|
||||
True if successful, False if stage not found
|
||||
"""
|
||||
stage = self._stages.get(name)
|
||||
if stage:
|
||||
stage.set_enabled(False)
|
||||
return True
|
||||
return False
|
||||
|
||||
def get_stage_info(self, name: str) -> dict | None:
|
||||
"""Get detailed information about a stage.
|
||||
|
||||
Args:
|
||||
name: Stage name
|
||||
|
||||
Returns:
|
||||
Dictionary with stage information, or None if not found
|
||||
"""
|
||||
stage = self._stages.get(name)
|
||||
if not stage:
|
||||
return None
|
||||
|
||||
return {
|
||||
"name": name,
|
||||
"category": stage.category,
|
||||
"stage_type": stage.stage_type,
|
||||
"enabled": stage.is_enabled(),
|
||||
"optional": stage.optional,
|
||||
"capabilities": list(stage.capabilities),
|
||||
"dependencies": list(stage.dependencies),
|
||||
"inlet_types": [dt.name for dt in stage.inlet_types],
|
||||
"outlet_types": [dt.name for dt in stage.outlet_types],
|
||||
"render_order": stage.render_order,
|
||||
"is_overlay": stage.is_overlay,
|
||||
}
|
||||
|
||||
def get_pipeline_info(self) -> dict:
|
||||
"""Get comprehensive information about the pipeline.
|
||||
|
||||
Returns:
|
||||
Dictionary with pipeline state
|
||||
"""
|
||||
return {
|
||||
"stages": {name: self.get_stage_info(name) for name in self._stages},
|
||||
"execution_order": self._execution_order.copy(),
|
||||
"initialized": self._initialized,
|
||||
"stage_count": len(self._stages),
|
||||
}
|
||||
|
||||
def build(self) -> "Pipeline":
|
||||
"""Build execution order based on dependencies."""
|
||||
self._capability_map = self._build_capability_map()
|
||||
|
||||
Reference in New Issue
Block a user