C конструкція стан-машини [закрито]


193

Я розробляю невеликий проект із змішаного С та С ++. Я будую одну маленьку державну машину в основі однієї з моїх робочих ниток.

Мені було цікаво, якщо ви гуру на SO поділиться своїми методами проектування штату-машини.

ПРИМІТКА. Я передусім після перевірених і перевірених методів реалізації.

ОНОВЛЕНО: Спираючись на всі великі вклади, зібрані на SO, я зупинився на цій архітектурі:

Насос події вказує на інтегратор події, який вказує на диспетчера.  Диспетчер вказує на 1 через n дій, які вказують на інтегратор події.  Таблиця переходу із символами підстановки вказує на диспетчера.


4
Відповіді тут дуже хороші. Також подивіться на це повторне запитання, на яке є кілька хороших відповідей: stackoverflow.com/questions/1371460/state-machines-tutorials
Майкл Берр



Дивіться також це питання .
Даніель Даранас

Відповіді:


170

Державні машини, які я розробляв раніше (C, а не C ++), звелись до рівня a struct масиву та циклу. Структура в основному складається з стану та події (для пошуку) та функції, яка повертає новий стан, щось на зразок:

typedef struct {
    int st;
    int ev;
    int (*fn)(void);
} tTransition;

Тоді ви визначаєте ваші стани та події простими параметрами ( ANYвони є спеціальними маркерами, див. Нижче):

#define ST_ANY              -1
#define ST_INIT              0
#define ST_ERROR             1
#define ST_TERM              2
: :
#define EV_ANY              -1
#define EV_KEYPRESS       5000
#define EV_MOUSEMOVE      5001

Потім ви визначаєте всі функції, які викликаються переходами:

static int GotKey (void) { ... };
static int FsmError (void) { ... };

Усі ці функції написані, щоб не брати змінних і повернути новий стан для машини машини. У цьому прикладі глобальні змінні використовуються для передачі будь-якої інформації в функції стану, де це необхідно.

Використання глобальних мереж не так вже й погано, як це звучить, оскільки FSM зазвичай замикається всередині однієї компіляційної одиниці, і всі змінні є статичними для цього блоку (саме тому я використовував лапки навколо "глобальних" вище - вони більше поділяються в межах FSM, ніж справді глобальний). Як і у всіх глобальних, він вимагає обережності.

Потім масив переходів визначає всі можливі переходи та функції, які викликаються для цих переходів (включаючи останній останній):

tTransition trans[] = {
    { ST_INIT, EV_KEYPRESS, &GotKey},
    : :
    { ST_ANY, EV_ANY, &FsmError}
};
#define TRANS_COUNT (sizeof(trans)/sizeof(*trans))

Що це означає: якщо ви перебуваєте в ST_INITштаті і отримуєте EV_KEYPRESSподію, зателефонуйте GotKey.

Потім FSM стає відносно простим циклом:

state = ST_INIT;
while (state != ST_TERM) {
    event = GetNextEvent();
    for (i = 0; i < TRANS_COUNT; i++) {
        if ((state == trans[i].st) || (ST_ANY == trans[i].st)) {
            if ((event == trans[i].ev) || (EV_ANY == trans[i].ev)) {
                state = (trans[i].fn)();
                break;
            }
        }
    }
}

Як уже згадувалося вище, зверніть увагу на використання ST_ANYдиких карт, що дозволяють події викликати функцію незалежно від поточного стану. EV_ANYтакож працює аналогічно, дозволяючи будь-якій події в певному стані викликати функцію.

Це також може гарантувати, що якщо ви дістанетеся до кінця масиву переходів, ви отримаєте помилку про те, що ваш FSM не був побудований правильно (за допомогою ST_ANY/EV_ANYкомбінації.

Я використовував подібний код для цього у багатьох комунікаційних проектах, таких як рання реалізація стеків зв'язку та протоколів для вбудованих систем. Великою перевагою була його простота та відносна легкість у зміні масиву переходів.

Я не сумніваюся, що будуть абстракції вищого рівня, які сьогодні можуть бути більш підходящими, але я підозрюю, що всі вони зберуться до такої ж структури.


І, як ldogзазначається в коментарі, ви можете уникнути глобалів взагалі, передавши вказівник структури на всі функції (і використовуючи це в циклі подій). Це дозволить безлічі машин працювати без перешкод.

Просто створіть тип структури, який містить дані, що відповідають певній машині (стан є мінімальним), і використовуйте це замість глобальних.

Причиною, яку я рідко робив, є просто те, що більшість машин, про які я писав, були однотипними (наприклад, одноразовий, при запуску, зчитування файлів конфігурації), не потрібно запускати більше ніж один екземпляр . Але воно має значення, якщо вам потрібно запустити більше одного.


24
Гігантський перемикач змішує код з FSM. Навіть якщо є лише виклик функції за перехід, все-таки є якийсь код, і хтось легко зловживає цим, просто додаючи невеликий 4-рядковий перехід в рядок. курка десятирядкова. Тоді це виходить з рук. За допомогою структурного масиву FSM залишається чистим - ви можете побачити кожен перехід та ефект (функцію). І я почав, коли енюмінг в очах ISO заблищав, писав код для вбудованих 6809 платформ із компіляторами, які були, скажемо так, менш ідеальними :-)
paxdiablo

5
Ви маєте рацію, перерахунки були б кращими, але я все ж вважаю за краще, щоб FSM був як структурний масив. Тоді все працює за даними, а не кодом (ну, є якийсь код, але шанс заповнити цю петлю FSM невеликий).
paxdiablo

2
Це добре, що для машин, що керують процесами, я завжди додавав три (можливо порожні) підстанції для кожного стану, щоб виклик функції стану став GotKey (субстата), де субдеталом буде: - SS_ENTRY - SS_RUN - SS_EXIT В основному, функція стану викликається з підстанцією SS_ENTRY під час входу, щоб стан міг реконструювати стан (наприклад, положення приводів). Поки немає переходу, значення підстановки SS_RUN передається. Після переходу функція стану викликається з піддержавою SS_EXIT, щоб вона могла робити будь-які очищення (наприклад, ресурси, що займаються місцем розташування).
Метью

13
Ви згадали, що ви обмінюєтесь даними за допомогою глобальних даних, але це, ймовірно, буде чистіше, якщо ви визначите функції стану, int (*fn)(void*);де void*є вказівник на дані, які кожна функція стану приймає як параметр. Тоді функції стану можуть або використовувати дані, або ігнорувати їх.
ldog

13
Я використовую ті самі розділення даних / коду для написання FSM, за винятком того, що мені ніколи не прийшло в голову вводити стан «wildcard». Цікава ідея! Однак ітерація масиву переходів може стати дорогим, якщо у вас є багато станів (це було для мене, оскільки код C автоматично генерувався). У таких ситуаціях було б ефективніше мати один масив переходів на стан. Отже стан - це вже не значення перерахунку, а таблиця переходу. Таким чином, вам не доведеться повторювати всі переходи в машині, а лише ті, які відповідають поточному стану.
Фріріх Раабе

78

Інші відповіді хороші, але дуже легка реалізація, яку я використовував, коли державна машина дуже проста, виглядає так:

enum state { ST_NEW, ST_OPEN, ST_SHIFT, ST_END };

enum state current_state = ST_NEW;

while (current_state != ST_END)
{
    input = get_input();

    switch (current_state)
    {
        case ST_NEW:
        /* Do something with input and set current_state */
        break;

        case ST_OPEN:
        /* Do something different and set current_state */
        break;

        /* ... etc ... */
    }
}

Я використовував би це, коли стан машини є досить простим, що підхід до функції вказівника та таблиці переходу стану є надмірним. Це часто корисно для розбору символів за символом або розбору слів за словом.


37

Вибачте мене за порушення кожного правила інформатики, але державна машина - одне з небагатьох (я можу порахувати лише два від руки) місця, де gotoзаява не тільки більш ефективна, але також робить ваш код більш чистим і легшим для читання. Оскільки gotoвисловлювання засновані на мітках, ви можете називати свої штати замість того, щоб відслідковувати безлад чисел або використовувати перерахунок. Це також робить набагато більш чистим кодом, оскільки вам не потрібна вся додаткова сукупність покажчиків функцій або величезних операторів комутації та циклів. Я вже згадував, що це також більш ефективно?

Ось як може виглядати державна машина:

void state_machine() {
first_state:
    // Do some stuff here
    switch(some_var) {
    case 0:
        goto first_state;
    case 1:
        goto second_state;
    default:
        return;
    }

second_state:
    // Do some stuff here
    switch(some_var) {
    case 0:
        goto first_state;
    case 1:
        goto second_state;
    default:
        return;
    }
}

Ви отримуєте загальну думку. Справа в тому, що ви можете реалізувати державну машину ефективно, та таку, яку порівняно легко читати, і кричить читачеві, що вони дивляться на державну машину. Зауважте, що якщо ви використовуєте gotoвисловлювання, ви все одно повинні бути обережні, оскільки дуже легко стріляти в ногу, роблячи це.


4
Це працює лише в тому випадку, якщо машина стану знаходиться в об'єкті верхнього рівня. У той момент, коли якийсь інший об’єкт, на який час від часу опитуються / надсилаються повідомлення, повинен мати стан, ви застрягли в такому підході (що, або вам доведеться зробити це набагато складніше)
skrebbel

1
Це дійсно змушує вас використовувати попереджувальну багатозадачність у всіх, крім найпростіших випадках.
Крейг МакКуїн

1
Ці готи можуть бути замінені викликами функцій. І якщо профілер скаже вам, що ваша програма потопає через накладні виклики функцій, то ви можете замінити дзвінки на gotos, якщо потрібно.
Абтін Форузанде

7
@AbtinForouzandeh проста заміна gotos функціональними викликами призведе до потокового потоку, оскільки стек виклику очищається лише у випадку помилки.
JustMaximumPower

Я згоден з методом goto. Ось набір макросів, які ілюструють це. А макроси роблять ваш код структурованим так, ніби ви його закодували так, як зазвичай. Він також працює на рівні переривання, зазвичай там, де потрібні державні машини codeproject.com/Articles/37037/…
eddyq

30

Ви можете розглянути питання про компілятор державних машин http://smc.sourceforge.net/

Ця чудова утиліта з відкритим кодом приймає опис державної машини простою мовою та компілює її на будь-якій з десятків і більше мов, включаючи C і C ++. Сама утиліта написана на Java і може бути включена як частина збірки.

Причина цього, а не кодування вручну за допомогою шаблону стану GoF або будь-якого іншого підходу, полягає в тому, що після того, як ваша державна машина виражається як код, базова структура, як правило, зникає під вагою котлової панелі, яку необхідно створити для її підтримки. Використовуючи такий підхід, ви чудово розділяєте проблеми, і ви зберігаєте структуру свого державного апарату «видимою». Автоматично створений код переходить у модулі, до яких не потрібно торкатися, так що ви можете повернутися назад і поспілкуватися зі структурою станкової машини, не впливаючи на написаний підтримуючим кодом.

Вибачте, я надто захоплений і, безсумнівно, всіх відпускаю. Але це найвища утиліта, і також добре задокументована.


20

Не забудьте перевірити роботу Миро Самека (блог State Space , веб-сайт Державні машини та інструменти ), статті якого в журналі C / C ++ користувачів були чудовими.

Веб-сайт містить повну (C / C ++) реалізацію як у відкритому коді, так і в комерційній ліцензії рамки державної машини (QP Framework) , обробника подій (QEP) , основного інструменту моделювання (QM) та інструменту відстеження (QSpy), який дозволяють малювати стан машини, створювати код і налагоджувати їх.

Книга містить велике пояснення того, що / навіщо реалізувати та як користуватися нею, а також є чудовим матеріалом для розуміння основ ієрархічних та кінцевих державних машин.

Веб-сайт також містить посилання на декілька пакетів підтримки для використання програмного забезпечення з вбудованими платформами.


Я змінив назву питання відповідно до вашого каламбуру.
jldupont

@jldupont: Я просто мав на увазі, що краще уточнити. Зараз я видалив невідповідні частини своєї відповіді.
Даніель Даранас

1
Я додав, що очікувати на веб-сайті / книзі, успішно використовуючи програмне забезпечення; це найкраща книга на моїй книжковій полиці.
Адріан

@Adriann, чудове пояснення! Я щойно змінив дім веб-сайту, попереднє посилання перестало працювати.
Даніель Даранас

2
Посилання або мертві, або вказують на головну сторінку сайту, яка, здається, змінила напрямок на вбудоване програмне забезпечення. Ви все ще можете побачити деякий вміст на state-machine.com/resources/articles.php , але навіть там більшість посилань, пов’язаних із машиною, є мертвими. Це одне з єдиних хороших посилань там: state-machine.com/resources/…
Тетяна Рачева,

11

Я зробив щось подібне до того, що описує paxdiablo, лише замість масиву переходів стану / подій я встановив двовимірний масив покажчиків функцій, значенням події як індекс однієї осі, а значення поточного стану як інші. Тоді я просто дзвоню state = state_table[event][state](params)і відбувається правильне. Клітини, що представляють недійсні комбінації стану / подій, звичайно, вказують на функцію, яка так говорить.

Очевидно, це працює лише в тому випадку, якщо значення стану та події є одночасно суміжними діапазонами і починаються від 0 або досить близько.


1
Схоже, це рішення не гарно масштабується: занадто багато заповнення столів, ні?
jldupont

2
+1. Проблема масштабування тут - це пам'ять - у моєму власному рішенні є проблема часу з масштабуванням, тобто час, необхідний для сканування таблиці переходів (хоча ви можете вручну оптимізувати найбільш поширені переходи). Цей жертвує пам’яттю на швидкість - це просто компроміс. Можливо, вам знадобляться чеки на межі, але це не погане рішення.
paxdiablo

Хлопці - Мій коментар не вийшов за призначенням: я мав на увазі, що він набагато більш трудомісткий і схильний до помилок. Якщо ви додасте стан / подію, потрібно зробити багато редагування.
jldupont

3
Ніхто не сказав, що 2D масив був ініціалізований вручну. Можливо, є щось, що читає конфігураційний файл і створює його (або принаймні там, безумовно, може бути).
Джон Цвінк

Один із способів ініціалізації таких масивів - це використання препроцесора, який є пізнім зв'язувачем (на відміну від раннього зв’язування). Ви визначаєте перелік усіх станів #define STATE_LIST() \STATE_LIST_ENTRY(state1)\STATE_LIST_ENTRY(state2)\...(мається на увазі новий рядок після кожного \ ), де ви (повторно) визначаєте макрос введення під час використання макросу STATE_LIST. Приклад - створення масиву назв станів: #define STATE_LIST_ENTRY(s) #s , \n const char *state_names[] = { STATE_LIST() };\n #undef STATE_LIST_ENTRY. Деякі працюють над тим, щоб налаштувати спочатку, але це надзвичайно потужно. Додати новий стан -> гарантовано без пропусків.
hlovdal

9

Дуже приємний «рамки» на основі шаблонів C ++ на основі шаблону надає Стефан Хайнцман у своїх статті .

Оскільки в статті немає посилання на повне завантаження коду, я взяв на себе можливість вставити код у проект та перевірити його. Далі наведено тестування, яке включає кілька незначних, але майже очевидних відсутніх фрагментів.

Основна інновація тут полягає в тому, що компілятор генерує дуже ефективний код. Порожні дії на в'їзд / виїзд не мають витрат. Непусті дії входу / виходу накреслені. Компілятор також перевіряє повноту діаграми стану. Пропущені дії породжують помилки зв’язку. Єдине, що не спіймане - це зниклі безвісти Top::init.

Це дуже приємна альтернатива реалізації Miro Samek, якщо ви можете жити без того, чого не вистачає, - це далеко не повна реалізація Ucha Statechart, хоча вона правильно реалізує семантику UML, тоді як код Samek за дизайном не обробляє вихід / перехід / вступні дії у правильному порядку.

Якщо цей код працює для того, що вам потрібно зробити, і у вас є гідний компілятор C ++ для вашої системи, він, ймовірно, буде краще, ніж реалізація C / C ++ Miro. Компілятор генерує для вас сплющену реалізацію машини перехідного стану O (1). Якщо аудит результатів складання підтвердить, що оптимізація працює за бажанням, ви наближаєтесь до теоретичних показників. Найкраща частина: це порівняно крихітний, простий для розуміння код.

#ifndef HSM_HPP
#define HSM_HPP

// This code is from:
// Yet Another Hierarchical State Machine
// by Stefan Heinzmann
// Overload issue 64 december 2004
// http://accu.org/index.php/journals/252

/* This is a basic implementation of UML Statecharts.
 * The key observation is that the machine can only
 * be in a leaf state at any given time. The composite
 * states are only traversed, never final.
 * Only the leaf states are ever instantiated. The composite
 * states are only mechanisms used to generate code. They are
 * never instantiated.
 */

// Helpers

// A gadget from Herb Sutter's GotW #71 -- depends on SFINAE
template<class D, class B>
class IsDerivedFrom {
    class Yes { char a[1]; };
    class No  { char a[10]; };
    static Yes Test(B*); // undefined
    static No Test(...); // undefined
public:
    enum { Res = sizeof(Test(static_cast<D*>(0))) == sizeof(Yes) ? 1 : 0 };
};

template<bool> class Bool {};

// Top State, Composite State and Leaf State

template <typename H>
struct TopState {
    typedef H Host;
    typedef void Base;
    virtual void handler(Host&) const = 0;
    virtual unsigned getId() const = 0;
};

template <typename H, unsigned id, typename B>
struct CompState;

template <typename H, unsigned id, typename B = CompState<H, 0, TopState<H> > >
struct CompState : B {
    typedef B Base;
    typedef CompState<H, id, Base> This;
    template <typename X> void handle(H& h, const X& x) const { Base::handle(h, x); }
    static void init(H&); // no implementation
    static void entry(H&) {}
    static void exit(H&) {}
};

template <typename H>
struct CompState<H, 0, TopState<H> > : TopState<H> {
    typedef TopState<H> Base;
    typedef CompState<H, 0, Base> This;
    template <typename X> void handle(H&, const X&) const {}
    static void init(H&); // no implementation
    static void entry(H&) {}
    static void exit(H&) {}
};

template <typename H, unsigned id, typename B = CompState<H, 0, TopState<H> > >
struct LeafState : B {
    typedef H Host;
    typedef B Base;
    typedef LeafState<H, id, Base> This;
    template <typename X> void handle(H& h, const X& x) const { Base::handle(h, x); }
    virtual void handler(H& h) const { handle(h, *this); }
    virtual unsigned getId() const { return id; }
    static void init(H& h) { h.next(obj); } // don't specialize this
    static void entry(H&) {}
    static void exit(H&) {}
    static const LeafState obj; // only the leaf states have instances
};

template <typename H, unsigned id, typename B>
const LeafState<H, id, B> LeafState<H, id, B>::obj;

// Transition Object

template <typename C, typename S, typename T>
// Current, Source, Target
struct Tran {
    typedef typename C::Host Host;
    typedef typename C::Base CurrentBase;
    typedef typename S::Base SourceBase;
    typedef typename T::Base TargetBase;
    enum { // work out when to terminate template recursion
        eTB_CB = IsDerivedFrom<TargetBase, CurrentBase>::Res,
        eS_CB = IsDerivedFrom<S, CurrentBase>::Res,
        eS_C = IsDerivedFrom<S, C>::Res,
        eC_S = IsDerivedFrom<C, S>::Res,
        exitStop = eTB_CB && eS_C,
        entryStop = eS_C || eS_CB && !eC_S
    };
    // We use overloading to stop recursion.
    // The more natural template specialization
    // method would require to specialize the inner
    // template without specializing the outer one,
    // which is forbidden.
    static void exitActions(Host&, Bool<true>) {}
    static void exitActions(Host&h, Bool<false>) {
        C::exit(h);
        Tran<CurrentBase, S, T>::exitActions(h, Bool<exitStop>());
    }
    static void entryActions(Host&, Bool<true>) {}
    static void entryActions(Host& h, Bool<false>) {
        Tran<CurrentBase, S, T>::entryActions(h, Bool<entryStop>());
        C::entry(h);
    }
    Tran(Host & h) : host_(h) {
        exitActions(host_, Bool<false>());
    }
    ~Tran() {
        Tran<T, S, T>::entryActions(host_, Bool<false>());
        T::init(host_);
    }
    Host& host_;
};

// Initializer for Compound States

template <typename T>
struct Init {
    typedef typename T::Host Host;
    Init(Host& h) : host_(h) {}
    ~Init() {
        T::entry(host_);
        T::init(host_);
    }
    Host& host_;
};

#endif // HSM_HPP

Наступний тестовий код.

#include <cstdio>
#include "hsm.hpp"
#include "hsmtest.hpp"

/* Implements the following state machine from Miro Samek's
 * Practical Statecharts in C/C++
 *
 * |-init-----------------------------------------------------|
 * |                           s0                             |
 * |----------------------------------------------------------|
 * |                                                          |
 * |    |-init-----------|        |-------------------------| |
 * |    |       s1       |---c--->|            s2           | |
 * |    |----------------|<--c----|-------------------------| |
 * |    |                |        |                         | |
 * |<-d-| |-init-------| |        | |-init----------------| | |
 * |    | |     s11    |<----f----| |          s21        | | |
 * | /--| |------------| |        | |---------------------| | |
 * | a  | |            | |        | |                     | | |
 * | \->| |            |------g--------->|-init------|    | | |
 * |    | |____________| |        | |-b->|    s211   |---g--->|
 * |    |----b---^       |------f------->|           |    | | |
 * |    |________________|        | |<-d-|___________|<--e----|
 * |                              | |_____________________| | |
 * |                              |_________________________| |
 * |__________________________________________________________|
 */

class TestHSM;

typedef CompState<TestHSM,0>     Top;
typedef CompState<TestHSM,1,Top>   S0;
typedef CompState<TestHSM,2,S0>      S1;
typedef LeafState<TestHSM,3,S1>        S11;
typedef CompState<TestHSM,4,S0>      S2;
typedef CompState<TestHSM,5,S2>        S21;
typedef LeafState<TestHSM,6,S21>         S211;

enum Signal { A_SIG, B_SIG, C_SIG, D_SIG, E_SIG, F_SIG, G_SIG, H_SIG };

class TestHSM {
public:
    TestHSM() { Top::init(*this); }
    ~TestHSM() {}
    void next(const TopState<TestHSM>& state) {
        state_ = &state;
    }
    Signal getSig() const { return sig_; }
    void dispatch(Signal sig) {
        sig_ = sig;
        state_->handler(*this);
    }
    void foo(int i) {
        foo_ = i;
    }
    int foo() const {
        return foo_;
    }
private:
    const TopState<TestHSM>* state_;
    Signal sig_;
    int foo_;
};

bool testDispatch(char c) {
    static TestHSM test;
    if (c<'a' || 'h'<c) {
        return false;
    }
    printf("Signal<-%c", c);
    test.dispatch((Signal)(c-'a'));
    printf("\n");
    return true;
}

int main(int, char**) {
    testDispatch('a');
    testDispatch('e');
    testDispatch('e');
    testDispatch('a');
    testDispatch('h');
    testDispatch('h');
    return 0;
}

#define HSMHANDLER(State) \
    template<> template<typename X> inline void State::handle(TestHSM& h, const X& x) const

HSMHANDLER(S0) {
    switch (h.getSig()) {
    case E_SIG: { Tran<X, This, S211> t(h);
        printf("s0-E;");
        return; }
    default:
        break;
    }
    return Base::handle(h, x);
}

HSMHANDLER(S1) {
    switch (h.getSig()) {
    case A_SIG: { Tran<X, This, S1> t(h);
        printf("s1-A;"); return; }
    case B_SIG: { Tran<X, This, S11> t(h);
        printf("s1-B;"); return; }
    case C_SIG: { Tran<X, This, S2> t(h);
        printf("s1-C;"); return; }
    case D_SIG: { Tran<X, This, S0> t(h);
        printf("s1-D;"); return; }
    case F_SIG: { Tran<X, This, S211> t(h);
        printf("s1-F;"); return; }
    default: break;
    }
    return Base::handle(h, x);
}

HSMHANDLER(S11) {
    switch (h.getSig()) {
    case G_SIG: { Tran<X, This, S211> t(h);
        printf("s11-G;"); return; }
    case H_SIG: if (h.foo()) {
            printf("s11-H");
            h.foo(0); return;
        } break;
    default: break;
    }
    return Base::handle(h, x);
}

HSMHANDLER(S2) {
    switch (h.getSig()) {
    case C_SIG: { Tran<X, This, S1> t(h);
        printf("s2-C"); return; }
    case F_SIG: { Tran<X, This, S11> t(h);
        printf("s2-F"); return; }
    default: break;
    }
    return Base::handle(h, x);
}

HSMHANDLER(S21) {
    switch (h.getSig()) {
    case B_SIG: { Tran<X, This, S211> t(h);
        printf("s21-B;"); return; }
    case H_SIG: if (!h.foo()) {
            Tran<X, This, S21> t(h);
            printf("s21-H;"); h.foo(1);
            return;
        } break;
    default: break;
    }
    return Base::handle(h, x);
}

HSMHANDLER(S211) {
    switch (h.getSig()) {
    case D_SIG: { Tran<X, This, S21> t(h);
        printf("s211-D;"); return; }
    case G_SIG: { Tran<X, This, S0> t(h);
        printf("s211-G;"); return; }
    }
    return Base::handle(h, x);
}

#define HSMENTRY(State) \
    template<> inline void State::entry(TestHSM&) { \
        printf(#State "-ENTRY;"); \
    }

HSMENTRY(S0)
HSMENTRY(S1)
HSMENTRY(S11)
HSMENTRY(S2)
HSMENTRY(S21)
HSMENTRY(S211)

#define HSMEXIT(State) \
    template<> inline void State::exit(TestHSM&) { \
        printf(#State "-EXIT;"); \
    }

HSMEXIT(S0)
HSMEXIT(S1)
HSMEXIT(S11)
HSMEXIT(S2)
HSMEXIT(S21)
HSMEXIT(S211)

#define HSMINIT(State, InitState) \
    template<> inline void State::init(TestHSM& h) { \
       Init<InitState> i(h); \
       printf(#State "-INIT;"); \
    }

HSMINIT(Top, S0)
HSMINIT(S0, S1)
HSMINIT(S1, S11)
HSMINIT(S2, S21)
HSMINIT(S21, S211)

Хм ... sth, у вашому коді відсутнє. Перш за все, ви включаєте два заголовки, але надайте лише перший. Коли я просто коментую заяву "включити", я отримую цю помилку під час компіляції: d: \ 1 \ hsm> g ++ test.cpp test.cpp: 195: 1: помилка: спеціалізація "статичної недійсності CompState <H, id, B> :: init (H &) [з Н = TestHSM; неподписаний int id = 0u; B = CompState <TestHSM, 0u, TopState <TestHSM>>] "після встановлення
Фредді Шопен

Я повинен був перемістити визначення всіх HSMINIT (), щоб бути вище класу TestHSM, і він добре компілює та працює (; Єдине, що не так - це те, що всі переходи "зовнішні", тоді як вони повинні бути "внутрішніми" - там було деякі суперечки про це в статті , і автор вирішили , що «extrenal» мав рацію, але стріли використовували запропонувати «внутрішні».
Фредді Шопен

5

Мені подобається в державних машинах (принаймні, для програмного керування) - використовувати функціональні покажчики. Кожен стан представлений різною функцією. Функція приймає символ введення і повертає функціональний вказівник на наступний стан. Монітор центрального диспетчерського циклу приймає наступний вхід, подає його до поточного стану та обробляє результат.

Введення тексту стає трохи дивним, оскільки C не має способу вказати типи покажчиків функцій, що повертаються самі, тому повертаються функції стану void*. Але ви можете зробити щось подібне:

typedef void* (*state_handler)(input_symbol_t);
void dispatch_fsm()
{
    state_handler current = initial_handler;
    /* Let's assume returning null indicates end-of-machine */
    while (current) {
        current = current(get_input);
    }
 }

Тоді ваші окремі функції стану можуть увімкнути свій вхід для обробки та повернути відповідне значення.


+1, що справді приємно, і надає приємні місця для передачі функціональності всередині функцій переходу
Fire Crow

5

Найпростіший випадок

enum event_type { ET_THIS, ET_THAT };
union event_parm { uint8_t this; uint16_t that; }
static void handle_event(enum event_type event, union event_parm parm)
{
  static enum { THIS, THAT } state;
  switch (state)
  {
    case THIS:
    switch (event)
    {
      case ET_THIS:
      // Handle event.
      break;

      default:
      // Unhandled events in this state.
      break;
    }
    break;

    case THAT:
    // Handle state.
    break;
  }
}

Окуляри: Держава є приватною, не тільки для компіляційного блоку, але і для події_інформатора. Спеціальні випадки можуть оброблятися окремо від головного комутатора, використовуючи будь-яку конструкцію, яку вважають за потрібну.

Більш складний випадок

Коли перемикач переповнюється на пару екранів, розділіть його на функції, що обробляють кожен стан, використовуючи таблицю стану, щоб безпосередньо шукати функцію. Держава як і раніше є приватною особою, яка займається організацією подій. Функції обробника стану повертають наступний стан. У разі потреби деякі події все ще можуть отримати спеціальне звернення до основного обробника подій. Мені подобається вводити псевдоподії для входу та виходу у стан та, можливо, запуск державної машини:

enum state_type { THIS, THAT, FOO, NA };
enum event_type { ET_START, ET_ENTER, ET_EXIT, ET_THIS, ET_THAT, ET_WHATEVER, ET_TIMEOUT };
union event_parm { uint8_t this; uint16_t that; };
static void handle_event(enum event_type event, union event_parm parm)
{
  static enum state_type state;
  static void (* const state_handler[])(enum event_type event, union event_parm parm) = { handle_this, handle_that };
  enum state_type next_state = state_handler[state](event, parm);
  if (NA != next_state && state != next_state)
  {
    (void)state_handler[state](ET_EXIT, 0);
    state = next_state;
    (void)state_handler[state](ET_ENTER, 0);
  }
}

Я не впевнений, чи прибив я синтаксис, особливо стосовно масиву функціональних покажчиків. Я нічого не запускав через це через компілятор. Переглянувши, я помітив, що забув я відмовитись від наступного стану під час обробки псевдоподій ((недійсних) дужок перед викликом до state_handler ()). Це те, що мені подобається робити, навіть якщо компілятори мовчки сприймають упущення. Він повідомляє читачам коду, що "так, я дійсно мав на увазі викликати функцію без використання зворотного значення", і це може зупинити засоби статичного аналізу від попередження про це. Це може бути ідіосинкратично, тому що я не пригадую, щоб хто-небудь ще це робив.

Окуляри: додавши крихітний біт складності (перевіряючи, чи не наступний стан відрізняється від поточного), можна уникнути дублювання коду в іншому місці, оскільки функції оброблювача стану можуть насолоджуватися псевдоподіями, які виникають при введенні та залишенні стану. Пам’ятайте, що стан не може змінюватися при обробці псевдоподій, оскільки результат обробника стану відміняється після цих подій. Звичайно, ви можете змінити поведінку.

Державний обробник виглядатиме так:

static enum state_type handle_this(enum event_type event, union event_parm parm)
{
  enum state_type next_state = NA;
  switch (event)
  {
    case ET_ENTER:
    // Start a timer to do whatever.
    // Do other stuff necessary when entering this state.
    break;

    case ET_WHATEVER:
    // Switch state.
    next_state = THAT;
    break;

    case ET_TIMEOUT:
    // Switch state.
    next_state = FOO;
    break;

    case ET_EXIT:
    // Stop the timer.
    // Generally clean up this state.
    break;
  }
  return next_state;
}

Більше складності

Коли одиниця компіляції стає занадто великою (що б ви не відчували, що я маю на увазі, я повинен сказати близько 1000 рядків), покладіть кожен обробник стану в окремий файл. Коли кожен обробник стану стає довше, ніж пара екранів, розділіть кожну подію на окрему функцію, подібно до того, як був розбитий перемикач стану. Це можна зробити різними способами, окремо від штату або за допомогою загальної таблиці або комбінуючи різні схеми. Деякі з них були охоплені тут іншими. Сортуйте свої таблиці та використовуйте двійковий пошук, якщо швидкість є потребою.

Загальне програмування

Мені б хотілося, щоб препроцесор розглядав такі питання, як сортування таблиць або навіть генерування державних машин з описів, що дозволяє вам "писати програми про програми". Я вважаю, що для цього люди Boost експлуатують шаблони C ++, але я вважаю синтаксис загадковим.

Двовимірні таблиці

У минулому я використовував таблиці стану / подій, але мушу сказати, що для найпростіших випадків я не вважаю їх необхідними, і я віддаю перевагу чіткість та читаність оператора перемикання, навіть якщо він поширюється на повний екран. У більш складних випадках таблиці швидко виходять з ладу, як зазначали інші. Представлені тут ідіоми дозволяють додавати цілий ряд подій та станів, коли вам це подобається, без необхідності підтримувати таблицю, що споживає пам'ять (навіть якщо це може бути програмна пам'ять).

Відмова від відповідальності

Особливі потреби можуть зробити ці ідіоми менш корисними, але я вважаю їх дуже зрозумілими та реальними.


Я б уникну "цього" як ім'я змінної або символ лише для асоціації, навіть якщо це насправді не зарезервоване слово.
XTL

4

Надзвичайно неперевірений, але цікавий для кодування, тепер у більш доопрацьованій версії, ніж моя оригінальна відповідь; оновлені версії можна знайти на сайті mercurial.intuxication.org :

см.х

#ifndef SM_ARGS
#error "SM_ARGS undefined: " \
    "use '#define SM_ARGS (void)' to get an empty argument list"
#endif

#ifndef SM_STATES
#error "SM_STATES undefined: " \
    "you must provide a list of comma-separated states"
#endif

typedef void (*sm_state) SM_ARGS;
static const sm_state SM_STATES;

#define sm_transit(STATE) ((sm_state (*) SM_ARGS)STATE)

#define sm_def(NAME) \
    static sm_state NAME ## _fn SM_ARGS; \
    static const sm_state NAME = (sm_state)NAME ## _fn; \
    static sm_state NAME ## _fn SM_ARGS

example.c

#include <stdio.h>

#define SM_ARGS (int i)
#define SM_STATES EVEN, ODD
#include "sm.h"

sm_def(EVEN)
{
    printf("even %i\n", i);
    return ODD;
}

sm_def(ODD)
{
    printf("odd  %i\n", i);
    return EVEN;
}

int main(void)
{
    int i = 0;
    sm_state state = EVEN;

    for(; i < 10; ++i)
        state = sm_transit(state)(i);

    return 0;
}

14
Я люблю "надзвичайно неперевірений" коментар. Здається, це вказує на те, що є ступінь неперевіреності і що ви доклали чимало зусиль, щоб не перевірити це :-)
paxdiablo

@Christoph посилання в цій відповіді порушено. Також ви протестували цей код чи ні? Якщо він перевірений і працює, слід усунути це з відповіді. Також, можливо, покажіть приклад того, який код отримує цей результат, як тільки макроси розгорнулися. Мені подобається загальна ідея.
Йоаким

4

Мені дуже сподобалась відповідь paxdiable і я вирішив реалізувати всі відсутні функції для мого додатка, такі як змінні захисту та дані, що стосуються стану машини.

Я завантажив свою реалізацію на цей сайт, щоб поділитися з громадою. Це було протестовано за допомогою IAR Embedded Workbench для ARM.

https://sourceforge.net/projects/compactfsm/


Дізнавшись про це у 2018 році та що він досі застосовується. Я читав відповідь @paxdiablo, і раніше успішно використовував такий тип реалізації у вбудованих системах. Це рішення додає пропущені речі з відповіді paxdiablos :)
Kristoffer

4

Ще один цікавий інструмент з відкритим вихідним кодом - Yakindu Statechart Tools на statecharts.org . Він використовує державні діаграми Харела і, таким чином, надає ієрархічні та паралельні стани та генерує код C та C ++ (а також Java). Він не використовує бібліотеки, але дотримується підходу «звичайного коду». Код в основному застосовує структури корпусних комутаторів. Генератори коду також можуть бути налаштовані. Крім того, інструмент надає багато інших функцій.


3

Підходячи до цього пізно (як зазвичай), але скануючи відповіді на сьогоднішній день, я думаю, що чогось важливого не вистачає;

Я знайшов в своїх проектах , що це може бути дуже корисно , щоб НЕ мати функцію для кожної дійсної комбінації стану / події. Мені подобається ідея ефективного створення 2D таблиці станів / подій. Але мені подобається, що елементи таблиці є більш ніж простим вказівником функції. Натомість я намагаюся організувати свій дизайн так, що в його основі лежить купа простих атомних елементів або дій. Таким чином я можу перерахувати ці прості атомні елементи на кожному перетині моєї таблиці стану / подій. Ідея полягає в тому, що ви цього не робите потрібно визначати масу N квадратних (як правило, дуже простих) функцій. Чому щось так схильне до помилок, забирає багато часу, важко писати, важко читати, ти це називаєш?

Я також включаю необов'язковий новий стан та необов'язковий вказівник функції для кожної комірки таблиці. Функціональний покажчик існує для тих виняткових випадків, коли ви не хочете просто запускати список атомних дій.

Ви знаєте, що все робите правильно, коли ви можете висловити безліч різних функціональних можливостей, просто редагуючи таблицю, не маючи нового коду для запису.


2
Може, приклад був би приємний, ні?
jldupont

1
Реалістичний приклад, який можна подати поодиноко, - це складне завдання, яке вимагало б більше часу, ніж я готовий дати саме на даний момент. Чи є в моєму дописі щось особливо важко зрозуміти? Можливо, я можу це виразніше висловити. Ідея дуже проста; Не визначайте механізм стану, який вимагає окремої функції для кожної комбінації подій / стану, ви отримуєте таким чином занадто багато функцій. Натомість знайдіть інший спосіб описати потрібну функціональність для цієї комбінації подій та стану, принаймні у більшості випадків.
Білл Форстер

2
Зрозуміли: приклад псевдо-коду був би хорошим, але ваш погляд зрозумілий.
jldupont

3

Добре, я думаю, що моя трохи відрізняється від усіх інших. Трохи більше розділення коду та даних, ніж я бачу в інших відповідях. Я справді читав на теорії, щоб написати це, що реалізує повну регулярну мову (без регулярних виразів, на жаль). Уллман, Мінський, Хомський. Не можу сказати, що я все це зрозумів, але я витяг із старих майстрів максимально прямо: через їх слова.

Я використовую вказівник функції на предикат, який визначає перехід до стану "так" або "ні". Це полегшує створення акцептора кінцевого стану для звичайної мови, яку ви запрограмуєте в більш схожій мові. Будь ласка, не відкладайте мої дурні варіанти імен. 'czek' == 'чек'. 'grok' == [перегляньте це в словнику Хакера].

Отже, для кожної ітерації czek називає функцію предиката з поточним символом як аргумент. Якщо предикат повертає істину, символ споживається (покажчик розширений), і ми слідуємо переходу 'y', щоб вибрати наступний стан. Якщо присудок повертає помилково, символ НЕ споживається, і ми слідуємо за переходом 'n'. Тож кожна інструкція - це двостороння галузь! Напевно, я тоді читав «Історію Мела».

Цей код походить безпосередньо від мого інтерпретатора постскриптуму і перетворився на його сучасний вигляд із великими рекомендаціями від стипендіатів на comp.lang.c. Оскільки постскрипт в основному не має синтаксису (вимагає лише збалансованих дужок), як звичайний аналізатор функціонує також регулярний мова Accepter.

/* currentstr is set to the start of string by czek
   and used by setrad (called by israd) to set currentrad
   which is used by israddig to determine if the character
   in question is valid for the specified radix
   --
   a little semantic checking in the syntax!
 */
char *currentstr;
int currentrad;
void setrad(void) {
    char *end;
    currentrad = strtol(currentstr, &end, 10);
    if (*end != '#' /* just a sanity check,
                       the automaton should already have determined this */
    ||  currentrad > 36
    ||  currentrad < 2)
        fatal("bad radix"); /* should probably be a simple syntaxerror */
}

/*
   character classes
   used as tests by automatons under control of czek
 */
char *alpha = "0123456789" "ABCDE" "FGHIJ" "KLMNO" "PQRST" "UVWXYZ";
#define EQ(a,b) a==b
#define WITHIN(a,b) strchr(a,b)!=NULL
int israd  (int c) {
    if (EQ('#',c)) { setrad(); return true; }
    return false;
}
int israddig(int c) {
    return strchrnul(alpha,toupper(c))-alpha <= currentrad;
}
int isdot  (int c) {return EQ('.',c);}
int ise    (int c) {return WITHIN("eE",c);}
int issign (int c) {return WITHIN("+-",c);}
int isdel  (int c) {return WITHIN("()<>[]{}/%",c);}
int isreg  (int c) {return c!=EOF && !isspace(c) && !isdel(c);}
#undef WITHIN
#undef EQ

/*
   the automaton type
 */
typedef struct { int (*pred)(int); int y, n; } test;

/*
   automaton to match a simple decimal number
 */
/* /^[+-]?[0-9]+$/ */
test fsm_dec[] = {
/* 0*/ { issign,  1,  1 },
/* 1*/ { isdigit, 2, -1 },
/* 2*/ { isdigit, 2, -1 },
};
int acc_dec(int i) { return i==2; }

/*
   automaton to match a radix number
 */
/* /^[0-9]+[#][a-Z0-9]+$/ */
test fsm_rad[] = {
/* 0*/ { isdigit,  1, -1 },
/* 1*/ { isdigit,  1,  2 },
/* 2*/ { israd,    3, -1 },
/* 3*/ { israddig, 4, -1 },
/* 4*/ { israddig, 4, -1 },
};
int acc_rad(int i) { return i==4; }

/*
   automaton to match a real number
 */
/* /^[+-]?(d+(.d*)?)|(d*.d+)([eE][+-]?d+)?$/ */
/* represents the merge of these (simpler) expressions
   [+-]?[0-9]+\.[0-9]*([eE][+-]?[0-9]+)?
   [+-]?[0-9]*\.[0-9]+([eE][+-]?[0-9]+)?
   The complexity comes from ensuring at least one
   digit in the integer or the fraction with optional
   sign and optional optionally-signed exponent.
   So passing isdot in state 3 means at least one integer digit has been found
   but passing isdot in state 4 means we must find at least one fraction digit
   via state 5 or the whole thing is a bust.
 */
test fsm_real[] = {
/* 0*/ { issign,  1,   1 },
/* 1*/ { isdigit, 2,   4 },
/* 2*/ { isdigit, 2,   3 },
/* 3*/ { isdot,   6,   7 },
/* 4*/ { isdot,   5,  -1 },
/* 5*/ { isdigit, 6,  -1 },
/* 6*/ { isdigit, 6,   7 },
/* 7*/ { ise,     8,  -1 },
/* 8*/ { issign,  9,   9 },
/* 9*/ { isdigit, 10, -1 },
/*10*/ { isdigit, 10, -1 },
};
int acc_real(int i) {
    switch(i) {
        case 2: /* integer */
        case 6: /* real */
        case 10: /* real with exponent */
            return true;
    }
    return false;
}

/*
   Helper function for grok.
   Execute automaton against the buffer,
   applying test to each character:
       on success, consume character and follow 'y' transition.
       on failure, do not consume but follow 'n' transition.
   Call yes function to determine if the ending state
   is considered an acceptable final state.
   A transition to -1 represents rejection by the automaton
 */
int czek (char *s, test *fsm, int (*yes)(int)) {
    int sta = 0;
    currentstr = s;
    while (sta!=-1 && *s) {
        if (fsm[sta].pred((int)*s)) {
            sta=fsm[sta].y;
            s++;
        } else {
            sta=fsm[sta].n;
        }
    }
    return yes(sta);
}

/*
   Helper function for toke.
   Interpret the contents of the buffer,
   trying automatons to match number formats;
   and falling through to a switch for special characters.
   Any token consisting of all regular characters
   that cannot be interpreted as a number is an executable name
 */
object grok (state *st, char *s, int ns,
    object *src,
    int (*next)(state *,object *),
    void (*back)(state *,int, object *)) {

    if (czek(s, fsm_dec, acc_dec)) {
        long num;
        num = strtol(s,NULL,10);
        if ((num==LONG_MAX || num==LONG_MIN) && errno==ERANGE) {
            error(st,limitcheck);
/*       } else if (num > INT_MAX || num < INT_MIN) { */
/*           error(limitcheck, OP_token); */
        } else {
            return consint(num);
        }
    }

    else if (czek(s, fsm_rad, acc_rad)) {
        long ra,num;
        ra = (int)strtol(s,NULL,10);
        if (ra > 36 || ra < 2) {
            error(st,limitcheck);
        }
        num = strtol(strchr(s,'#')+1, NULL, (int)ra);
        if ((num==LONG_MAX || num==LONG_MIN) && errno==ERANGE) {
            error(st,limitcheck);
/*       } else if (num > INT_MAX || num < INT_MAX) { */
/*           error(limitcheck, OP_token); */
        } else {
            return consint(num);
        }
    }

    else if (czek(s, fsm_real, acc_real)) {
        double num;
        num = strtod(s,NULL);
        if ((num==HUGE_VAL || num==-HUGE_VAL) && errno==ERANGE) {
            error(st,limitcheck);
        } else {
            return consreal(num);
        }
    }

    else switch(*s) {
        case '(': {
            int c, defer=1;
            char *sp = s;

            while (defer && (c=next(st,src)) != EOF ) {
                switch(c) {
                    case '(': defer++; break;
                    case ')': defer--;
                        if (!defer) goto endstring;
                        break;
                    case '\\': c=next(st,src);
                        switch(c) {
                            case '\n': continue;
                            case 'a': c = '\a'; break;
                            case 'b': c = '\b'; break;
                            case 'f': c = '\f'; break;
                            case 'n': c = '\n'; break;
                            case 'r': c = '\r'; break;
                            case 't': c = '\t'; break;
                            case 'v': c = '\v'; break;
                            case '\'': case '\"':
                            case '(': case ')':
                            default: break;
                        }
                }
                if (sp-s>ns) error(st,limitcheck);
                else *sp++ = c;
            }
endstring:  *sp=0;
            return cvlit(consstring(st,s,sp-s));
        }

        case '<': {
            int c;
            char d, *x = "0123456789abcdef", *sp = s;
            while (c=next(st,src), c!='>' && c!=EOF) {
                if (isspace(c)) continue;
                if (isxdigit(c)) c = strchr(x,tolower(c)) - x;
                else error(st,syntaxerror);
                d = (char)c << 4;
                while (isspace(c=next(st,src))) /*loop*/;
                if (isxdigit(c)) c = strchr(x,tolower(c)) - x;
                else error(st,syntaxerror);
                d |= (char)c;
                if (sp-s>ns) error(st,limitcheck);
                *sp++ = d;
            }
            *sp = 0;
            return cvlit(consstring(st,s,sp-s));
        }

        case '{': {
            object *a;
            size_t na = 100;
            size_t i;
            object proc;
            object fin;

            fin = consname(st,"}");
            (a = malloc(na * sizeof(object))) || (fatal("failure to malloc"),0);
            for (i=0 ; objcmp(st,a[i]=toke(st,src,next,back),fin) != 0; i++) {
                if (i == na-1)
                (a = realloc(a, (na+=100) * sizeof(object))) || (fatal("failure to malloc"),0);
            }
            proc = consarray(st,i);
            { size_t j;
                for (j=0; j<i; j++) {
                    a_put(st, proc, j, a[j]);
                }
            }
            free(a);
            return proc;
        }

        case '/': {
            s[1] = (char)next(st,src);
            puff(st, s+2, ns-2, src, next, back);
            if (s[1] == '/') {
                push(consname(st,s+2));
                opexec(st, op_cuts.load);
                return pop();
            }
            return cvlit(consname(st,s+1));
        }

        default: return consname(st,s);
    }
    return null; /* should be unreachable */
}

/*
   Helper function for toke.
   Read into buffer any regular characters.
   If we read one too many characters, put it back
   unless it's whitespace.
 */
int puff (state *st, char *buf, int nbuf,
    object *src,
    int (*next)(state *,object *),
    void (*back)(state *,int, object *)) {
    int c;
    char *s = buf;
    while (isreg(c=next(st,src))) {
        if (s-buf >= nbuf-1) return false;
        *s++ = c;
    }
    *s = 0;
    if (!isspace(c) && c != EOF) back(st,c,src); /* eat interstice */
    return true;
}

/*
   Helper function for Stoken Ftoken.
   Read a token from src using next and back.
   Loop until having read a bona-fide non-whitespace non-comment character.
   Call puff to read into buffer up to next delimiter or space.
   Call grok to figure out what it is.
 */
#define NBUF MAXLINE
object toke (state *st, object *src,
        int (*next)(state *, object *),
        void (*back)(state *, int, object *)) {
    char buf[NBUF] = "", *s=buf;
    int c,sta = 1;
    object o;

    do {
        c=next(st,src);
        //if (c==EOF) return null;
        if (c=='%') {
            if (DUMPCOMMENTS) fputc(c, stdout);
            do {
                c=next(st,src);
                if (DUMPCOMMENTS) fputc(c, stdout);
            } while (c!='\n' && c!='\f' && c!=EOF);
        }
    } while (c!=EOF && isspace(c));
    if (c==EOF) return null;
    *s++ = c;
    *s = 0;
    if (!isdel(c)) sta=puff(st, s,NBUF-1,src,next,back);

    if (sta) {
        o=grok(st,buf,NBUF-1,src,next,back);
        return o;
    } else {
        return null;
    }
}

2
Це те, що будь-який аналізатор парсера чи лексерів із задоволенням випромінює для вас. Незвично так. Чи хочете ви кодувати це вручну, сумнівно. Це, звичайно, має педагогічну заслугу.
Відновіть Моніку

3

boost.org постачається з двома різними реалізаціями діаграм стану:

Як завжди, прискорення призведе вас до пекла шаблонів.

Перша бібліотека призначена для машин з більш критичним показником продуктивності. Друга бібліотека дає вам прямий шлях переходу від діаграми стану UML до коду.

Ось питання ЗО, яке задає порівняння між двома, де відповідають обидва автори.



2

Побачив це десь

#define FSM
#define STATE(x)      s_##x :
#define NEXTSTATE(x)  goto s_##x

FSM {
  STATE(x) {
    ...
    NEXTSTATE(y);
  }

  STATE(y) {
    ...
    if (x == 0)
      NEXTSTATE(y);
    else
      NEXTSTATE(x);
  }
}

1
Це цікаво, але жодних підсумків, поки ви не приведете приклад або два (і, можливо, результат макроекології) або якусь дискусію щодо того, чому це може бути більш практичним, ніж інше. Цікаве використання осиротілих дужок та макросів. Я думаю, що щось подібне можна зробити на мові, яка робить якусь оптимізацію хвостової рекурсії; Ви можете використовувати прямі виклики функцій і не турбуватися про перевантаження простору стеку сміттям функціональних викликів (що, на мою думку, макроси тут по суті долають)
Ape-inago

2
Перевагами цього методу є ...? Я бачу декілька недоліків, таких як маскування макросів, і використання gotoцього створює залежність від передбачуваної багатозадачності ОС.
Крейг МакКуїн

2

Зважаючи на те, що ви маєте на увазі, що ви можете використовувати C ++ і, отже, код OO, я б запропонував оцінити "GoF'state" (GoF = Банда з чотирьох, хлопці, які написали книгу моделей дизайну, яка принесла дизайнерські шаблони в центр уваги).

Він не особливо складний і широко використовується і обговорюється, тому легко бачити приклади та пояснення в режимі он-лайн.

Він також може бути розпізнаний будь-ким іншим, хто підтримує ваш код пізніше.

Якщо ефективність викликає занепокоєння, варто було б насправді порівняльний аналіз, щоб переконатися, що підхід, що не входить в ООС, є більш ефективним, оскільки безліч факторів впливає на продуктивність, і це не завжди просто поганий функціональний код. Так само, якщо використання пам'яті є обмеженням для вас, то знову варто зробити кілька тестів або розрахунків, щоб побачити, чи це насправді буде проблемою для вашої конкретної програми, якщо ви використовуєте шаблон стану.

Нижче наведено декілька посилань на модель стану "Гоф", як пропонує Крейг:


більше схожий на коментар: чи можу я запропонувати вам ставитися до цього як до такого? тобто не розміщуйте його в розділі "відповідь".
jldupont

Було б добре, якщо ви могли б надати гарне посилання URL-адреси для "шаблону стану GoF" для тих, хто не знає його.
Крейг МакКуїн

1
@jldupont - справедливий коментар. Я змінив текст, щоб зробити його правильною відповіддю, оскільки відчуваю, що базується на особистому досвіді, що, якщо не виникнуть конкретні проблеми з ефективністю, підхід GoF прекрасно працює і матиме відносно велику «базу користувачів»
Мік

@Craig - додав декілька посилань. Обидва виглядали точними та чіткими в той момент, коли я їх додав.
Мік

2

Ось приклад Кінцевої машини машини для Linux, яка використовує черги повідомлень як події. Події ставлять на чергу та обробляють по порядку. Стан змінюється залежно від того, що відбувається для кожної події.

Це приклад з'єднання даних із станами типу:

  • Неініціалізований
  • Ініціалізовано
  • Підключено
  • Переговори про МТУ
  • Перевірені

Ще однією додатковою функцією, яку я додав, була позначка часу для кожного повідомлення / події. Обробник події проігнорує події, які є занадто старими (вони минули). Це може статися багато в реальному світі, де ви можете несподівано застрягти в стані.

Цей приклад працює в Linux, використовуйте Makefile нижче, щоб компілювати його і грати з ним.

state_machine.c

#include <stdio.h>
#include <stdint.h>
#include <assert.h>
#include <unistd.h>   // sysconf()
#include <errno.h>    // errno
#include <string.h>   // strerror()
#include <sys/time.h> // gettimeofday()
#include <fcntl.h>    // For O_* constants
#include <sys/stat.h> // For mode constants

#include <mqueue.h>
#include <poll.h>

//------------------------------------------------
// States
//------------------------------------------------
typedef enum
{
    ST_UNKNOWN = 0,
    ST_UNINIT,
    ST_INIT,
    ST_CONNECTED,
    ST_MTU_NEGOTIATED,
    ST_AUTHENTICATED,
    ST_ERROR,
    ST_DONT_CHANGE,
    ST_TERM,
} fsmState_t;

//------------------------------------------------
// Events
//------------------------------------------------
typedef enum
{
    EV_UNKNOWN = 0,
    EV_INIT_SUCCESS,
    EV_INIT_FAIL,
    EV_MASTER_CMD_MSG,
    EV_CONNECT_SUCCESS,
    EV_CONNECT_FAIL,
    EV_MTU_SUCCESS,
    EV_MTU_FAIL,
    EV_AUTH_SUCCESS,
    EV_AUTH_FAIL,
    EV_TX_SUCCESS,
    EV_TX_FAIL,
    EV_DISCONNECTED,
    EV_DISCON_FAILED,
    EV_LAST_ENTRY,
} fsmEvName_t;

typedef struct fsmEvent_type
{
    fsmEvName_t name;
    struct timeval genTime; // Time the event was generated.
                            // This allows us to see how old the event is.
} fsmEvent_t;

// Finite State Machine Data Members
typedef struct fsmData_type
{
    int  connectTries;
    int  MTUtries;
    int  authTries;
    int  txTries;
} fsmData_t;

// Each row of the state table
typedef struct stateTable_type {
    fsmState_t  st;             // Current state
    fsmEvName_t evName;         // Got this event
    int (*conditionfn)(void *);  // If this condition func returns TRUE
    fsmState_t nextState;       // Change to this state and
    void (*fn)(void *);          // Run this function
} stateTable_t;

// Finite State Machine state structure
typedef struct fsm_type
{
    const stateTable_t *pStateTable; // Pointer to state table
    int        numStates;            // Number of entries in the table
    fsmState_t currentState;         // Current state
    fsmEvent_t currentEvent;         // Current event
    fsmData_t *fsmData;              // Pointer to the data attributes
    mqd_t      mqdes;                // Message Queue descriptor
    mqd_t      master_cmd_mqdes;     // Master command message queue
} fsm_t;

// Wildcard events and wildcard state
#define   EV_ANY    -1
#define   ST_ANY    -1
#define   TRUE     (1)
#define   FALSE    (0)

// Maximum priority for message queues (see "man mq_overview")
#define FSM_PRIO  (sysconf(_SC_MQ_PRIO_MAX) - 1)

static void addev                              (fsm_t *fsm, fsmEvName_t ev);
static void doNothing                          (void *fsm) {addev(fsm, EV_MASTER_CMD_MSG);}
static void doInit                             (void *fsm) {addev(fsm, EV_INIT_SUCCESS);}
static void doConnect                          (void *fsm) {addev(fsm, EV_CONNECT_SUCCESS);}
static void doMTU                              (void *fsm) {addev(fsm, EV_MTU_SUCCESS);}
static void reportFailConnect                  (void *fsm) {addev(fsm, EV_ANY);}
static void doAuth                             (void *fsm) {addev(fsm, EV_AUTH_SUCCESS);}
static void reportDisConnect                   (void *fsm) {addev(fsm, EV_ANY);}
static void doDisconnect                       (void *fsm) {addev(fsm, EV_ANY);}
static void doTransaction                      (void *fsm) {addev(fsm, EV_TX_FAIL);}
static void fsmError                           (void *fsm) {addev(fsm, EV_ANY);}

static int currentlyLessThanMaxConnectTries    (void *fsm) {
    fsm_t *l = (fsm_t *)fsm;
    return (l->fsmData->connectTries < 5 ? TRUE : FALSE);
}
static int        isMoreThanMaxConnectTries    (void *fsm) {return TRUE;}
static int currentlyLessThanMaxMTUtries        (void *fsm) {return TRUE;}
static int        isMoreThanMaxMTUtries        (void *fsm) {return TRUE;}
static int currentyLessThanMaxAuthTries        (void *fsm) {return TRUE;}
static int       isMoreThanMaxAuthTries        (void *fsm) {return TRUE;}
static int currentlyLessThanMaxTXtries         (void *fsm) {return FALSE;}
static int        isMoreThanMaxTXtries         (void *fsm) {return TRUE;}
static int didNotSelfDisconnect                (void *fsm) {return TRUE;}

static int  waitForEvent                       (fsm_t *fsm);
static void runEvent                           (fsm_t *fsm);
static void runStateMachine(fsm_t *fsm);
static int newEventIsValid(fsmEvent_t *event);
static void getTime(struct timeval *time);
void printState(fsmState_t st);
void printEvent(fsmEvName_t ev);

// Global State Table
const stateTable_t GST[] = {
    // Current state         Got this event          If this condition func returns TRUE     Change to this state and    Run this function
    { ST_UNINIT,             EV_INIT_SUCCESS,        NULL,                                   ST_INIT,                    &doNothing              },
    { ST_UNINIT,             EV_INIT_FAIL,           NULL,                                   ST_UNINIT,                  &doInit                 },
    { ST_INIT,               EV_MASTER_CMD_MSG,      NULL,                                   ST_INIT,                    &doConnect              },
    { ST_INIT,               EV_CONNECT_SUCCESS,     NULL,                                   ST_CONNECTED,               &doMTU                  },
    { ST_INIT,               EV_CONNECT_FAIL,        &currentlyLessThanMaxConnectTries,      ST_INIT,                    &doConnect              },
    { ST_INIT,               EV_CONNECT_FAIL,        &isMoreThanMaxConnectTries,             ST_INIT,                    &reportFailConnect      },
    { ST_CONNECTED,          EV_MTU_SUCCESS,         NULL,                                   ST_MTU_NEGOTIATED,          &doAuth                 },
    { ST_CONNECTED,          EV_MTU_FAIL,            &currentlyLessThanMaxMTUtries,          ST_CONNECTED,               &doMTU                  },
    { ST_CONNECTED,          EV_MTU_FAIL,            &isMoreThanMaxMTUtries,                 ST_CONNECTED,               &doDisconnect           },
    { ST_CONNECTED,          EV_DISCONNECTED,        &didNotSelfDisconnect,                  ST_INIT,                    &reportDisConnect       },
    { ST_MTU_NEGOTIATED,     EV_AUTH_SUCCESS,        NULL,                                   ST_AUTHENTICATED,           &doTransaction          },
    { ST_MTU_NEGOTIATED,     EV_AUTH_FAIL,           &currentyLessThanMaxAuthTries,          ST_MTU_NEGOTIATED,          &doAuth                 },
    { ST_MTU_NEGOTIATED,     EV_AUTH_FAIL,           &isMoreThanMaxAuthTries,                ST_MTU_NEGOTIATED,          &doDisconnect           },
    { ST_MTU_NEGOTIATED,     EV_DISCONNECTED,        &didNotSelfDisconnect,                  ST_INIT,                    &reportDisConnect       },
    { ST_AUTHENTICATED,      EV_TX_SUCCESS,          NULL,                                   ST_AUTHENTICATED,           &doDisconnect           },
    { ST_AUTHENTICATED,      EV_TX_FAIL,             &currentlyLessThanMaxTXtries,           ST_AUTHENTICATED,           &doTransaction          },
    { ST_AUTHENTICATED,      EV_TX_FAIL,             &isMoreThanMaxTXtries,                  ST_AUTHENTICATED,           &doDisconnect           },
    { ST_AUTHENTICATED,      EV_DISCONNECTED,        &didNotSelfDisconnect,                  ST_INIT,                    &reportDisConnect       },
    { ST_ANY,                EV_DISCON_FAILED,       NULL,                                   ST_DONT_CHANGE,             &doDisconnect           },
    { ST_ANY,                EV_ANY,                 NULL,                                   ST_UNINIT,                  &fsmError               }    // Wildcard state for errors
};

#define GST_COUNT (sizeof(GST)/sizeof(stateTable_t))

int main()
{
    int ret = 0;
    fsmData_t dataAttr;
    dataAttr.connectTries = 0;
    dataAttr.MTUtries     = 0;
    dataAttr.authTries    = 0;
    dataAttr.txTries      = 0;

    fsm_t lfsm;
    memset(&lfsm, 0, sizeof(fsm_t));
    lfsm.pStateTable       = GST;
    lfsm.numStates         = GST_COUNT;
    lfsm.currentState      = ST_UNINIT;
    lfsm.currentEvent.name = EV_ANY;
    lfsm.fsmData           = &dataAttr;

    struct mq_attr attr;
    attr.mq_maxmsg = 30;
    attr.mq_msgsize = sizeof(fsmEvent_t);

    // Dev info
    //printf("Size of fsmEvent_t [%ld]\n", sizeof(fsmEvent_t));

    ret = mq_unlink("/abcmq");
    if (ret == -1) {
        fprintf(stderr, "Error on mq_unlink(), errno[%d] strerror[%s]\n",
                errno, strerror(errno));
    }

    lfsm.mqdes = mq_open("/abcmq", O_CREAT | O_RDWR, S_IWUSR | S_IRUSR, &attr);
    if (lfsm.mqdes == (mqd_t)-1) {
        fprintf(stderr, "Error on mq_open(), errno[%d] strerror[%s]\n",
                errno, strerror(errno));
        return -1;
    }

    doInit(&lfsm);  // This will generate the first event
    runStateMachine(&lfsm);

    return 0;
}


static void runStateMachine(fsm_t *fsm)
{
    int ret = 0;

    if (fsm == NULL) {
        fprintf(stderr, "[%s] NULL argument\n", __func__);
        return;
    }

    // Cycle through the state machine
    while (fsm->currentState != ST_TERM) {
        printf("current state [");
        printState(fsm->currentState);
        printf("]\n");

        ret = waitForEvent(fsm);
        if (ret == 0) {
            printf("got event [");
            printEvent(fsm->currentEvent.name);
            printf("]\n");

            runEvent(fsm);
        }
        sleep(2);
    }
}


static int waitForEvent(fsm_t *fsm)
{
    //const int numFds = 2;
    const int numFds = 1;
    struct pollfd fds[numFds];
    int timeout_msecs = -1; // -1 is forever
    int ret = 0;
    int i = 0;
    ssize_t num = 0;
    fsmEvent_t newEv;

    if (fsm == NULL) {
        fprintf(stderr, "[%s] NULL argument\n", __func__);
        return -1;
    }

    fsm->currentEvent.name = EV_ANY;

    fds[0].fd     = fsm->mqdes;
    fds[0].events = POLLIN;
    //fds[1].fd     = fsm->master_cmd_mqdes;
    //fds[1].events = POLLIN;
    ret = poll(fds, numFds, timeout_msecs);

    if (ret > 0) {
        // An event on one of the fds has occurred
        for (i = 0; i < numFds; i++) {
            if (fds[i].revents & POLLIN) {
                // Data may be read on device number i
                num = mq_receive(fds[i].fd, (void *)(&newEv),
                                 sizeof(fsmEvent_t), NULL);
                if (num == -1) {
                    fprintf(stderr, "Error on mq_receive(), errno[%d] "
                            "strerror[%s]\n", errno, strerror(errno));
                    return -1;
                }

                if (newEventIsValid(&newEv)) {
                    fsm->currentEvent = newEv;
                } else {
                    return -1;
                }
            }
        }
    } else {
        fprintf(stderr, "Error on poll(), ret[%d] errno[%d] strerror[%s]\n",
                ret, errno, strerror(errno));
        return -1;
    }

    return 0;
}


static int newEventIsValid(fsmEvent_t *event)
{
    if (event == NULL) {
        fprintf(stderr, "[%s] NULL argument\n", __func__);
        return FALSE;
    }

    printf("[%s]\n", __func__);

    struct timeval now;
    getTime(&now);

    if ( (event->name < EV_LAST_ENTRY) &&
         ((now.tv_sec - event->genTime.tv_sec) < (60*5))
       )
    {
        return TRUE;
    } else {
        return FALSE;
    }
}


//------------------------------------------------
// Performs event handling on the FSM (finite state machine).
// Make sure there is a wildcard state at the end of
// your table, otherwise; the event will be ignored.
//------------------------------------------------
static void runEvent(fsm_t *fsm)
{
    int i;
    int condRet = 0;

    if (fsm == NULL) {
        fprintf(stderr, "[%s] NULL argument\n", __func__);
        return;
    }

    printf("[%s]\n", __func__);

    // Find a relevant entry for this state and event
    for (i = 0; i < fsm->numStates; i++) {
        // Look in the table for our current state or ST_ANY
        if (  (fsm->pStateTable[i].st == fsm->currentState) ||
              (fsm->pStateTable[i].st == ST_ANY)
           )
        {
            // Is this the event we are looking for?
            if ( (fsm->pStateTable[i].evName == fsm->currentEvent.name) ||
                 (fsm->pStateTable[i].evName == EV_ANY)
               )
            {
                if (fsm->pStateTable[i].conditionfn != NULL) {
                    condRet = fsm->pStateTable[i].conditionfn(fsm->fsmData);
                }

                // See if there is a condition associated
                // or we are not looking for any condition
                //
                if ( (condRet != 0) || (fsm->pStateTable[i].conditionfn == NULL))
                {
                    // Set the next state (if applicable)
                    if (fsm->pStateTable[i].nextState != ST_DONT_CHANGE) {
                        fsm->currentState = fsm->pStateTable[i].nextState;
                        printf("new state [");
                        printState(fsm->currentState);
                        printf("]\n");
                    }

                    // Call the state callback function
                    fsm->pStateTable[i].fn(fsm);
                    break;
                }
            }
        }
    }
}


//------------------------------------------------
//               EVENT HANDLERS
//------------------------------------------------
static void getTime(struct timeval *time)
{
    if (time == NULL) {
        fprintf(stderr, "[%s] NULL argument\n", __func__);
        return;
    }

    printf("[%s]\n", __func__);

    int ret = gettimeofday(time, NULL);
    if (ret != 0) {
        fprintf(stderr, "gettimeofday() failed: errno [%d], strerror [%s]\n",
                errno, strerror(errno));
        memset(time, 0, sizeof(struct timeval));
    }
}


static void addev (fsm_t *fsm, fsmEvName_t ev)
{
    int ret = 0;

    if (fsm == NULL) {
        fprintf(stderr, "[%s] NULL argument\n", __func__);
        return;
    }

    printf("[%s] ev[%d]\n", __func__, ev);

    if (ev == EV_ANY) {
        // Don't generate a new event, just return...
        return;
    }

    fsmEvent_t newev;
    getTime(&(newev.genTime));
    newev.name = ev;

    ret = mq_send(fsm->mqdes, (void *)(&newev), sizeof(fsmEvent_t), FSM_PRIO);
    if (ret == -1) {
        fprintf(stderr, "[%s] mq_send() failed: errno [%d], strerror [%s]\n",
                __func__, errno, strerror(errno));
    }
}
//------------------------------------------------
//           end EVENT HANDLERS
//------------------------------------------------

void printState(fsmState_t st)
{
    switch(st) {
        case    ST_UNKNOWN:
        printf("ST_UNKNOWN");
            break;
        case    ST_UNINIT:
        printf("ST_UNINIT");
            break;
        case    ST_INIT:
        printf("ST_INIT");
            break;
        case    ST_CONNECTED:
        printf("ST_CONNECTED");
            break;
        case    ST_MTU_NEGOTIATED:
        printf("ST_MTU_NEGOTIATED");
            break;
        case    ST_AUTHENTICATED:
        printf("ST_AUTHENTICATED");
            break;
        case    ST_ERROR:
        printf("ST_ERROR");
            break;
        case    ST_TERM:
        printf("ST_TERM");
            break;
        default:
        printf("unknown state");
            break;
    }
}

void printEvent(fsmEvName_t ev)
{
    switch (ev) {
        case    EV_UNKNOWN:
        printf("EV_UNKNOWN");
            break;
        case    EV_INIT_SUCCESS:
        printf("EV_INIT_SUCCESS");
            break;
        case    EV_INIT_FAIL:
        printf("EV_INIT_FAIL");
            break;
        case    EV_MASTER_CMD_MSG:
        printf("EV_MASTER_CMD_MSG");
            break;
        case    EV_CONNECT_SUCCESS:
        printf("EV_CONNECT_SUCCESS");
            break;
        case    EV_CONNECT_FAIL:
        printf("EV_CONNECT_FAIL");
            break;
        case    EV_MTU_SUCCESS:
        printf("EV_MTU_SUCCESS");
            break;
        case    EV_MTU_FAIL:
        printf("EV_MTU_FAIL");
            break;
        case    EV_AUTH_SUCCESS:
        printf("EV_AUTH_SUCCESS");
            break;
        case    EV_AUTH_FAIL:
        printf("EV_AUTH_FAIL");
            break;
        case    EV_TX_SUCCESS:
        printf("EV_TX_SUCCESS");
            break;
        case    EV_TX_FAIL:
        printf("EV_TX_FAIL");
            break;
        case    EV_DISCONNECTED:
        printf("EV_DISCONNECTED");
            break;
        case    EV_LAST_ENTRY:
        printf("EV_LAST_ENTRY");
            break;
        default:
        printf("unknown event");
            break;
    }
}

Makefile

CXX = gcc
COMPFLAGS = -c -Wall -g

state_machine: state_machine.o
    $(CXX) -lrt state_machine.o -o state_machine

state_machine.o: state_machine.c
    $(CXX) $(COMPFLAGS) state_machine.c

clean:
    rm state_machine state_machine.o

1

Ваше запитання досить загальне.
Ось дві довідкові статті, які можуть бути корисними,

  1. Вбудована державна машина

    У цій статті описаний простий підхід до реалізації державної машини для вбудованої системи. Для цілей цієї статті стан машини визначається як алгоритм, який може знаходитися в одному з невеликої кількості станів. Стан - це умова, яка викликає встановлене співвідношення входів до виходів та входів до наступних станів.
    Кмітливий читач швидко відзначить, що описані в цій статті державні машини є машинами Mealy. Машина Мелі - це станкова машина, де виходи є функцією як існуючого стану, так і входу, на відміну від машини Мура, в якій виходи є лише функцією стану.

    • Кодування державних машин в C і C ++

      Моя зайнятість у цій статті полягає в основах стан-машина та деяких простих вказівках щодо програмування кодування державних машин на C або C ++. Я сподіваюся, що ці прості методи можуть стати більш поширеними, щоб ви (та інші) могли легко бачити структуру стан-машина прямо з вихідного коду.



1

Це стара публікація з великою кількістю відповідей, але я думав, що я додам власний підхід до машини з кінцевими станами в C. Я створив сценарій Python для створення коду С скелета для будь-якої кількості станів. Цей сценарій задокументований на GituHub на FsmTemplateC

Цей приклад ґрунтується на інших підходах, про які я читав. Він не використовує оператори goto або switch, але замість цього має функції переходу в матриці покажчика (таблиця пошуку). Код покладається на великий макрос багаторядковий ініціалізатор та функції C99 (призначені ініціалізатори та складні літерали), тому якщо вам не подобаються ці речі, такий підхід може не сподобатися.

Ось сценарій Python з турнікетного прикладу, який генерує скелет C-коду за допомогою FsmTemplateC :

# dict parameter for generating FSM
fsm_param = {
    # main FSM struct type string
    'type': 'FsmTurnstile',
    # struct type and name for passing data to state machine functions
    # by pointer (these custom names are optional)
    'fopts': {
        'type': 'FsmTurnstileFopts',
        'name': 'fopts'
    },
    # list of states
    'states': ['locked', 'unlocked'],
    # list of inputs (can be any length > 0)
    'inputs': ['coin', 'push'],
    # map inputs to commands (next desired state) using a transition table
    # index of array corresponds to 'inputs' array
    # for this example, index 0 is 'coin', index 1 is 'push'
    'transitiontable': {
        # current state |  'coin'  |  'push'  |
        'locked':       ['unlocked',        ''],
        'unlocked':     [        '',  'locked']
    }
}

# folder to contain generated code
folder = 'turnstile_example'
# function prefix
prefix = 'fsm_turnstile'

# generate FSM code
code = fsm.Fsm(fsm_param).genccode(folder, prefix)

Сгенерований заголовок виводу містить typedefs:

/* function options (EDIT) */
typedef struct FsmTurnstileFopts {
    /* define your options struct here */
} FsmTurnstileFopts;

/* transition check */
typedef enum eFsmTurnstileCheck {
    EFSM_TURNSTILE_TR_RETREAT,
    EFSM_TURNSTILE_TR_ADVANCE,
    EFSM_TURNSTILE_TR_CONTINUE,
    EFSM_TURNSTILE_TR_BADINPUT
} eFsmTurnstileCheck;

/* states (enum) */
typedef enum eFsmTurnstileState {
    EFSM_TURNSTILE_ST_LOCKED,
    EFSM_TURNSTILE_ST_UNLOCKED,
    EFSM_TURNSTILE_NUM_STATES
} eFsmTurnstileState;

/* inputs (enum) */
typedef enum eFsmTurnstileInput {
    EFSM_TURNSTILE_IN_COIN,
    EFSM_TURNSTILE_IN_PUSH,
    EFSM_TURNSTILE_NUM_INPUTS,
    EFSM_TURNSTILE_NOINPUT
} eFsmTurnstileInput;

/* finite state machine struct */
typedef struct FsmTurnstile {
    eFsmTurnstileInput input;
    eFsmTurnstileCheck check;
    eFsmTurnstileState cur;
    eFsmTurnstileState cmd;
    eFsmTurnstileState **transition_table;
    void (***state_transitions)(struct FsmTurnstile *, FsmTurnstileFopts *);
    void (*run)(struct FsmTurnstile *, FsmTurnstileFopts *, const eFsmTurnstileInput);
} FsmTurnstile;

/* transition functions */
typedef void (*pFsmTurnstileStateTransitions)(struct FsmTurnstile *, FsmTurnstileFopts *);
  • enum eFsmTurnstileCheckвикористовується для визначення того, чи був перехід заблокований EFSM_TURNSTILE_TR_RETREAT, чи дозволяв йому переходити EFSM_TURNSTILE_TR_ADVANCE, або виклику функції не передував перехід ізEFSM_TURNSTILE_TR_CONTINUE .
  • enum eFsmTurnstileState- це просто список держав.
  • enum eFsmTurnstileInput- це просто список входів.
  • FsmTurnstileСтруктура є серцем державної машини з перевіркою переходу, функція пошуку таблицею, поточним станом, заданим станом, і псевдонімом до основної функції , яка запускає машину.
  • Кожен функціональний вказівник (псевдонім) у виклику FsmTurnstileповинен бути викликаний тільки з структури та повинен мати свій перший вхід як вказівник на себе, щоб підтримувати стійкий стан, об'єктно-орієнтований стиль.

Тепер для декларацій функції в заголовку:

/* fsm declarations */
void fsm_turnstile_locked_locked (FsmTurnstile *fsm, FsmTurnstileFopts *fopts);
void fsm_turnstile_locked_unlocked (FsmTurnstile *fsm, FsmTurnstileFopts *fopts);
void fsm_turnstile_unlocked_locked (FsmTurnstile *fsm, FsmTurnstileFopts *fopts);
void fsm_turnstile_unlocked_unlocked (FsmTurnstile *fsm, FsmTurnstileFopts *fopts);
void fsm_turnstile_run (FsmTurnstile *fsm, FsmTurnstileFopts *fopts, const eFsmTurnstileInput input);

Імена функцій у форматі {prefix}_{from}_{to}, де {from}попередній (поточний) стан і {to}наступний стан. Зверніть увагу, що якщо таблиця переходів не дозволяє певні переходи, замість функції вказівника буде встановлено покажчик NULL. Нарешті, магія відбувається з макросом. Тут ми будуємо таблицю переходу (матриця перерахунків стану), а функції переходу стану шукають таблицю (матрицю покажчиків функцій):

/* creation macro */
#define FSM_TURNSTILE_CREATE() \
{ \
    .input = EFSM_TURNSTILE_NOINPUT, \
    .check = EFSM_TURNSTILE_TR_CONTINUE, \
    .cur = EFSM_TURNSTILE_ST_LOCKED, \
    .cmd = EFSM_TURNSTILE_ST_LOCKED, \
    .transition_table = (eFsmTurnstileState * [EFSM_TURNSTILE_NUM_STATES]) { \
        (eFsmTurnstileState [EFSM_TURNSTILE_NUM_INPUTS]) { \
            EFSM_TURNSTILE_ST_UNLOCKED, \
            EFSM_TURNSTILE_ST_LOCKED \
        }, \
        (eFsmTurnstileState [EFSM_TURNSTILE_NUM_INPUTS]) { \
            EFSM_TURNSTILE_ST_UNLOCKED, \
            EFSM_TURNSTILE_ST_LOCKED \
        } \
    }, \
    .state_transitions = (pFsmTurnstileStateTransitions * [EFSM_TURNSTILE_NUM_STATES]) { \
        (pFsmTurnstileStateTransitions [EFSM_TURNSTILE_NUM_STATES]) { \
            fsm_turnstile_locked_locked, \
            fsm_turnstile_locked_unlocked \
        }, \
        (pFsmTurnstileStateTransitions [EFSM_TURNSTILE_NUM_STATES]) { \
            fsm_turnstile_unlocked_locked, \
            fsm_turnstile_unlocked_unlocked \
        } \
    }, \
    .run = fsm_turnstile_run \
}

Під час створення FSM макрос FSM_EXAMPLE_CREATE()повинен бути використаний.

Тепер у вихідному коді повинна бути заповнена кожна заявлена ​​вище функція переходу стану. FsmTurnstileFoptsСтруктура може бути використана для передачі даних в / з автомата. Кожен перехід повинен fsm->checkбути рівним або EFSM_EXAMPLE_TR_RETREATблокувати його переходу, або EFSM_EXAMPLE_TR_ADVANCEдозволити йому перейти до командованого стану. Робочий приклад можна знайти на веб-сайті (FsmTemplateC) [ https://github.com/ChisholmKyle/FsmTemplateC] .

Ось дуже просте фактичне використання у вашому коді:

/* create fsm */
FsmTurnstile fsm = FSM_TURNSTILE_CREATE();
/* create fopts */
FsmTurnstileFopts fopts = {
    .msg = ""
};
/* initialize input */
eFsmTurnstileInput input = EFSM_TURNSTILE_NOINPUT;

/* main loop */
for (;;) {
    /* wait for timer signal, inputs, interrupts, whatever */
    /* optionally set the input (my_input = EFSM_TURNSTILE_IN_PUSH for example) */
    /* run state machine */
    my_fsm.run(&my_fsm, &my_fopts, my_input);
}

Увесь той головний бізнес та всі ці функції просто для того, щоб мати простий та швидкий інтерфейс, варто подумати про це.


0

Ви можете використовувати бібліотеку з відкритим кодом OpenFST .

OpenFst - це бібліотека для побудови, комбінування, оптимізації та пошуку зважених перетворювачів з кінцевим станом (FST). Зважені перетворювачі з кінцевим станом - це автомати, де кожен перехід має вхідну мітку, вихідну мітку та вагу. Більш звичний акцептор кінцевого стану представлений у вигляді перетворювача, кожен вхід і вихід мітки переходу рівні. Приймачі кінцевих станів використовуються для представлення наборів рядків (конкретно, регулярних чи раціональних множин); перетворювачі з кінцевим станом використовуються для представлення бінарних зв’язків між парами струн (конкретно, раціональними перетвореннями). Ваги можуть бути використані для відображення витрат на певний перехід.


0
void (* StateController)(void); 
void state1(void);
void state2(void);

void main()
{
 StateController=&state1;
 while(1)
 {
  (* StateController)();
 }
}

void state1(void)
{
 //do something in state1
 StateController=&state2;
}

void state2(void)
{
 //do something in state2
 //Keep changing function direction based on state transition
 StateController=&state1;
}

Ви можете додатково оптимізувати його для безпеки, використовуючи масив постійних функціональних покажчиків на функції
AlphaGoku

0

Я особисто використовую структури самовідсилань у поєднанні з масивами вказівників. Я завантажив підручник на github, а посилання:

https://github.com/mmelchger/polling_state_machine_c

Примітка. Я розумію, що цей потік досить старий, але я сподіваюся отримати внесок і думки щодо дизайну машини-машини, а також зможу надати приклад для можливого дизайну стан-машини в C.


0

Ви можете вважати UML-state-machine-in-c , "легкою" рамкою машинної машини в C. Я написав цю рамку для підтримки як машин Кінцевого стану, так і Ієрархічної машини . Порівняно з таблицями стану або простими випадками перемикання, рамковий підхід є більш масштабним. Він може бути використаний для простих машин з кінцевим станом до складних ієрархічних машин.

Державна машина представлена state_machine_tструктурою. Він містить лише двох учасників "Подія" та вказівник на "state_t".

struct state_machine_t
{
   uint32_t Event;          //!< Pending Event for state machine
   const state_t* State;    //!< State of state machine.
};

state_machine_tповинен бути першим членом вашої державної машини. напр

struct user_state_machine
{
  state_machine_t Machine;    // Base state machine. Must be the first member of user derived state machine.

  // User specific state machine members
  uint32_t param1;
  uint32_t param2;
  ...
};

state_t містить обробник для стану, а також необов'язкові обробники для входу та виходу.

//! finite state structure
struct finite_state{
  state_handler Handler;      //!< State handler to handle event of the state
  state_handler Entry;        //!< Entry action for state
  state_handler Exit;          //!< Exit action for state.
};

Якщо рамка налаштована для ієрархічної машини стану, то state_t містить вказівник на стан батьків і дочірніх.

Framework надає API dispatch_eventдля відправки події на стан машини та switch_stateдля запуску переходу стану.

Детальніше про те, як реалізувати ієрархічну машину стану, див. У GitHub сховищі .

приклади коду,

https://github.com/kiishor/UML-State-Machine-in-C/blob/master/demo/simple_state_machine/readme.md https://github.com/kiishor/UML-State-Machine-in-C /blob/master/demo/simple_state_machine_enhanced/readme.md


-1

Ось метод для машини машини, що використовує макроси, такі, що кожна функція може мати власний набір станів: https://www.codeproject.com/Articles/37037/Macros-to-simulate-multi-tasking-blocking-code -на

Він має назву "імітувати багатозадачні завдання", але це не єдине використання.

Цей метод використовує зворотні дзвінки для підбору кожної функції, де вона вимкнена. Кожна функція містить список станів, унікальних для кожної функції. Центральний "цикл холостого ходу" використовується для запуску державних машин. "Цикл простою" не має уявлення про те, як працюють державні машини, саме окремі функції "знають, що робити". Для того, щоб написати код для функцій, потрібно просто створити список станів і використовувати макроси для "призупинення" та "відновлення". Ці макроси я використовував у Cisco, коли писав бібліотеку приймачів для перемикача Nexus 7000.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.