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