3、分层状态机(HSM):状态嵌套、历史状态、进入/退出动作、HSM在复杂场景下的优势、代码实现一个带嵌套的决策逻辑

好,咱们今天聊一个在自动驾驶和机器人决策里绕不开的话题——分层状态机。

说实话,我刚入行那会儿,用的都是扁平状态机。一个路口左转,我得画十几个状态:直行、减速、观察、打灯、转弯、回正……状态一多,状态转移图就乱成一锅粥。后来我师父扔给我一句话:「你试试分层。」

嗯,这一试,就回不去了。

3.1 什么是分层状态机?

分层状态机,英文叫 Hierarchical State Machine,简称 HSM。说白了,就是把状态「套娃」——一个大的状态里面,可以包含若干个子状态。

举个例子:

  • 「行驶」是一个顶层状态
  • 「行驶」下面可以拆成 「直行」、「左转」、「右转」、「变道」
  • 「左转」下面又可以拆成 「减速」、「观察」、「执行转弯」、「回正」

你想想看,如果把这些全铺平了写,状态数量会爆炸。但用分层,每个层级只关心自己这一层的逻辑,清晰多了。

核心思想:父状态负责「公共行为」,子状态负责「细节行为」。子状态可以继承父状态的进入/退出动作和转移逻辑。

3.2 状态嵌套:父与子的关系

状态嵌套,是 HSM 的基石。我习惯把父状态叫做「容器」,子状态叫做「内容」。

关键规则就三条:

  1. 进入父状态时,自动进入其默认子状态(比如「行驶」默认进入「直行」)
  2. 子状态活跃时,父状态也算活跃
  3. 子状态处理不了的事件,向上抛给父状态处理

我在项目中遇到过一个问题:车辆在「左转」子状态里,突然收到一个「紧急停车」指令。如果只在子状态里处理,那每个子状态都得写一遍紧急停车的逻辑。但用分层,我只需要在「行驶」这个父状态里写一次,所有子状态自动继承。省了多少代码?你自己算算。

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 在实际工程中的落地——状态机怎么和感知、规划模块对接,以及一些常见的性能优化技巧。