【问题标题】:Implementing Hierarchical State Machines in C在 C 中实现分层状态机
【发布时间】:2014-12-29 19:19:35
【问题描述】:

我对如何实现我的状态机有点困惑。
我已经知道它是分层的,因为某些州共享相同的操作。
我通过这些参数确定我需要做什么:

  • 类(值为:Base、Derived、Specific)
  • 操作码
  • 参数 1 - 可选
  • 参数 2 - 可选

我的层次结构是由Class决定的,OpCode代表动作。
Derived可以使用的OpCodes Base 和 Specific 可以使用 Base 和 Derived 的 OpCodes。
天真的实现如下:

void (*const state_table [MAX_CLASSES][MAX_OPCODES]) (state *) {
  {base_state1, base_state2, NULL, NULL},
  {base_state1, base_state2, derived_state1, NULL},
  {base_state1,base_state2, derived_state1, specific_state3},
};

void dispatch(state *s)
{
  if (state_table[s->Class][s->OpCode] != NULL)
    state_table[s->Class][s->OpCode](s);
}

这将很快变得无法维护。
还有其他方法可以将状态映射到超类吗?

编辑:
进一步的计算使我认为我可能会使用大部分(如果不是全部)OpCodes,但我不会使用所有可用的Classes。
另一个澄清:
一些 OpCodes 可能通过多个派生和基 类 共享。
例如:

  • 我有一个名为 Any 的 Class 这是一个 Base 类。它有 操作码:STATE_ON、STATE_OFF、STATE_SET。
  • 我有另一个 Class 叫 MyGroup 是一个 Derived 类。它有 OpCodes: STATE_FLIP、STATE_FLOP。

  • 第三个类是一个具体 名为 ThingInMyGroup 的类 具有操作码: STATE_FLIP_FLOP_AND_FLOOP。

因此,从服务器发送具有 Any 类的消息,在所有客户端中接收并处理。

类 MyGroup 的消息从服务器发送,在所有客户端中接收并仅在属于 MyGroup 的客户端上处理,任何 OpCodes 对 Any 类有效的 > 对 MyGroup 类有效。

类 ThingInMyGroup 的消息从服务器发送,在所有客户端中接收并仅在属于 MyGroup 且属于 ThingInMyGroup* 的客户端上处理,对 Any 类和 MyGroup 类有效的任何 **OpCodes 对 ThingInMyGroup 类有效。

在收到消息后,客户端将相应地 ACK/NACK。

我不喜欢使用 switch case 或 const 数组,因为当它们变大时,它们将变得无法维护。
我需要一个灵活的设计,让我:

  1. 指定哪些操作码可用 每个类。
  2. 为每个 Class 指定一个超类,并通过该规范允许我调用由当前 OpCode 表示的函数指针。

【问题讨论】:

    标签: c state-machine hsm


    【解决方案1】:

    有几种方法可以解决这个问题。这是一个:

    编辑 -- 添加通用层次结构

    typedef unsigned op_code_type;
    typedef void (*dispatch_type)(op_code_type);
    typedef struct hierarchy_stack hierarchy_stack;
    struct hierarchy_stack {
           dispatch_type func;
           hierarchy_stack *tail;
    };
    
    void dispatch(state *s, hierarchy_stack *stk) {
        if (!stk) {
              printf("this shouldn't have happened");
        } else {
              stk->func(s, stk->tail);
        }
    }
    
    void Base(state *s, hierarchy_stack *stk ) {
        switch (s->OpCode) {
              case bstate1:
                   base_state1(s);
                   break;
              case bstate2:
                   base_state(2);
                   break;
              default:
                   dispatch(s, stk);
        }
    }
    void Derived(state *s, hierarchy_stack *stk ) {
        switch(s->opcode) {
               case dstate1:
                    deriveds_state1(s);
                    break;
               default:
                    dispatch(s, stk);
        }
    }
    ... 
    

    注意:所有函数调用都是尾调用。

    这可以很好地本地化您的“类”,因此如果您决定 Derived 需要 100 多个方法/操作码,那么您只需编辑用于定义操作码的方法和枚举(或其他)。

    另一种更动态的处理方式是在每个“类”中都有一个父指针,该指针指向可以处理它无法处理的任何事情的“类”。

    2D 表方法既快速又灵活(Derived 可能具有与操作码 0 的 Base 不同的处理程序),但它的增长速度很快。

    【讨论】:

    • 您的第一种方法根本不是分层的。它只是一个普通的状态机。
    • 它符合给定的示例(当我编写它时),但是如果您将 default 案例上的直接调用替换为指针调用,您可以轻松地使其分层。您只需要跟上父状态,或者层次结构可以作为附加参数(列表)传递,默认情况将其传递给调度程序,调度程序删除层次结构的头部,使用它来调用另一个处理程序,并传递状态事件和层次结构的尾部。
    • 我的问题是开关盒。它会变得巨大。我也没有得到hierarchy_stack的目的。看到这个:accu-usa.org/Slides/samek0311.pdf 它是使用 for 循环实现的,问题是我真的不明白。另一个问题是我需要一些不太灵活的东西。我只需要在状态之间共享操作并允许返回回复的操作
    【解决方案2】:

    我编写了一个小工具,可以生成类似于您基于迷你语言的幼稚实现的代码。该语言只是指定了状态-操作码-动作的关系,所有的动作都是符合 typedef 的 C 函数。

    它不处理 HSM 方面,但这相对容易添加到语言中。

    我建议采用这种方法 - 创建一种小语言,为您提供一种清晰的方式来描述状态机,然后根据该机器描述生成代码。这样,当您需要在一个月后插入一个新状态时,整个事情就不会乱七八糟地编辑了。

    如果您需要该代码,请告诉我,我会确保它在某处仍然可用。

    【讨论】:

    • 问题是这个状态表并不能真正满足我的需要,因为实际上我必须将 Class 和 OpCode 转换为函数的索引,因为两者都不是从 0 开始,因此我仍然需要使用 switch case 或一些计算来映射它以访问 state_table。我找到了此链接accu-usa.org/Slides/samek0311.pdf,但其中的代码不适合我的需要。我实际上需要一些更简单的东西。有什么想法吗?
    • 您的状态表很大吗?有多少操作码、类、状态?
    • sizeof(OpCode) = 8 sizeof(Class) = 16 最多有 16711425 个可能的状态。由于存在重叠,因此可能会更少。基本上 OpCode 和 Class 是状态,有时它们有参数,有时它们没有。
    猜你喜欢
    • 1970-01-01
    • 2017-12-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-09-24
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多