3、分层状态机(HSM):状态嵌套、历史状态、进入/退出动作、HSM在复杂场景下的优势、代码实现一个带嵌套的决策逻辑
好,咱们今天聊一个在自动驾驶和机器人决策里绕不开的话题——分层状态机。
说实话,我刚入行那会儿,用的都是扁平状态机。一个路口左转,我得画十几个状态:直行、减速、观察、打灯、转弯、回正……状态一多,状态转移图就乱成一锅粥。后来我师父扔给我一句话:「你试试分层。」
嗯,这一试,就回不去了。
3.1 什么是分层状态机?
分层状态机,英文叫 Hierarchical State Machine,简称 HSM。说白了,就是把状态「套娃」——一个大的状态里面,可以包含若干个子状态。
举个例子:
- 「行驶」是一个顶层状态
- 「行驶」下面可以拆成 「直行」、「左转」、「右转」、「变道」
- 「左转」下面又可以拆成 「减速」、「观察」、「执行转弯」、「回正」
你想想看,如果把这些全铺平了写,状态数量会爆炸。但用分层,每个层级只关心自己这一层的逻辑,清晰多了。
核心思想:父状态负责「公共行为」,子状态负责「细节行为」。子状态可以继承父状态的进入/退出动作和转移逻辑。
3.2 状态嵌套:父与子的关系
状态嵌套,是 HSM 的基石。我习惯把父状态叫做「容器」,子状态叫做「内容」。
关键规则就三条:
- 进入父状态时,自动进入其默认子状态(比如「行驶」默认进入「直行」)
- 子状态活跃时,父状态也算活跃
- 子状态处理不了的事件,向上抛给父状态处理
我在项目中遇到过一个问题:车辆在「左转」子状态里,突然收到一个「紧急停车」指令。如果只在子状态里处理,那每个子状态都得写一遍紧急停车的逻辑。但用分层,我只需要在「行驶」这个父状态里写一次,所有子状态自动继承。省了多少代码?你自己算算。
3.3 历史状态:记住你从哪来
历史状态是个好东西。它解决了一个很实际的问题:
假设你的车正在「左转」的「观察」子状态里,突然来了个「让行行人」的指令,状态切到了「停车等待」。等行人走完了,你想回到之前的状态——是回到「左转」的「观察」,还是回到「左转」的初始子状态「减速」?
嗯,这里要注意。如果没有历史状态,你只能回到默认子状态。但有了历史状态,HSM 会记住你上次离开时的子状态,直接跳回去。
我的习惯:在「中断型」的状态切换里,比如避让、停车、等待,我都会开启历史状态记录。这样恢复时体验最自然。
3.4 进入/退出动作:生命周期管理
每个状态都可以有进入动作和退出动作。这玩意儿在扁平状态机里也有,但在 HSM 里,它的威力被放大了。
为什么?因为嵌套。
假设你从「直行」切换到「左转」:
- 「直行」退出 → 执行退出动作(比如关闭巡航指示灯)
- 「左转」进入 → 执行进入动作(比如打开左转灯)
但如果你从「直行」切换到「停车」呢?
- 「直行」退出 → 执行退出动作
- 「行驶」退出 → 执行退出动作(比如关闭自动驾驶模式指示灯)
- 「停车」进入 → 执行进入动作
看到了吗?退出动作是从内到外依次执行,进入动作是从外到内依次执行。这个顺序不能乱,乱了就会出 bug。
我曾经踩过的坑:在退出动作里释放了资源,结果进入动作还没执行完,导致资源被双重释放。后来我定了个规矩:退出动作只做「标记」,进入动作才做「实际分配」。顺序问题,一定要想清楚。
3.5 HSM 在复杂场景下的优势
咱们用表格对比一下,扁平状态机和分层状态机在几个维度上的表现:
| 维度 | 扁平状态机 | 分层状态机 |
|---|---|---|
| 状态数量 | 多,容易膨胀 | 少,层级复用 |
| 代码复用 | 差,重复逻辑多 | 好,父状态统一管理 |
| 可读性 | 状态图像蜘蛛网 | 层次清晰,像树 |
| 维护成本 | 高,改一个状态可能影响一片 | 低,局部修改 |
| 历史状态支持 | 需要手动实现 | 原生支持 |
我个人觉得,HSM 最大的优势不是「能实现复杂逻辑」,而是「让复杂逻辑变得可理解」。你想想看,一个自动驾驶系统可能有上百个状态,如果全是平铺的,新来的同事光看状态图就得看三天。但分层之后,顶层可能就五六个状态,每个点进去再看细节,学习成本直线下降。
3.6 代码实现:一个带嵌套的决策逻辑
光说不练假把式。咱们直接上代码。我用 Python 写一个简单的 HSM 框架,然后实现一个「自动驾驶过路口」的决策逻辑。
class State:
def __init__(self, name, parent=None):
self.name = name
self.parent = parent
self.children = {}
self.default_child = None
self.history = None
self.current_child = None
self.entry_action = None
self.exit_action = None
def set_entry(self, func):
self.entry_action = func
def set_exit(self, func):
self.exit_action = func
def add_child(self, child, is_default=False):
self.children[child.name] = child
if is_default:
self.default_child = child
def enter(self):
if self.entry_action:
self.entry_action()
if self.default_child:
# 如果有历史状态,优先恢复
target = self.history if self.history else self.default_child
self.current_child = target
target.enter()
def exit(self):
if self.current_child:
self.current_child.exit()
if self.exit_action:
self.exit_action()
def handle_event(self, event):
if self.current_child:
# 子状态优先处理
if self.current_child.handle_event(event):
return True
# 子状态处理不了,自己处理
return self.on_event(event)
def on_event(self, event):
# 由子类重写
return False
def transition_to(self, target_name):
if target_name in self.children:
if self.current_child:
self.current_child.exit()
self.current_child = self.children[target_name]
self.current_child.enter()
return True
return False
def save_history(self):
self.history = self.current_child
# 具体实现:自动驾驶过路口
class Driving(State):
def __init__(self):
super().__init__("行驶")
straight = State("直行", self)
turning = Turning("转弯", self)
self.add_child(straight, is_default=True)
self.add_child(turning)
def on_event(self, event):
if event == "前方路口":
self.save_history()
self.transition_to("转弯")
return True
return False
class Turning(State):
def __init__(self, parent):
super().__init__("转弯", parent)
slow = State("减速", self)
observe = State("观察", self)
execute = State("执行转弯", self)
resume = State("回正", self)
self.add_child(slow, is_default=True)
self.add_child(observe)
self.add_child(execute)
self.add_child(resume)
slow.set_entry(lambda: print("进入减速状态"))
slow.set_exit(lambda: print("退出减速状态"))
observe.set_entry(lambda: print("进入观察状态"))
execute.set_entry(lambda: print("开始转弯"))
resume.set_entry(lambda: print("回正方向"))
def on_event(self, event):
if event == "行人出现":
self.save_history()
# 这里可以切换到"停车等待"状态
print("检测到行人,准备停车")
return True
return False
# 使用示例
if __name__ == "__main__":
driving = Driving()
driving.enter() # 进入行驶 -> 默认进入直行
print("\n--- 收到路口事件 ---")
driving.handle_event("前方路口") # 切换到转弯 -> 默认进入减速
print("\n--- 收到行人事件 ---")
driving.handle_event("行人出现") # 转弯状态处理
print("\n--- 行人离开,恢复 ---")
# 恢复逻辑:回到转弯的观察状态(假设之前保存了历史)
driving.current_child.history = driving.current_child.current_child
# 实际项目中会通过事件触发恢复
代码说明:这个例子虽然简单,但包含了 HSM 的核心要素——状态嵌套、进入/退出动作、历史状态保存。实际项目中,你还需要考虑事件队列、异步处理、状态持久化等。但骨架就是这个。
3.7 小结
分层状态机不是什么新概念,但它在自动驾驶和机器人领域的价值,怎么强调都不过分。
我见过太多团队,一开始用扁平状态机,写到后面自己都看不懂了。然后重构,换成 HSM,世界清净了。
记住三句话:
- 父状态管公共,子状态管细节
- 进入从外到内,退出从内到外
- 历史状态是你的后悔药,该用就用
下一章,咱们聊聊 HSM 在实际工程中的落地——状态机怎么和感知、规划模块对接,以及一些常见的性能优化技巧。