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)})")
|