294 lines
13 KiB
Python
294 lines
13 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""
|
||
Task Action 配置计划
|
||
─────────────────────────────────────────────────────────────────
|
||
描述 Task 中 Action 的挂载方式与执行策略:
|
||
|
||
挂载层级:
|
||
RouteActionPlan —— 整条航线级别的 Action 配置
|
||
SegmentActionPlan —— 单个航段(RouteSegment)级别的 Action 配置
|
||
|
||
执行策略:
|
||
PARALLEL —— 同组 Action 并行执行(无顺序约束)
|
||
SEQUENTIAL—— 同组 Action 按 order 字段串行执行
|
||
|
||
触发时机:
|
||
ON_ENTER —— 进入航线/航段时触发
|
||
ON_EXIT —— 离开航线/航段时触发
|
||
ON_WAYPOINT—— 到达指定航路点时触发
|
||
CONTINUOUS —— 在航线/航段内持续触发(按 interval_s 间隔)
|
||
SCHEDULED —— 在指定偏移时刻触发
|
||
─────────────────────────────────────────────────────────────────
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import uuid
|
||
from dataclasses import dataclass, field
|
||
from enum import Enum
|
||
from typing import Dict, List, Optional
|
||
|
||
|
||
# ─────────────────────────────────────────────────────────────────
|
||
# 执行策略
|
||
# ─────────────────────────────────────────────────────────────────
|
||
|
||
class ExecutionPolicy(Enum):
|
||
PARALLEL = "parallel" # 并行执行(无顺序约束的 Action 组)
|
||
SEQUENTIAL = "sequential" # 串行执行(按 order 字段顺序)
|
||
|
||
|
||
# ─────────────────────────────────────────────────────────────────
|
||
# 触发时机
|
||
# ─────────────────────────────────────────────────────────────────
|
||
|
||
class TriggerMoment(Enum):
|
||
ON_ENTER = "on_enter" # 进入航线/航段时触发(一次)
|
||
ON_EXIT = "on_exit" # 离开航线/航段时触发(一次)
|
||
ON_WAYPOINT = "on_waypoint" # 到达指定航路点时触发
|
||
CONTINUOUS = "continuous" # 持续触发(按 interval_s)
|
||
SCHEDULED = "scheduled" # 在指定时间偏移处触发
|
||
|
||
|
||
# ─────────────────────────────────────────────────────────────────
|
||
# Action 挂载项
|
||
# ─────────────────────────────────────────────────────────────────
|
||
|
||
@dataclass
|
||
class ActionEntry:
|
||
"""
|
||
单个 Action 挂载项
|
||
封装 Action 对象 + 执行顺序 + 触发时机 + 调度参数
|
||
"""
|
||
action: object # Action 实例(来自 action_module)
|
||
order: Optional[int] = None # 串行顺序编号(None=不参与串行,归入并行组)
|
||
trigger: TriggerMoment = TriggerMoment.ON_ENTER
|
||
interval_s: float = 10.0 # CONTINUOUS 模式触发间隔(秒)
|
||
time_offset_s: float = 0.0 # SCHEDULED 模式时间偏移(秒,相对于段起始)
|
||
waypoint_id: Optional[str] = None # ON_WAYPOINT 模式目标航路点标识符
|
||
enabled: bool = True # 是否启用
|
||
entry_id: str = field(default_factory=lambda: str(uuid.uuid4())[:8])
|
||
description: str = ""
|
||
|
||
@property
|
||
def is_sequential(self) -> bool:
|
||
"""是否参与串行执行"""
|
||
return self.order is not None
|
||
|
||
@property
|
||
def is_parallel(self) -> bool:
|
||
"""是否归入并行执行组"""
|
||
return self.order is None
|
||
|
||
def __repr__(self):
|
||
policy = f"seq[{self.order}]" if self.is_sequential else "parallel"
|
||
return (f"ActionEntry(id={self.entry_id}, "
|
||
f"action={self.action.type_key}, "
|
||
f"policy={policy}, trigger={self.trigger.value})")
|
||
|
||
|
||
# ─────────────────────────────────────────────────────────────────
|
||
# 航段 Action 配置计划
|
||
# ─────────────────────────────────────────────────────────────────
|
||
|
||
@dataclass
|
||
class SegmentActionPlan:
|
||
"""
|
||
单个航段(RouteSegment)的 Action 配置计划
|
||
|
||
segment_index: 对应 FlightRoute.segments 的下标
|
||
entries: 该航段挂载的所有 ActionEntry
|
||
|
||
执行规则:
|
||
- entries 中 order=None 的 ActionEntry → 并行执行组
|
||
- entries 中 order!=None 的 ActionEntry → 按 order 升序串行执行
|
||
- 串行组与并行组的相对执行顺序由 serial_first 控制
|
||
"""
|
||
segment_index: int
|
||
entries: List[ActionEntry] = field(default_factory=list)
|
||
serial_first: bool = True # True=先串行后并行,False=先并行后串行
|
||
description: str = ""
|
||
|
||
# ── 添加 Action ───────────────────────────────────────────────
|
||
|
||
def add(
|
||
self,
|
||
action,
|
||
order: Optional[int] = None,
|
||
trigger: TriggerMoment = TriggerMoment.ON_ENTER,
|
||
interval_s: float = 10.0,
|
||
time_offset_s: float = 0.0,
|
||
waypoint_id: Optional[str] = None,
|
||
description: str = "",
|
||
) -> "SegmentActionPlan":
|
||
"""
|
||
添加 Action 到本航段计划
|
||
:param order: None=并行,整数=串行顺序(从0或1开始均可)
|
||
:return: self(支持链式调用)
|
||
"""
|
||
entry = ActionEntry(
|
||
action = action,
|
||
order = order,
|
||
trigger = trigger,
|
||
interval_s = interval_s,
|
||
time_offset_s = time_offset_s,
|
||
waypoint_id = waypoint_id,
|
||
description = description,
|
||
)
|
||
self.entries.append(entry)
|
||
return self
|
||
|
||
# ── 分组查询 ──────────────────────────────────────────────────
|
||
|
||
@property
|
||
def parallel_entries(self) -> List[ActionEntry]:
|
||
"""获取并行执行组(order=None)"""
|
||
return [e for e in self.entries if e.is_parallel and e.enabled]
|
||
|
||
@property
|
||
def sequential_entries(self) -> List[ActionEntry]:
|
||
"""获取串行执行组(order!=None),按 order 升序排列"""
|
||
return sorted(
|
||
[e for e in self.entries if e.is_sequential and e.enabled],
|
||
key=lambda e: e.order
|
||
)
|
||
|
||
def entries_by_trigger(self, trigger: TriggerMoment) -> List[ActionEntry]:
|
||
"""按触发时机过滤"""
|
||
return [e for e in self.entries if e.trigger == trigger and e.enabled]
|
||
|
||
def __repr__(self):
|
||
return (f"SegmentActionPlan(seg={self.segment_index}, "
|
||
f"entries={len(self.entries)}, "
|
||
f"parallel={len(self.parallel_entries)}, "
|
||
f"sequential={len(self.sequential_entries)})")
|
||
|
||
|
||
# ─────────────────────────────────────────────────────────────────
|
||
# 航线 Action 配置计划
|
||
# ─────────────────────────────────────────────────────────────────
|
||
|
||
@dataclass
|
||
class RouteActionPlan:
|
||
"""
|
||
整条航线的 Action 配置计划
|
||
|
||
包含:
|
||
route_entries —— 航线级别的 ActionEntry(整条航线触发)
|
||
segment_plans —— 各航段的 SegmentActionPlan(按段索引索引)
|
||
|
||
执行规则与 SegmentActionPlan 相同:
|
||
- order=None → 并行组
|
||
- order!=None → 串行组(按 order 升序)
|
||
"""
|
||
route_entries: List[ActionEntry] = field(default_factory=list)
|
||
segment_plans: Dict[int, SegmentActionPlan] = field(default_factory=dict)
|
||
serial_first: bool = True
|
||
description: str = ""
|
||
|
||
# ── 航线级 Action ─────────────────────────────────────────────
|
||
|
||
def add_route_action(
|
||
self,
|
||
action,
|
||
order: Optional[int] = None,
|
||
trigger: TriggerMoment = TriggerMoment.ON_ENTER,
|
||
interval_s: float = 10.0,
|
||
time_offset_s: float = 0.0,
|
||
waypoint_id: Optional[str] = None,
|
||
description: str = "",
|
||
) -> "RouteActionPlan":
|
||
"""
|
||
添加航线级 Action(整条航线范围内触发)
|
||
:param order: None=并行,整数=串行顺序
|
||
:return: self(链式调用)
|
||
"""
|
||
entry = ActionEntry(
|
||
action = action,
|
||
order = order,
|
||
trigger = trigger,
|
||
interval_s = interval_s,
|
||
time_offset_s = time_offset_s,
|
||
waypoint_id = waypoint_id,
|
||
description = description,
|
||
)
|
||
self.route_entries.append(entry)
|
||
return self
|
||
|
||
# ── 航段级 Action ─────────────────────────────────────────────
|
||
|
||
def get_segment_plan(self, segment_index: int) -> SegmentActionPlan:
|
||
"""获取指定航段的计划(不存在则自动创建)"""
|
||
if segment_index not in self.segment_plans:
|
||
self.segment_plans[segment_index] = SegmentActionPlan(
|
||
segment_index=segment_index
|
||
)
|
||
return self.segment_plans[segment_index]
|
||
|
||
def add_segment_action(
|
||
self,
|
||
segment_index: int,
|
||
action,
|
||
order: Optional[int] = None,
|
||
trigger: TriggerMoment = TriggerMoment.ON_ENTER,
|
||
interval_s: float = 10.0,
|
||
time_offset_s: float = 0.0,
|
||
waypoint_id: Optional[str] = None,
|
||
description: str = "",
|
||
) -> "RouteActionPlan":
|
||
"""
|
||
为指定航段添加 Action
|
||
:param segment_index: FlightRoute.segments 的下标
|
||
:param order: None=并行,整数=串行顺序
|
||
:return: self(链式调用)
|
||
"""
|
||
plan = self.get_segment_plan(segment_index)
|
||
plan.add(
|
||
action = action,
|
||
order = order,
|
||
trigger = trigger,
|
||
interval_s = interval_s,
|
||
time_offset_s = time_offset_s,
|
||
waypoint_id = waypoint_id,
|
||
description = description,
|
||
)
|
||
return self
|
||
|
||
# ── 分组查询(航线级)────────────────────────────────────────
|
||
|
||
@property
|
||
def parallel_route_entries(self) -> List[ActionEntry]:
|
||
return [e for e in self.route_entries if e.is_parallel and e.enabled]
|
||
|
||
@property
|
||
def sequential_route_entries(self) -> List[ActionEntry]:
|
||
return sorted(
|
||
[e for e in self.route_entries if e.is_sequential and e.enabled],
|
||
key=lambda e: e.order
|
||
)
|
||
|
||
def route_entries_by_trigger(self, trigger: TriggerMoment) -> List[ActionEntry]:
|
||
return [e for e in self.route_entries
|
||
if e.trigger == trigger and e.enabled]
|
||
|
||
# ── 汇总信息 ──────────────────────────────────────────────────
|
||
|
||
def summary(self) -> str:
|
||
lines = [
|
||
f"RouteActionPlan:",
|
||
f" 航线级 Actions : {len(self.route_entries)} "
|
||
f"(并行={len(self.parallel_route_entries)}, "
|
||
f"串行={len(self.sequential_route_entries)})",
|
||
f" 已配置航段数 : {len(self.segment_plans)}",
|
||
]
|
||
for idx, plan in sorted(self.segment_plans.items()):
|
||
lines.append(
|
||
f" Seg[{idx}]: {len(plan.entries)} actions "
|
||
f"(并行={len(plan.parallel_entries)}, "
|
||
f"串行={len(plan.sequential_entries)})"
|
||
)
|
||
return "\n".join(lines)
|
||
|
||
def __repr__(self):
|
||
return (f"RouteActionPlan(route_actions={len(self.route_entries)}, "
|
||
f"segment_plans={len(self.segment_plans)})") |