Tworzę mały projekt w mieszanym C i C ++. Buduję jedną małą maszynę państwową w sercu jednego z moich wątków roboczych.
Zastanawiałem się, czy guru z SO podzieliliby się swoimi technikami projektowania automatu stanów.
UWAGA: Jestem przede wszystkim po wypróbowanych i przetestowanych technikach wdrażania.
ZAKTUALIZOWANO: W oparciu o wszystkie wspaniałe informacje zebrane w SO, zdecydowałem się na tę architekturę:
c++
c
architecture
state-machine
Jldupont
źródło
źródło
Odpowiedzi:
Maszyny stanowe, które wcześniej projektowałem (C, nie C ++) sprowadzają się do
struct
tablicy i pętli. Struktura składa się zasadniczo ze stanu i zdarzenia (do wyszukiwania) oraz funkcji, która zwraca nowy stan, na przykład:Następnie definiujesz swoje stany i zdarzenia za pomocą prostych definicji (
ANY
te są specjalnymi znacznikami, patrz poniżej):Następnie definiujesz wszystkie funkcje wywoływane przez przejścia:
Wszystkie te funkcje są napisane, aby nie pobierać zmiennych i zwracać nowy stan dla automatu stanów. W tym przykładzie zmienne globalne są używane do przekazywania dowolnych informacji do funkcji stanu w razie potrzeby.
Używanie globałów nie jest tak złe, jak się wydaje, ponieważ FSM jest zwykle zamknięty w jednej jednostce kompilacji, a wszystkie zmienne są statyczne dla tej jednostki (dlatego użyłem cudzysłowów wokół „globalnej” powyżej - są one bardziej wspólne w obrębie FSM, niż prawdziwie globalny). Jak w przypadku wszystkich globaliów, wymaga opieki.
Tablica przejść definiuje następnie wszystkie możliwe przejścia i funkcje, które są wywoływane dla tych przejść (w tym ostatnia opcja catch-all):
Oznacza to, że jeśli jesteś w
ST_INIT
stanie i odbieraszEV_KEYPRESS
wydarzenie, zadzwoń pod numerGotKey
.Działanie FSM staje się wówczas względnie prostą pętlą:
Jak wspomniano powyżej, zwróć uwagę na użycie
ST_ANY
jako symboli wieloznacznych, pozwalających zdarzeniu na wywołanie funkcji bez względu na aktualny stan.EV_ANY
działa również podobnie, pozwalając dowolnemu zdarzeniu w określonym stanie wywołać funkcję.Może także zagwarantować, że po osiągnięciu końca tablicy przejść pojawi się błąd informujący, że FSM nie został poprawnie zbudowany (za pomocą
ST_ANY/EV_ANY
kombinacji.Użyłem podobnego kodu w wielu projektach komunikacyjnych, takich jak wczesna implementacja stosów komunikacyjnych i protokołów dla systemów wbudowanych. Dużą zaletą była jego prostota i względna łatwość zmiany tablicy przejść.
Nie mam wątpliwości, że będą abstrakcje wyższego poziomu, które mogą być bardziej odpowiednie w dzisiejszych czasach, ale podejrzewam, że wszystkie sprowadzą się do tego samego rodzaju struktury.
I, jak
ldog
stwierdzono w komentarzu, można całkowicie uniknąć globałów, przekazując wskaźnik struktury do wszystkich funkcji (i używając go w pętli zdarzeń). Umożliwi to uruchamianie wielu maszyn stanowych obok siebie bez zakłóceń.Wystarczy utworzyć typ struktury, który przechowuje dane specyficzne dla maszyny (stan na absolutnym minimum) i użyć tego zamiast globali.
Powodem, dla którego rzadko to robię, jest po prostu dlatego, że większość automatów stanowych, które napisałem, były typami singletonowymi (na przykład jednorazowe uruchomienie na początku procesu, odczyt plików konfiguracyjnych) i nie trzeba uruchamiać więcej niż jednej instancji . Ale ma wartość, jeśli chcesz uruchomić więcej niż jeden.
źródło
int (*fn)(void*);
tam gdzievoid*
jest wskaźnik do danych, że każda funkcja państwo pobiera jako parametr. Następnie funkcje stanu mogą albo użyć danych, albo je zignorować.Inne odpowiedzi są dobre, ale bardzo „lekka” implementacja, której użyłem, gdy automat stanowy jest bardzo prosty, wygląda następująco:
Użyłbym tego, gdy automat stanowy jest na tyle prosty, że wskaźnik funkcji i podejście do tabeli przejścia stanu jest nadmierne. Jest to często przydatne do analizowania znak po znaku lub słowo po słowie.
źródło
Wybaczcie mi, że złamałem każdą zasadę w informatyce, ale automat stanowy jest jednym z niewielu (mogę policzyć tylko dwa z ręki) miejsc, w których
goto
instrukcja jest nie tylko bardziej wydajna, ale także sprawia, że kod jest czystszy i łatwiejszy do odczytania. Ponieważgoto
oświadczenia oparte są na etykietach, możesz nazwać swoje stany zamiast śledzić bałagan liczb lub użyć wyliczenia. Powoduje to również, że kod jest znacznie bardziej przejrzysty, ponieważ nie potrzebujesz wszystkich dodatkowych wskaźników wskaźników funkcji lub dużych instrukcji switch i pętli while. Czy wspominałem, że jest też bardziej wydajny?Oto jak może wyglądać automat stanowy:
Masz ogólny pomysł. Chodzi o to, że można efektywnie zaimplementować maszynę stanu, która jest stosunkowo łatwa do odczytania i krzyczy do czytelnika, że patrzą na maszynę stanu. Pamiętaj, że jeśli używasz
goto
instrukcji, musisz zachować ostrożność, ponieważ bardzo łatwo jest w ten sposób zastrzelić się w stopę.źródło
Możesz rozważyć State Machine Compiler http://smc.sourceforge.net/
To wspaniałe narzędzie typu open source akceptuje opis automatu stanowego w prostym języku i kompiluje go do jednego z kilkunastu języków - w tym C i C ++. Samo narzędzie jest napisane w Javie i może być włączone jako część kompilacji.
Powodem tego jest, zamiast ręcznego kodowania przy użyciu wzorca stanu GoF lub innego podejścia, że po wyrażeniu maszyny stanu jako kodu, podstawowa struktura ma tendencję do znikania pod ciężarem płyty kotłowej, którą należy wygenerować w celu jej obsługi. Korzystanie z tego podejścia zapewnia doskonały rozdział problemów, a struktura maszyny stanowej jest „widoczna”. Automatycznie wygenerowany kod przechodzi do modułów, których nie trzeba dotykać, dzięki czemu można cofać się i bawić ze strukturą automatu stanów bez wpływu na napisany kod pomocniczy.
Przepraszam, jestem zbyt entuzjastyczny i niewątpliwie odkładam wszystkich na później. Jest to jednak narzędzie na najwyższym poziomie i dobrze udokumentowane.
źródło
Koniecznie sprawdź pracę Miro Samka (blog State Space , witryna State Machines & Tools ), której artykuły w C / C ++ Users Journal były świetne.
Witryna zawiera pełną implementację (C / C ++) zarówno w licencji open source, jak i komercyjnej frameworka stanowego (QP Framework) , procedury obsługi zdarzeń (QEP) , podstawowego narzędzia do modelowania (QM) i narzędzia śledzenia (QSpy), które pozwalają rysować maszyny stanów, tworzyć kod i debugować je.
Książka zawiera obszerne wyjaśnienie co / dlaczego implementacji i jak z niej korzystać, a także jest świetnym materiałem do zrozumienia podstaw hierachicznych i skończonych maszyn stanów.
Witryna zawiera również łącza do kilku pakietów obsługi płyt głównych do użytku z oprogramowaniem z wbudowanymi platformami.
źródło
Zrobiłem coś podobnego do tego, co opisuje paxdiablo, ale zamiast tablicy przejść stanu / zdarzenia ustawiłem dwuwymiarową tablicę wskaźników funkcji, z wartością zdarzenia jako indeksem jednej osi, a bieżącą wartością stanu jako inny. Potem dzwonię
state = state_table[event][state](params)
i dzieje się właściwa rzecz. Komórki reprezentujące nieprawidłowe kombinacje stanu / zdarzenia otrzymują oczywiście wskaźnik do funkcji, która tak mówi.Oczywiście działa to tylko wtedy, gdy wartości stanu i zdarzenia są ciągłymi zakresami i zaczynają się od 0 lub wystarczająco blisko.
źródło
#define STATE_LIST() \STATE_LIST_ENTRY(state1)\STATE_LIST_ENTRY(state2)\...
(implikowany nowy wiersz po każdym\
), w którym (ponownie) definiujesz makro wprowadzania, gdy używasz makra STATE_LIST. Przykład - co tablicę stanu nazwy:#define STATE_LIST_ENTRY(s) #s , \n const char *state_names[] = { STATE_LIST() };\n #undef STATE_LIST_ENTRY
. Niektóre pracują, aby skonfigurować jako pierwsze, ale jest to niezwykle potężne. Dodaj nowy stan -> gwarantowane bez błędów.Stefan Heinzmann w swoim artykule podał bardzo ładny „szkielet” maszyny stanu C ++ oparty na szablonie .
Ponieważ w artykule nie ma linku do pełnego pobrania kodu, mogłem wkleić kod do projektu i sprawdzić go. Poniższe rzeczy są testowane i obejmują kilka drobnych, ale prawie oczywistych brakujących elementów.
Główną innowacją jest to, że kompilator generuje bardzo wydajny kod. Puste działania wejścia / wyjścia nie wiążą się z żadnymi kosztami. Wstawiane są niepuste operacje wejścia / wyjścia. Kompilator sprawdza również kompletność wykresu statystycznego. Brakujące działania generują błędy łączenia. Jedyne, czego nie złapano, to brak
Top::init
.Jest to bardzo fajna alternatywa dla implementacji Miro Samka, jeśli możesz żyć bez tego, czego brakuje - jest to dalekie od pełnej implementacji Statechart UML, chociaż poprawnie implementuje semantykę UML, podczas gdy kod Samka według projektu nie obsługuje wyjścia / przejścia / działania wejściowe we właściwej kolejności.
Jeśli ten kod działa zgodnie z tym, co musisz zrobić, i masz przyzwoity kompilator C ++ dla swojego systemu, prawdopodobnie będzie on działał lepiej niż implementacja Miro C / C ++. Kompilator generuje dla Ciebie spłaszczoną implementację maszyny stanu przejściowego O (1). Jeśli audyt danych wyjściowych zespołu potwierdzi, że optymalizacje działają zgodnie z oczekiwaniami, zbliżasz się do wydajności teoretycznej. Najlepsza część: to stosunkowo niewielki, łatwy do zrozumienia kod.
Kod testowy następuje.
źródło
Techniką, którą lubię dla maszyn stanów (przynajmniej tych do sterowania programem) jest używanie wskaźników funkcji. Każdy stan jest reprezentowany przez inną funkcję. Funkcja przyjmuje symbol wejściowy i zwraca wskaźnik funkcji do następnego stanu. Centralne monitory pętli dyspozytorskiej pobierają kolejne dane wejściowe, przekazują je do bieżącego stanu i przetwarzają wynik.
Pisanie na nim staje się trochę dziwne, ponieważ C nie ma sposobu na wskazanie typów wskaźników funkcji, które same się zwracają, więc funkcje stanu wracają
void*
. Ale możesz zrobić coś takiego:Następnie poszczególne funkcje stanu mogą włączyć dane wejściowe do przetworzenia i zwrócić odpowiednią wartość.
źródło
Najprostszy przypadek
Punkty: stan jest prywatny, nie tylko dla jednostki kompilującej, ale także dla modułu obsługi zdarzeń. Przypadki specjalne mogą być obsługiwane oddzielnie od głównego przełącznika przy użyciu dowolnej konstrukcji uznanej za niezbędną.
Bardziej złożona sprawa
Gdy przełącznik zapełni się więcej niż kilka ekranów, podziel go na funkcje obsługujące każdy stan, używając tabeli stanów do bezpośredniego wyszukiwania funkcji. Stan jest nadal prywatny dla modułu obsługi zdarzeń. Funkcje modułu obsługi stanu zwracają następny stan. W razie potrzeby niektóre zdarzenia mogą być nadal traktowane w głównym module obsługi zdarzeń. Lubię wrzucać pseudo-zdarzenia dla wejścia i wyjścia ze stanu i być może uruchomienie automatu stanów:
Nie jestem pewien, czy skompilowałem składnię, zwłaszcza jeśli chodzi o tablicę wskaźników funkcji. Nie uruchomiłem nic z tego przez kompilator. Po przejrzeniu zauważyłem, że zapomniałem jawnie odrzucić następny stan podczas obsługi pseudo zdarzeń (nawias (void) przed wywołaniem funkcji state_handler ()). To jest coś, co lubię robić, nawet jeśli kompilatory po cichu akceptują pominięcie. Mówi czytelnikom kodu, że „tak, naprawdę chciałem wywołać tę funkcję bez użycia wartości zwracanej”, i może powstrzymać ostrzeżenia przed narzędziami analizy statycznej. Może to być idiosynkratyczne, ponieważ nie przypominam sobie, by widział to ktoś inny.
Punkty: dodanie odrobiny złożoności (sprawdzenie, czy następny stan różni się od bieżącego), może uniknąć powielania kodu w innym miejscu, ponieważ funkcje obsługi stanu mogą cieszyć się pseudo zdarzeniami, które występują, gdy stan jest wprowadzany i opuszczany. Pamiętaj, że stan nie może się zmienić podczas obsługi pseudo zdarzeń, ponieważ wynik procedury obsługi stanu jest odrzucany po tych zdarzeniach. Możesz oczywiście zmodyfikować zachowanie.
Procedura obsługi stanu wyglądałaby tak:
Większa złożoność
Kiedy jednostka kompilacji staje się zbyt duża (cokolwiek ci się wydaje, powinienem powiedzieć około 1000 wierszy), umieść każdy moduł obsługi stanu w osobnym pliku. Gdy każdy moduł obsługi stanu będzie dłuższy niż kilka ekranów, podziel każde zdarzenie na osobną funkcję, podobnie do sposobu, w jaki przełącznik stanu został podzielony. Możesz to zrobić na wiele sposobów, niezależnie od stanu, używając wspólnej tabeli lub łącząc różne schematy. Niektóre z nich zostały tutaj objęte innymi. Posortuj tabele i użyj wyszukiwania binarnego, jeśli wymagana jest szybkość.
Programowanie ogólne
Chciałbym, aby preprocesor zajął się takimi problemami, jak sortowanie tabel, a nawet generowanie automatów stanów z opisów, umożliwiając „pisanie programów o programach”. Wierzę, że do tego ludzie Boost wykorzystują szablony C ++, ale uważam, że składnia jest tajemnicza.
Stoły dwuwymiarowe
W przeszłości używałem tabel stanu / zdarzeń, ale muszę powiedzieć, że w najprostszych przypadkach nie uważam ich za konieczne i wolę przejrzystość i czytelność instrukcji przełączania, nawet jeśli przekracza ona jeden ekran. W bardziej skomplikowanych przypadkach tabele szybko wymykają się spod kontroli, jak zauważyli inni. Idiomy, które tu przedstawiam, pozwalają dodawać mnóstwo zdarzeń i stanów, kiedy masz na to ochotę, bez konieczności utrzymywania tabeli zajmującej pamięć (nawet jeśli może to być pamięć programu).
Zrzeczenie się
Specjalne potrzeby mogą sprawić, że te idiomy będą mniej przydatne, ale uważam je za bardzo jasne i łatwe do utrzymania.
źródło
Niezwykle niesprawdzony, ale przyjemny w kodowaniu, teraz w bardziej dopracowanej wersji niż moja pierwotna odpowiedź; aktualne wersje można znaleźć na stronie mercurial.intuxication.org :
sm.h
przyklad.c
źródło
Naprawdę podobała mi się odpowiedź paxdiable i postanowiłem zaimplementować wszystkie brakujące funkcje mojej aplikacji, takie jak zmienne ochronne i dane specyficzne dla maszyny stanów.
Przesłałem moją implementację do tej witryny, aby udostępnić ją społeczności. Został przetestowany przy użyciu IAR Embedded Workbench dla ARM.
https://sourceforge.net/projects/compactfsm/
źródło
Innym interesującym narzędziem typu open source jest Yakindu Statechart Tools na statecharts.org . Wykorzystuje wykresy statyczne Harela, a tym samym zapewnia stany hierarchiczne i równoległe oraz generuje kod C i C ++ (a także Java). Nie korzysta z bibliotek, ale stosuje podejście „zwykłego kodu”. Kod zasadniczo stosuje struktury skrzynek rozdzielczych. Generatory kodu można również dostosować. Dodatkowo narzędzie zapewnia wiele innych funkcji.
źródło
Przybywając tak późno (jak zwykle), ale skanując dotychczasowe odpowiedzi, myślę, że czegoś ważnego brakuje;
W moich własnych projektach stwierdziłem, że bardzo pomocne może być nie posiadanie funkcji dla każdej prawidłowej kombinacji stanu / zdarzenia. Podoba mi się pomysł efektywnego posiadania dwuwymiarowej tabeli stanów / zdarzeń. Ale podoba mi się, że elementy tabeli są czymś więcej niż prostym wskaźnikiem funkcji. Zamiast tego staram się uporządkować mój projekt, tak aby w jego sercu było kilka prostych atomowych elementów lub akcji. W ten sposób mogę wymienić te proste elementy atomowe na każdym przecięciu mojej tabeli stanów / zdarzeń. Chodzi o to, że nie musisz definiować masy N kwadratowych (zwykle bardzo prostych) funkcji. Po co mieć coś tak podatnego na błędy, czasochłonne, trudne do napisania, trudne do odczytania?
Podaję także opcjonalny nowy stan i opcjonalny wskaźnik funkcji dla każdej komórki w tabeli. Wskaźnik funkcji występuje w tych wyjątkowych przypadkach, w których nie chcesz po prostu odpalać listy działań atomowych.
Wiesz, że robisz to dobrze, gdy możesz wyrazić wiele różnych funkcji, po prostu edytując tabelę, bez nowego kodu do napisania.
źródło
Alrght, myślę, że moje trochę różnią się od wszystkich innych. Nieco więcej separacji kodu i danych niż widzę w innych odpowiedziach. Naprawdę czytam teorię, aby to napisać, która implementuje pełny język regularny (niestety, bez wyrażeń regularnych). Ullman, Minsky, Chomsky. Nie mogę powiedzieć, że wszystko zrozumiałem, ale czerpałem ze starych mistrzów tak bezpośrednio, jak to możliwe: poprzez ich słowa.
Używam wskaźnika funkcji do predykatu, który określa przejście do stanu „tak” lub „nie”. Ułatwia to utworzenie akceptora stanu skończonego dla zwykłego języka, który programujesz w sposób bardziej podobny do języka asemblera. Proszę, nie zniechęcaj się moim głupim wyborem. „czek” == „czek”. 'grok' == [przejdź do słownika hakera].
Tak więc dla każdej iteracji czek wywołuje funkcję predykatu z argumentem bieżącego znaku. Jeśli predykat zwróci wartość true, znak zostanie zużyty (wskaźnik przesunie się) i wykonamy przejście „y”, aby wybrać następny stan. Jeśli predykat zwróci false, znak NIE zostanie zużyty, a my przejdziemy przez przejście „n”. Tak więc każda instrukcja jest dwukierunkową gałęzią! Musiałem wtedy czytać Historię Mela.
Ten kod pochodzi prosto z mojego interpretera postscriptowego i ewoluował do swojej obecnej postaci z dużą ilością wskazówek od kolegów na comp.lang.c. Ponieważ PostScript w zasadzie nie ma składni (wymaga tylko zrównoważonych nawiasów kwadratowych), taki parser działa również jako parser.
źródło
boost.org zawiera 2 różne implementacje wykresów stanu:
Jak zawsze, boost przeniesie Cię do piekła szablonu.
Pierwsza biblioteka jest przeznaczona dla bardziej krytycznych pod względem wydajności maszyn stanów. Druga biblioteka zapewnia bezpośrednią ścieżkę przejścia z wykresu statycznego UML do kodu.
Oto pytanie SO z prośbą o porównanie dwóch, w których obaj autorzy odpowiadają.
źródło
Ta seria postów Ars OpenForum o nieco skomplikowanej logice sterowania zawiera bardzo łatwą do wykonania implementację jako maszyna stanu w C.
źródło
Widziałem to gdzieś
źródło
goto
stwarza zależność od systemu operacyjnego z wielozadaniowością.Biorąc pod uwagę, że sugerujesz, że możesz używać C ++, a zatem i kodu OO, sugerowałbym ocenę wzorca stanu „GoF” (GoF = Gang of Four, faceci, którzy napisali książkę wzorców projektowych, która wprowadziła wzorce projektowe do centrum uwagi).
Nie jest to szczególnie skomplikowane i jest szeroko stosowane i omawiane, więc łatwo jest zobaczyć przykłady i wyjaśnienia w Internecie.
Prawdopodobnie będzie także rozpoznawalny przez każdego, kto utrzyma Twój kod w późniejszym terminie.
Jeśli zmartwieniem jest wydajność, warto faktycznie przeprowadzić analizę porównawczą, aby upewnić się, że podejście inne niż OO jest bardziej wydajne, ponieważ wiele czynników wpływa na wydajność i nie zawsze jest to po prostu zły OO, funkcjonalny kod dobry. Podobnie, jeśli użycie pamięci jest dla ciebie ograniczeniem, ponownie warto wykonać kilka testów lub obliczeń, aby sprawdzić, czy rzeczywiście będzie to problem dla konkretnej aplikacji, jeśli użyjesz wzorca stanu.
Oto niektóre linki do wzorca stanu „Gof”, jak sugeruje Craig:
źródło
Oto przykład maszyny stanu skończonego dla systemu Linux, która wykorzystuje zdarzenia jako kolejki komunikatów. Zdarzenia są umieszczane w kolejce i porządkowane. Stan zmienia się w zależności od tego, co dzieje się dla każdego zdarzenia.
To jest przykład połączenia danych ze stanami takimi jak:
Jedną dodatkową funkcją, którą dodałem, był znacznik czasu dla każdej wiadomości / zdarzenia. Program obsługi zdarzeń zignoruje zdarzenia, które są zbyt stare (wygasły). Może się to zdarzyć często w prawdziwym świecie, w którym możesz nieoczekiwanie utknąć w stanie.
Ten przykład działa na systemie Linux, użyj poniższego pliku Makefile, aby go skompilować i bawić się nim.
state_machine.c
Makefile
źródło
Twoje pytanie jest dość ogólne.
Oto dwa artykuły referencyjne, które mogą być przydatne,
Implementacja wbudowanej maszyny stanu
źródło
Z powodzeniem korzystałem z State Machine Compiler w projektach Java i Python.
źródło
To jest stary post z wieloma odpowiedziami, ale pomyślałem, że dodam własne podejście do skończonej maszyny stanów w C. Stworzyłem skrypt Pythona, aby wygenerować szkieletowy kod C dla dowolnej liczby stanów. Ten skrypt jest udokumentowany na GituHub na FsmTemplateC
Ten przykład opiera się na innych podejściach, o których czytałem. Nie używa instrukcji goto ani switch, ale zamiast tego ma funkcje przejścia w macierzy wskaźników (tablica przeglądowa). Kod opiera się na dużym wieloliniowym makrze inicjalizacyjnej i funkcjach C99 (wyznaczone inicjalizatory i literały złożone), więc jeśli nie lubisz tych rzeczy, może ci się to nie podobać.
Oto skrypt w Pythonie na przykładzie kołowrotu, który generuje szkielet C-code przy użyciu FsmTemplateC :
Wygenerowany nagłówek wyjściowy zawiera typedefs:
eFsmTurnstileCheck
służy do określenia, czy przejście zostało zablokowaneEFSM_TURNSTILE_TR_RETREAT
, dozwolone postępyEFSM_TURNSTILE_TR_ADVANCE
, czy wywołanie funkcji nie było poprzedzone przejściem zEFSM_TURNSTILE_TR_CONTINUE
.eFsmTurnstileState
to po prostu lista stanów.eFsmTurnstileInput
to po prostu lista danych wejściowych.FsmTurnstile
jest sercem automatu stanów z kontrolą przejścia, tabelą wyszukiwania funkcji, stanem bieżącym, stanem polecenia i aliasem do podstawowej funkcji, która uruchamia maszynę.FsmTurnstile
powinien być wywoływany tylko ze struktury i musi mieć swoje pierwsze wejście jako wskaźnik do siebie, aby utrzymać trwały stan, styl obiektowy.Teraz deklaracje funkcji w nagłówku:
Nazwy funkcji mają format
{prefix}_{from}_{to}
, w którym{from}
jest poprzedni (bieżący) stan i{to}
następny. Zauważ, że jeśli tabela przejść nie zezwala na pewne przejścia, zamiast wskaźnika funkcji zostanie ustawiony wskaźnik NULL. Wreszcie magia dzieje się z makrem. Tutaj budujemy tabelę przejściową (macierz wyliczeń stanu) i tabelę wyszukiwania funkcji przejścia stanu (macierz wskaźników funkcji):Podczas tworzenia FSM makro
FSM_EXAMPLE_CREATE()
należy użyć .Teraz w kodzie źródłowym należy zapełnić każdą zadeklarowaną powyżej funkcję zmiany stanu. Strukturę
FsmTurnstileFopts
można wykorzystać do przekazywania danych do / z automatu stanów. Każde przejście musifsm->check
być równe, aby alboEFSM_EXAMPLE_TR_RETREAT
zablokować przejście, alboEFSM_EXAMPLE_TR_ADVANCE
pozwolić mu przejść do stanu nakazanego. Działający przykład można znaleźć na stronie (FsmTemplateC) [ https://github.com/ChisholmKyle/FsmTemplateC] .Oto bardzo proste rzeczywiste użycie w kodzie:
Cały ten biznes nagłówkowy i wszystkie te funkcje, aby mieć prosty i szybki interfejs, są tego warte.
źródło
Możesz użyć biblioteki Open Source OpenFST .
źródło
źródło
Osobiście używam struktur odwołujących się do siebie w połączeniu z tablicami wskaźników. Jakiś czas temu przesłałem tutorial na github, link:
https://github.com/mmelchger/polling_state_machine_c
Uwaga: Zdaję sobie sprawę, że ten wątek jest dość stary, ale mam nadzieję, że uzyskam informacje i przemyślenia na temat konstrukcji maszyny stanów, a także będę w stanie podać przykład możliwego projektu maszyny stanu w C.
źródło
Możesz rozważyć UML-state-machine-in-c , „lekką” strukturę maszyny stanów w C. Napisałem tę platformę do obsługi zarówno maszyny stanów skończonych, jak i maszyny stanów hierarchicznych . W porównaniu z tabelami stanów lub prostymi przypadkami przełączników, podejście ramowe jest bardziej skalowalne. Może być stosowany do prostych skończonych maszyn stanów do złożonych hierarchicznych maszyn stanów.
Maszyna stanowa jest reprezentowana przez
state_machine_t
strukturę. Zawiera tylko dwóch członków „Event” i wskaźnik do „state_t”.state_machine_t
musi być pierwszym członkiem struktury maszyny stanów. na przykładstate_t
zawiera moduł obsługi stanu, a także opcjonalny moduł obsługi wejścia i wyjścia.Jeśli struktura jest skonfigurowana dla hierarchicznej maszyny stanów, to
state_t
zawiera wskaźnik do stanu nadrzędnego i podrzędnego.Framework udostępnia interfejs API
dispatch_event
do wysyłania zdarzenia do automatu stanów iswitch_state
wyzwalania przejścia stanu.Aby uzyskać więcej informacji na temat implementowania hierarchicznej maszyny stanów, zobacz GitHub repozytorium .
przykłady kodu,
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
źródło
Oto metoda dla automatu stanów, który używa makr, dzięki czemu każda funkcja może mieć swój własny zestaw stanów: https://www.codeproject.com/Articles/37037/Macros-to-simulate-multi-tasking-blocking-code -w
Jest zatytułowany „symuluj wielozadaniowość”, ale to nie jedyne zastosowanie.
Ta metoda wykorzystuje wywołania zwrotne do odebrania w każdej funkcji, w której została przerwana. Każda funkcja zawiera listę stanów unikalnych dla każdej funkcji. Centralna „bezczynna pętla” służy do uruchamiania automatów stanów. „Bezczynna pętla” nie ma pojęcia, jak działają maszyny stanów, to poszczególne funkcje „wiedzą, co robić”. Aby napisać kod dla funkcji, wystarczy utworzyć listę stanów i użyć makr do „zawieszenia” i „wznowienia”. Użyłem tych makr w Cisco, kiedy napisałem Bibliotekę Transceiver dla przełącznika Nexus 7000.
źródło