การออกแบบเครื่องจักร C [ปิด]


193

ฉันกำลังสร้างโครงการขนาดเล็กในแบบผสม C และ C ++ ฉันกำลังสร้างเครื่องจักรเล็ก ๆ แห่งหนึ่งที่เป็นหัวใจของด้ายคนงานของฉัน

ฉันสงสัยว่าคุณ gurus บน SO จะแบ่งปันเทคนิคการออกแบบเครื่องจักรของรัฐหรือไม่

หมายเหตุ:ฉันเป็นหลักหลังจากพยายามและทดสอบเทคนิคการใช้งาน

อัปเดต:อิงจากอินพุตที่ยอดเยี่ยมทั้งหมดที่รวบรวมบน SO ฉันได้ตัดสินสถาปัตยกรรมนี้:

ตัวปั๊มเหตุการณ์ชี้ไปที่ตัวรวมเหตุการณ์ซึ่งชี้ไปที่ผู้เลือกจ่ายงาน  โปรแกรมเลือกจ่ายงานชี้ไปที่ 1 ถึง n การดำเนินการซึ่งชี้กลับไปยังผู้รวบรวมเหตุการณ์  ตารางช่วงการเปลี่ยนภาพที่มีสัญลักษณ์ตัวแทนชี้ไปที่ผู้เลือกจ่าย


4
คำตอบที่นี่ดีมาก ดูคำถามที่ซ้ำกันนี้ซึ่งมีคำตอบที่ดีเช่นกัน: stackoverflow.com/questions/1371460/state-machines-tutorials
Michael Burr

2
สิ่งนี้ก็น่าสนใจเช่น: stackoverflow.com/questions/133214/…
Daniel Daranas


คำตอบ:


170

เครื่องจักรที่ฉันเคยออกแบบมาก่อน (C ไม่ใช่ C ++) มี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) { ... };

ฟังก์ชั่นทั้งหมดเหล่านี้เขียนขึ้นเพื่อไม่ใช้ตัวแปรและส่งคืนสถานะใหม่สำหรับเครื่องสถานะ ในตัวอย่างนี้ตัวแปรโกลบอลใช้สำหรับการส่งข้อมูลใด ๆ ไปยังฟังก์ชันสถานะที่จำเป็น

การใช้ globals นั้นไม่ได้แย่อย่างที่คิดเพราะ FSM มักจะถูกล็อคไว้ในหน่วยการคอมไพล์เดียวและตัวแปรทั้งหมดเป็นแบบสแตติกกับหน่วยนั้น (ซึ่งเป็นเหตุผลที่ฉันใช้เครื่องหมายคำพูดรอบ "โลก" ข้างบน FSM มากกว่าระดับโลกอย่างแท้จริง) เช่นเดียวกับ globals ทั้งหมดมันต้องมีการดูแล

อาร์เรย์การเปลี่ยนแปลงจะกำหนดช่วงการเปลี่ยนภาพที่เป็นไปได้ทั้งหมดและฟังก์ชั่นที่เรียกใช้สำหรับการเปลี่ยนแปลงเหล่านั้น (รวมถึง catch-all อันสุดท้าย):

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

อะไรที่ว่าคือถ้าคุณอยู่ในST_INITรัฐและคุณได้รับเหตุการณ์ทำให้การเรียกร้องให้EV_KEYPRESSGotKey

การทำงานของ 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รวมกัน

ฉันใช้รหัสที่คล้ายกันนี้ในโครงการการสื่อสารมากมายเช่นการใช้งานสแต็คการสื่อสารและโปรโตคอลสำหรับระบบฝังตัว ข้อได้เปรียบใหญ่คือความเรียบง่ายและความสะดวกในการเปลี่ยนอาร์เรย์การเปลี่ยน

ฉันไม่สงสัยเลยว่าจะมี abstractions ระดับสูงกว่าซึ่งอาจเหมาะสมกว่าในทุกวันนี้ แต่ฉันคิดว่าพวกเขาจะต้มลงไปในโครงสร้างแบบเดียวกันนี้


และตามที่ldogระบุไว้ในความคิดเห็นคุณสามารถหลีกเลี่ยงการรวมกันโดยการส่งตัวชี้โครงสร้างไปยังฟังก์ชั่นทั้งหมด (และใช้สิ่งนั้นในวงเหตุการณ์) สิ่งนี้จะช่วยให้เครื่องหลายสถานะทำงานแบบเคียงข้างกันโดยไม่มีการรบกวน

เพียงแค่สร้างประเภทโครงสร้างที่เก็บข้อมูลเฉพาะเครื่อง (ระบุอย่างต่ำสุด) และใช้สิ่งนั้นแทนที่จะเป็นรูปทรงกลม

เหตุผลที่ฉันไม่ค่อยทำนั่นเป็นเพราะเครื่องจักรส่วนใหญ่ที่ฉันเขียนมาเป็นแบบซิงเกิลตัน (แบบครั้งเดียว, ตอนเริ่มกระบวนการ, การอ่านไฟล์การกำหนดค่าเป็นต้น) ไม่จำเป็นต้องรันมากกว่าหนึ่งอินสแตนซ์ . แต่มันก็มีค่าถ้าคุณจำเป็นต้องเรียกใช้มากกว่าหนึ่ง


24
สวิตช์ยักษ์ผสมรหัสเข้ากับ FSM แม้ว่าจะมีเพียงการเรียกใช้ฟังก์ชันต่อการเปลี่ยนแปลง แต่ก็ยังมีรหัสบางส่วนและเป็นเรื่องง่ายสำหรับคนที่จะใช้วิธีการที่ไม่เหมาะสมโดยเพียงเพิ่มอินไลน์การเปลี่ยนแปลง 4 บรรทัดขนาดเล็ก ไก่หนึ่งบรรทัดสิบ จากนั้นมันก็หลุดออกจากมือ ด้วยอาร์เรย์โครงสร้าง FSM ยังคงสะอาดอยู่ - คุณสามารถเห็นการเปลี่ยนแปลงทุกอย่างและเอฟเฟกต์ (ฟังก์ชัน) และฉันเริ่มต้นเมื่อ enums กระพริบตาในสายตาของ ISO การเขียนโค้ดสำหรับแพลตฟอร์มที่ฝังตัวด้วย 6809 กับคอมไพเลอร์ที่เราจะพูดว่า :-) น้อยกว่าที่สมบูรณ์แบบ
paxdiablo

5
คุณพูดถูกใช่ว่า enums จะดีกว่า แต่ฉันก็ยังอยากให้มี FSM เป็นอาร์เรย์ struct จากนั้นมันก็ทำงานโดยข้อมูลมากกว่ารหัส (ดีมีบางรหัส แต่โอกาสในการบรรจุที่วน FSM ที่ฉันให้เป็นบาง)
paxdiablo

2
นี่เป็นสิ่งที่ดีสำหรับเครื่องควบคุมสถานะกระบวนการที่ฉันใช้เพื่อเพิ่มสาม substates (อาจว่างเปล่า) สำหรับทุก ๆ สถานะดังนั้นการเรียกฟังก์ชันสถานะจะกลายเป็น GotKey (substate) โดยที่ substate จะเป็น: - SS_ENTUN - SS_EXIT โดยทั่วไปแล้วฟังก์ชั่นของรัฐจะถูกเรียกด้วยสถานะย่อย SS_ENTRY เมื่อเข้าเพื่อให้รัฐสามารถสร้างสถานะใหม่ (เช่นตำแหน่งแอคทูเอเตอร์) ในขณะที่ไม่มีการเปลี่ยนแปลงค่าสถานะย่อย SS_RUN จะถูกส่งผ่าน เมื่อมีการเปลี่ยนแปลงฟังก์ชันสถานะจะถูกเรียกด้วยสถานะย่อย SS_EXIT เพื่อให้สามารถทำการล้างข้อมูลใด ๆ (เช่น deallocate ทรัพยากร)
Metiu

13
คุณบอกว่าคุณแบ่งปันข้อมูลโดยใช้ Globals แต่มันอาจจะทำความสะอาดถ้าคุณกำหนดหน้าที่ของรัฐที่จะเป็นint (*fn)(void*);ที่void*เป็นตัวชี้ไปยังข้อมูลที่ทำงานแต่ละรัฐจะใช้เวลาในการเป็นพารามิเตอร์ จากนั้นฟังก์ชั่นของรัฐสามารถใช้ข้อมูลหรือเพิกเฉยได้
ldog

13
ฉันใช้การแยกข้อมูล / รหัสเดียวกันสำหรับการเขียน FSM ยกเว้นว่ามันไม่เคยเกิดขึ้นกับฉันเพื่อแนะนำสถานะ 'ตัวแทน' ความคิดที่น่าสนใจ! อย่างไรก็ตามการวนซ้ำของช่วงการเปลี่ยนภาพอาจมีราคาแพงหากคุณมีสถานะเป็นจำนวนมาก (ซึ่งเป็นกรณีของฉันเนื่องจากรหัส C ถูกสร้างขึ้นโดยอัตโนมัติ) ในสถานการณ์เช่นนี้จะมีประสิทธิภาพมากกว่าที่จะมีหนึ่งช่วงการเปลี่ยนภาพต่อรัฐ ดังนั้นรัฐจึงไม่ใช่ค่า enum อีกต่อไป แต่เป็นตารางการเปลี่ยนแปลง ด้วยวิธีนี้คุณไม่จำเป็นต้องวนซ้ำในช่วงการเปลี่ยนภาพทั้งหมดในเครื่อง แต่จะเปลี่ยนไปเป็นสถานะที่เกี่ยวข้องกับสถานะปัจจุบัน
Frerich Raabe

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ข้อความสั่งขึ้นอยู่กับป้ายกำกับคุณสามารถตั้งชื่อรัฐของคุณแทนที่จะต้องติดตามความยุ่งเหยิงของตัวเลขหรือใช้ enum นอกจากนี้ยังสร้างรหัสที่สะอาดกว่าเนื่องจากคุณไม่ต้องการ cruft ของฟังก์ชันพอยน์เตอร์หรือคำสั่ง switch ขนาดใหญ่และขณะที่ลูป ฉันพูดถึงมันมีประสิทธิภาพมากขึ้นด้วยหรือไม่

นี่คือลักษณะที่เครื่องสถานะอาจมีลักษณะ:

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
นี่เป็นการบังคับให้คุณใช้มัลติทาสกิ้งแบบ preemptive ในทุกกรณียกเว้นกรณีที่ง่ายที่สุด
Craig McQueen

1
gotos เหล่านั้นสามารถถูกแทนที่ด้วยการเรียกใช้ฟังก์ชัน และถ้าผู้สร้างโปรไฟล์บอกคุณว่าโปรแกรมของคุณกำลังจมน้ำเนื่องจากค่าใช้จ่ายการเรียกใช้ฟังก์ชันคุณสามารถแทนที่การโทรด้วย gotos ได้ตามต้องการ
Abtin Forouzandeh

7
@AbtinForouzandeh เพียงแค่แทนที่ gotos ด้วยการเรียกใช้ฟังก์ชันจะทำให้เกิด stackoverflow เนื่องจาก call stack จะถูกล้างในกรณีที่เกิดข้อผิดพลาดเท่านั้น
JustMaximumPower

ฉันเห็นด้วยกับวิธีการโกโตะ นี่คือชุดของมาโครที่แสดงให้เห็นว่า และมาโครก็ทำให้โค้ดของคุณมีโครงสร้างราวกับว่าคุณเขียนมันตามปกติ นอกจากนี้ยังทำงานในระดับ
อินเตอร์รัปต์

30

คุณอาจพิจารณา State Machine Compiler http://smc.sourceforge.net/

ยูทิลิตี้โอเพนซอร์ซที่สวยงามนี้ยอมรับคำอธิบายของเครื่องรัฐในภาษาที่ง่ายและคอมไพล์ให้กับภาษาใดภาษาหนึ่งโหลหรือมากกว่านั้นรวมถึง C และ C ++ ยูทิลิตีนี้เขียนขึ้นใน Java และสามารถรวมเป็นส่วนหนึ่งของบิลด์

เหตุผลในการทำเช่นนี้แทนที่จะเขียนด้วยมือโดยใช้รูปแบบ GoF State หรือวิธีการอื่น ๆ คือเมื่อเครื่องของคุณแสดงเป็นรหัสโครงสร้างพื้นฐานจะหายไปภายใต้น้ำหนักของสำเร็จรูปที่ต้องสร้างเพื่อรองรับ การใช้วิธีการนี้ช่วยให้คุณสามารถแยกความกังวลได้อย่างยอดเยี่ยมและคุณยังคงรักษาโครงสร้างของสถานะเครื่องจักรของคุณให้มองเห็น รหัสที่สร้างขึ้นอัตโนมัติจะเข้าสู่โมดูลที่คุณไม่จำเป็นต้องสัมผัสเพื่อให้คุณสามารถย้อนกลับไปเล่นกับโครงสร้างของเครื่องสถานะโดยไม่ส่งผลกระทบต่อรหัสสนับสนุนที่คุณเขียน

ขออภัยฉันมีความกระตือรือร้นและไม่ต้องสงสัยเลยว่าจะทำให้ทุกคนออกไป แต่มันเป็นยูทิลิตี้บากอันดับต้น ๆ และมีเอกสารที่ดีเช่นกัน


20

อย่าลืมตรวจสอบการทำงานของ Miro Samek (บล็อกState Spaceเว็บไซต์State Machines & Tools ) ที่มีบทความในวารสารผู้ใช้ C / C ++ที่ยอดเยี่ยม

เว็บไซต์มีการใช้งานที่สมบูรณ์ (C / C ++) ทั้งในโอเพนซอร์ซและใบอนุญาตการค้าของเครื่องกรอบของรัฐ (QP Framework) , ตัวจัดการเหตุการณ์ (QEP) , เครื่องมือสร้างแบบจำลองพื้นฐาน (QM)และเครื่องมือติดตาม (QSpy)ซึ่ง อนุญาตให้วาดเครื่องรัฐสร้างรหัสและแก้ปัญหาพวกเขา

หนังสือเล่มนี้มีคำอธิบายที่ครอบคลุมเกี่ยวกับสิ่งที่ / สาเหตุของการนำไปใช้และวิธีการใช้งานและยังเป็นวัสดุที่ดีในการทำความเข้าใจพื้นฐานของเครื่องเชิงลำดับขั้นและ จำกัด

เว็บไซต์นี้มีลิงค์ไปยังแพ็คเกจสนับสนุนบอร์ดหลายชุดสำหรับการใช้งานซอฟต์แวร์ที่มีแพลตฟอร์มแบบฝัง


ฉันแก้ไขชื่อคำถามตามที่คุณเล่น
jldupont

@ jldupont: ฉันแค่หมายความว่ามันจะดีกว่าที่จะชี้แจง ฉันได้ลบส่วนที่ไม่เกี่ยวข้องของคำตอบของฉันตอนนี้
แดเนียล Daranas

1
ฉันได้เพิ่มสิ่งที่คาดหวังในเว็บไซต์ / หนังสือหลังจากใช้ซอฟต์แวร์สำเร็จตัวเอง; มันเป็นหนังสือที่ดีที่สุดในชั้นวางหนังสือของฉัน
Adriaan

@Adriann คำอธิบายที่ดี! ฉันเพิ่งแก้ไขบ้านของเว็บไซต์ลิงค์ก่อนหน้านี้หยุดทำงาน
Daniel Daranas

2
ลิงก์อาจตายหรือชี้ไปที่หน้าแรกของเว็บไซต์ที่ดูเหมือนจะเปลี่ยนทิศทางไปเป็นซอฟต์แวร์ฝังตัว คุณยังสามารถดูเนื้อหาบางส่วนได้ที่state-machine.com/resources/articles.phpแต่ถึงแม้จะมีลิงค์ที่เกี่ยวข้องกับเครื่องส่วนใหญ่ก็ยังไม่ตาย นี่เป็นหนึ่งในลิงค์ที่ดีเพียงแห่งเดียวที่นั่น: state-machine.com/resources/…
Tatiana Racheva

11

ฉันได้ทำสิ่งที่คล้ายกับสิ่งที่ paxdiablo อธิบายเท่านั้นแทนที่จะเป็นอาร์เรย์ของการเปลี่ยนสถานะ / เหตุการณ์ฉันตั้งค่าตัวชี้ฟังก์ชันอาร์เรย์แบบสองมิติโดยมีค่าเหตุการณ์เป็นดัชนีของแกนหนึ่งและค่าสถานะปัจจุบันเป็น อื่น ๆ. จากนั้นฉันก็โทรมาstate = state_table[event][state](params)และสิ่งที่ถูกต้องเกิดขึ้น เซลล์ที่แสดงชุดค่าผสมสถานะ / เหตุการณ์ที่ไม่ถูกต้องจะได้รับตัวชี้ไปยังฟังก์ชันที่ระบุว่าเป็นเช่นนั้นแน่นอน

เห็นได้ชัดว่ามันใช้งานได้ก็ต่อเมื่อค่าสถานะและเหตุการณ์มีทั้งช่วงที่ต่อเนื่องกันและเริ่มต้นที่ 0 หรือใกล้พอ


1
รู้สึกเหมือนโซลูชันนี้ไม่ได้ปรับขนาดอย่างเหมาะสม: การเติมตารางมากเกินไปใช่ไหม
jldupont

2
+1 ปัญหาการปรับขนาดที่นี่คือหน่วยความจำ - โซลูชันของฉันมีเวลาการปรับมาตราส่วนอีกครั้งนั่นคือเวลาที่ใช้ในการสแกนตารางการเปลี่ยนภาพ อันนี้เสียสละหน่วยความจำสำหรับความเร็ว - มันเป็นเพียงการแลกเปลี่ยน คุณอาจต้องการตรวจสอบขอบเขต แต่ไม่ใช่วิธีแก้ปัญหาที่ไม่ดี
paxdiablo

Guys - ความคิดเห็นของฉันไม่ได้ออกมาตามที่ตั้งใจไว้: ฉันหมายถึงมันลำบากกว่าและมีแนวโน้มที่จะเกิดข้อผิดพลาด หากคุณเพิ่มรัฐ / เหตุการณ์จำเป็นต้องแก้ไขจำนวนมาก
jldupont

3
ไม่มีใครพูดว่าอาเรย์ 2 มิตินั้นเริ่มต้นด้วยมือ อาจมีบางอย่างที่อ่านไฟล์กำหนดค่าและสร้าง (หรืออย่างน้อยก็อาจมี)
John Zwinck

วิธีหนึ่งในการเริ่มต้นอาร์เรย์เช่นนั้นคือการใช้ประโยชน์จาก preprocessor ที่เป็นสารยึดเกาะที่ล่าช้า (เมื่อเทียบกับการรวมก่อนหน้า) คุณกำหนดรายการของสถานะทั้งหมด#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 ++ เครื่องรัฐ "กรอบ" จะได้รับจากสเตฟาน Heinzmann ของเขาในบทความ

เนื่องจากไม่มีการเชื่อมโยงไปยังการดาวน์โหลดโค้ดที่สมบูรณ์ในบทความฉันจึงมีอิสระที่จะวางโค้ดในโปรเจ็กต์และลองดูได้ สิ่งต่าง ๆ ด้านล่างนี้ได้รับการทดสอบและรวมชิ้นส่วนที่ขาดหายไปเล็กน้อย แต่เห็นได้ชัดมาก

นวัตกรรมสำคัญที่นี่คือคอมไพเลอร์กำลังสร้างรหัสที่มีประสิทธิภาพมาก การกระทำการเข้า / ออกที่ว่างเปล่าไม่มีค่าใช้จ่าย การกระทำในการเข้า / ออกที่ไม่ว่างเปล่าจะถูกแทรกเข้าไป คอมไพเลอร์ยังตรวจสอบความสมบูรณ์ของ statechart การกระทำที่ขาดหายไปจะทำให้เกิดข้อผิดพลาดในการเชื่อมโยง Top::initสิ่งเดียวที่ไม่ได้ถูกจับเป็นที่ขาดหายไป

นี่เป็นทางเลือกที่ดีมากสำหรับการนำไปใช้ของ Miro Samek หากคุณสามารถอยู่ได้โดยปราศจากสิ่งที่ขาดหายไป - สิ่งนี้อยู่ไกลจากการใช้ UML 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)

อืม ... รหัสของคุณหายไป ก่อนอื่นคุณต้องมีสองส่วนหัว แต่ให้เฉพาะส่วนแรกเท่านั้น เมื่อฉันแสดงความคิดเห็นคำสั่ง "รวม" ฉันได้รับข้อผิดพลาดนี้เมื่อรวบรวม: d: \ 1 \ hsm> g ++ test.cpp test.cpp: 195: 1: ข้อผิดพลาด: ความเชี่ยวชาญของ 'คงเป็นโมฆะ CompState <H, id, B> :: init (H &) [กับ H = TestHSM; int id ที่ไม่ได้ลงชื่อ = 0u; B = CompState <TestHSM, 0u, TopState <TestHSM>>] 'หลังจากการเริ่มต้น
Freddie Chopin

ฉันต้องย้ายคำจำกัดความของ 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;
  }
}

คะแนน: รัฐเป็นส่วนตัวไม่เพียง แต่ในหน่วยรวบรวม แต่ยังรวมถึง event_handler กรณีพิเศษอาจได้รับการจัดการแยกต่างหากจากสวิตช์หลักโดยใช้สิ่งก่อสร้างที่เห็นว่าจำเป็น

กรณีที่ซับซ้อนมากขึ้น

เมื่อสวิตช์มีขนาดใหญ่กว่าหน้าจอสองจอให้แบ่งเป็นฟังก์ชันที่จัดการแต่ละสถานะโดยใช้ตารางสถานะเพื่อค้นหาฟังก์ชันโดยตรง สถานะยังคงเป็นส่วนตัวของตัวจัดการเหตุการณ์ ฟังก์ชันตัวจัดการสถานะส่งคืนสถานะถัดไป หากจำเป็นเหตุการณ์บางอย่างยังสามารถได้รับการดูแลเป็นพิเศษในตัวจัดการเหตุการณ์หลัก ฉันชอบที่จะโยนในเหตุการณ์หลอกสำหรับรายการรัฐและออกและอาจเริ่มต้นเครื่องรัฐ:

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;
}

ความซับซ้อนมากขึ้น

เมื่อหน่วยการรวบรวมมีขนาดใหญ่เกินไป (ไม่ว่าคุณจะรู้สึกอย่างไรฉันควรพูดประมาณ 1,000 บรรทัด) ใส่ตัวจัดการสถานะแต่ละไฟล์ไว้ในไฟล์แยกต่างหาก เมื่อตัวจัดการสถานะแต่ละรายการยาวเกินกว่าสองหน้าจอให้แบ่งแต่ละเหตุการณ์ออกเป็นฟังก์ชันแยกต่างหากคล้ายกับวิธีที่สวิตช์สถานะถูกแยก คุณสามารถทำได้หลายวิธีแยกจากรัฐหรือโดยใช้ตารางทั่วไปหรือรวมรูปแบบต่าง ๆ บางคนได้รับความคุ้มครองจากผู้อื่น เรียงลำดับตารางของคุณและใช้การค้นหาแบบไบนารีหากต้องการความเร็ว

การเขียนโปรแกรมทั่วไป

ฉันต้องการให้ preprocessor จัดการกับปัญหาต่าง ๆ เช่นการเรียงลำดับตารางหรือแม้แต่การสร้างเครื่องสถานะจากคำอธิบายช่วยให้คุณ "เขียนโปรแกรมเกี่ยวกับโปรแกรม" ฉันเชื่อว่านี่คือสิ่งที่คน Boost ใช้ประโยชน์จากเทมเพลต C ++ สำหรับ แต่ฉันพบว่ามีความลับทางไวยากรณ์

ตารางสองมิติ

ฉันเคยใช้ตารางสถานะ / เหตุการณ์ในอดีต แต่ฉันต้องบอกว่าสำหรับกรณีที่ง่ายที่สุดฉันไม่พบพวกเขาที่จำเป็นและฉันชอบความชัดเจนและความสามารถในการอ่านคำสั่งสวิตช์แม้ว่ามันจะขยายผ่านหนึ่งหน้าจอเต็ม สำหรับกรณีที่ซับซ้อนมากขึ้นตารางจะออกจากมืออย่างรวดเร็วตามที่คนอื่น ๆ ระบุไว้ สำนวนที่ฉันนำเสนอที่นี่ช่วยให้คุณสามารถเพิ่มเหตุการณ์และสถานะเมื่อคุณรู้สึกเหมือนมันโดยไม่ต้องบำรุงรักษาตารางการใช้หน่วยความจำ (แม้ว่ามันอาจจะเป็นหน่วยความจำของโปรแกรม)

คำปฏิเสธ

ความต้องการพิเศษอาจทำให้สำนวนเหล่านี้มีประโยชน์น้อยลง แต่ฉันพบว่ามันชัดเจนและดูแลได้


ฉันจะหลีกเลี่ยง 'นี่' เป็นชื่อตัวแปรหรือสัญลักษณ์สำหรับการเชื่อมโยงแม้ว่ามันจะไม่ใช่คำสงวน
XTL

4

ยังไม่ทดลองมาก แต่สนุกกับการเขียนโค้ดตอนนี้เป็นเวอร์ชั่นที่ปรับปรุงมากกว่าคำตอบเดิมของฉัน ดูเวอร์ชั่นล่าสุดได้ที่mercurial.intuxication.org :

sm.h

#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
ฉันชอบความคิดเห็นที่ "ยังไม่ทดลอง" ดูเหมือนจะบ่งบอกว่ามีองศา untestedness และที่คุณใส่ไม่น้อยของความพยายามในการทดสอบนั้นไม่ :-)
paxdiablo

@Christoph ลิงก์ในคำตอบนี้ใช้งานไม่ได้ คุณเคยทดสอบรหัสนี้หรือไม่? หากผ่านการทดสอบและใช้งานได้คุณควรลบออกจากคำตอบ อาจแสดงตัวอย่างว่าโค้ดนี้ให้ผลลัพธ์เมื่อมีการขยายมาโคร ฉันชอบความคิดทั่วไป
Joakim

4

ฉันชอบคำตอบของ paxdiable และตัดสินใจที่จะใช้คุณสมบัติที่หายไปทั้งหมดสำหรับแอปพลิเคชันของฉันเช่นตัวแปร Guard และข้อมูลเฉพาะของเครื่องจักร

ฉันอัพโหลดการใช้งานของฉันไปยังไซต์นี้เพื่อแบ่งปันกับชุมชน มันได้รับการทดสอบโดยใช้ IAR Embedded Workbench สำหรับ ARM

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


ค้นหาสิ่งนี้ในปีพ. ศ. 2561 และยังใช้งานได้อยู่ ฉันอ่านคำตอบ @paxdiablo และฉันได้ใช้งานประเภทนั้นในระบบฝังตัวเรียบร้อยแล้ว วิธีการแก้ปัญหานี้จะเพิ่มสิ่งที่ขาดหายไปจากคำตอบ paxdiablos :)
Kristoffer

4

เครื่องมือมาเปิดอีกที่น่าสนใจคือเครื่องมือ Yakindu Statechart บน statecharts.org มันใช้ประโยชน์จากชาร์ตของรัฐ Harel และทำให้มีสถานะลำดับชั้นและแบบขนานและสร้างรหัส C และ C ++ (เช่นเดียวกับ Java) มันไม่ได้ใช้ประโยชน์จากห้องสมุด แต่ทำตามวิธีการ 'รหัสธรรมดา' รหัสโดยทั่วไปใช้โครงสร้างกรณีสลับ เครื่องกำเนิดรหัสยังสามารถปรับแต่ง นอกจากนี้เครื่องมือยังมีคุณสมบัติอื่น ๆ อีกมากมาย


3

มาถึงสายนี้ (ตามปกติ) แต่การสแกนคำตอบถึงวันที่ฉันคิดว่าสิ่งที่สำคัญหายไป;

ฉันพบในโครงการของตัวเองว่ามันจะมีประโยชน์มากหากไม่มีฟังก์ชั่นสำหรับการรวมกันของรัฐ / เหตุการณ์ที่ถูกต้อง ฉันชอบความคิดของการมีตารางรัฐ / กิจกรรม 2D อย่างมีประสิทธิภาพ แต่ฉันชอบองค์ประกอบตารางมากกว่าตัวชี้ฟังก์ชั่นง่าย ๆ แต่ฉันพยายามที่จะจัดระเบียบงานออกแบบของฉันดังนั้นที่เป็นหัวใจมันประกอบด้วยองค์ประกอบอะตอมหรือการกระทำที่เรียบง่าย ด้วยวิธีนี้ฉันสามารถแสดงรายการองค์ประกอบอะตอมแบบง่ายเหล่านั้นที่จุดตัดของตารางสถานะ / เหตุการณ์ของฉัน แนวคิดคือคุณไม่จำเป็นต้องกำหนดฟังก์ชั่น N squared (โดยทั่วไปจะง่ายมาก) เหตุใดจึงมีข้อผิดพลาดง่ายใช้เวลานานเขียนยากอ่านยากคุณตั้งชื่อทำไม

ฉันยังรวมสถานะใหม่ที่เป็นทางเลือกและตัวชี้ฟังก์ชันเสริมสำหรับแต่ละเซลล์ในตาราง ตัวชี้ฟังก์ชั่นมีไว้สำหรับกรณีพิเศษเหล่านั้นซึ่งคุณไม่ต้องการเพียงแค่ยิงออกจากรายการการกระทำของอะตอม

คุณรู้ว่าคุณทำถูกต้องเมื่อคุณสามารถแสดงฟังก์ชั่นที่แตกต่างกันมากมายเพียงแค่แก้ไขตารางของคุณโดยไม่ต้องเขียนโค้ดใหม่


2
อาจเป็นตัวอย่างที่ดีใช่มั้ย
jldupont

1
ตัวอย่างที่เป็นจริงที่สามารถแยกได้เป็นงานที่ท้าทายที่ต้องใช้เวลามากกว่าที่ฉันพร้อมที่จะให้ในขณะนี้ มีอะไรในโพสต์ของฉันที่เข้าใจยากเป็นพิเศษหรือไม่ บางทีฉันสามารถแสดงได้ชัดเจนยิ่งขึ้น ความคิดนั้นง่ายมาก อย่ากำหนดกลไกสถานะที่ต้องการฟังก์ชั่นที่แยกจากกันสำหรับทุกเหตุการณ์ / สถานะรวมกันคุณจะได้รับฟังก์ชั่นที่มากเกินไป หาวิธีอื่นในการอธิบายการทำงานที่คุณต้องการสำหรับการรวมเหตุการณ์ / สถานะนั้นอย่างน้อยในกรณีส่วนใหญ่
Bill Forster

2
เข้าใจ: ตัวอย่างรหัสหลอกจะดี แต่ประเด็นของคุณชัดเจน
jldupont

3

เอาล่ะฉันคิดว่าของฉันแตกต่างจากคนอื่นเล็กน้อย การแยกรหัสและข้อมูลเล็กน้อยกว่าที่ฉันเห็นในคำตอบอื่น ๆ ฉันอ่านทฤษฎีเพื่อเขียนสิ่งนี้ซึ่งใช้ภาษาปกติเต็มรูปแบบ (ไม่มีการแสดงออกปกติเศร้า) Ullman, Minsky, Chomsky ไม่สามารถพูดได้ว่าฉันเข้าใจทั้งหมด แต่ฉันชักชวนจากเจ้านายเก่าที่สุดเท่าที่จะทำได้: ผ่านคำพูดของพวกเขา

ฉันใช้ตัวชี้ฟังก์ชันเป็นเพรดิเคตที่กำหนดการเปลี่ยนเป็นสถานะ 'ใช่' หรือสถานะ 'ไม่' สิ่งนี้จะช่วยอำนวยความสะดวกในการสร้างตัวรับสถานะที่ จำกัด สำหรับภาษาปกติที่คุณตั้งโปรแกรมด้วยภาษาแอสเซมบลีที่มากกว่า โปรดอย่าถูกเลื่อนออกไปโดยการเลือกชื่อโง่ของฉัน 'czek' == 'ตรวจสอบ' 'grok' == [ลองค้นหาในพจนานุกรม Hacker]

ดังนั้นสำหรับการวนซ้ำแต่ละครั้ง czek จึงเรียกใช้ฟังก์ชันเพรดิเคตที่มีอักขระปัจจุบันเป็นอาร์กิวเมนต์ ถ้าเพรดิเคตส่งคืนค่าจริงอักขระจะถูกใช้ (ตัวชี้ขั้นสูง) และเราติดตามการเปลี่ยนแปลง 'y' เพื่อเลือกสถานะถัดไป หากภาคแสดงผลเป็นเท็จตัวละครจะไม่ถูกบริโภคและเราจะติดตามการเปลี่ยนแปลง 'n' ดังนั้นการเรียนการสอนทุกครั้งจึงเป็นสาขาสองทาง! ฉันต้องอ่านเรื่องราวของเมลในเวลานั้น

รหัสนี้มาจากล่าม postscript ของฉันและพัฒนามาเป็นรูปแบบปัจจุบันพร้อมคำแนะนำมากมายจากเพื่อนบน comp.lang.c เนื่องจากโดยทั่วไปแล้ว postscript จะไม่มีไวยากรณ์ (ต้องการเพียงวงเล็บเหลี่ยมที่มีความสมดุลเท่านั้น), 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
นี่คือสิ่งที่ตัวแยกวิเคราะห์หรือตัวสร้าง lexer ยินดีปล่อยให้คุณ อย่างแปลกประหลาดเช่นนั้น ไม่ว่าคุณต้องการโค้ดด้วยมือหรือไม่ แน่นอนมันมีข้อดีของการสอน
Reinstate Monica

3

boost.org มาพร้อมกับการใช้งานแผนภูมิสถานะที่แตกต่างกัน 2 แบบ:

เช่นเคยเพิ่มจะคานคุณเข้าไปในนรกแม่แบบ

ไลบรารีแรกมีไว้สำหรับเครื่องสถานะที่มีประสิทธิภาพยิ่งขึ้น ไลบรารีที่สองให้เส้นทางการเปลี่ยนผ่านโดยตรงจาก UML Statechart ไปยังโค้ด

นี่คือคำถาม SO ที่ขอการเปรียบเทียบระหว่างสองที่ผู้เขียนทั้งสองตอบสนอง



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สิ่งนั้นสร้างการพึ่งพาระบบปฏิบัติการมัลติทาสก์แบบ preemtive
Craig McQueen

2

เมื่อระบุว่าคุณสามารถใช้ C ++ และด้วยเหตุนี้รหัส OO ฉันขอแนะนำให้ประเมินรูปแบบ 'GoF'state (GoF = Gang of Four พวกที่เขียนหนังสือรูปแบบการออกแบบซึ่งนำรูปแบบการออกแบบมาสู่ไฟแก็ซ)

มันไม่ซับซ้อนโดยเฉพาะและใช้กันอย่างแพร่หลายและพูดคุยเพื่อให้ง่ายต่อการดูตัวอย่างและคำอธิบายในบรรทัด

มันจะค่อนข้างเป็นที่รู้จักโดยคนอื่นที่รักษารหัสของคุณในภายหลัง

หากความกังวลเกี่ยวกับประสิทธิภาพเป็นสิ่งที่ควรค่าแก่การเปรียบเทียบเพื่อให้แน่ใจว่าวิธีการที่ไม่ใช่ OO นั้นมีประสิทธิภาพมากกว่าเนื่องจากมีปัจจัยหลายอย่างที่ส่งผลกระทบต่อประสิทธิภาพการทำงานและไม่ใช่ OO ที่ไม่ถูกต้อง ในทำนองเดียวกันหากการใช้หน่วยความจำเป็นข้อ จำกัด สำหรับคุณคุณควรทำการทดสอบหรือการคำนวณเพื่อดูว่านี่จะเป็นปัญหาสำหรับแอปพลิเคชันของคุณหรือไม่หากคุณใช้รูปแบบสถานะ

ต่อไปนี้เป็นลิงค์ไปสู่รูปแบบสถานะ 'Gof' ดังที่ Craig แนะนำ:


ดูเหมือนความคิดเห็น: ฉันขอแนะนำให้คุณรักษามันเป็นเช่นนั้น? เช่นไม่ได้วางไว้ในส่วน "คำตอบ"
jldupont

มันจะดีถ้าคุณสามารถให้ลิงค์ URL ที่ดีสำหรับ "รูปแบบสถานะ GoF" สำหรับผู้ที่ไม่คุ้นเคย
Craig McQueen

1
@jldupont - ความคิดเห็นที่ยุติธรรม ฉันเปลี่ยนข้อความเพื่อให้เป็นคำตอบที่เหมาะสมเนื่องจากฉันรู้สึกว่าขึ้นอยู่กับประสบการณ์ส่วนตัวว่าหากมีปัญหาด้านประสิทธิภาพเฉพาะวิธี GoF ทำงานได้ดีและจะมี 'ฐานผู้ใช้' ค่อนข้างมาก
Mick

@Craig - เพิ่มลิงก์ ทั้งคู่ดูแม่นยำและชัดเจนในเวลาที่ฉันเพิ่มเข้าไป
มิก

2

นี่คือตัวอย่างของ Finite State Machine สำหรับ Linux ที่ใช้คิวข้อความเป็นเหตุการณ์ เหตุการณ์ถูกวางในคิวและจัดการตามลำดับ สถานะการเปลี่ยนแปลงขึ้นอยู่กับสิ่งที่เกิดขึ้นสำหรับแต่ละเหตุการณ์

นี่คือตัวอย่างสำหรับการเชื่อมต่อข้อมูลกับรัฐเช่น:

  • เตรียม
  • เริ่มต้น
  • เกี่ยวข้อง
  • การเจรจาต่อรองของ MTU
  • รับรองความถูกต้อง

หนึ่งคุณสมบัติพิเศษเล็ก ๆ น้อย ๆ ที่ฉันเพิ่มคือการประทับเวลาสำหรับแต่ละข้อความ / เหตุการณ์ ตัวจัดการเหตุการณ์จะเพิกเฉยต่อเหตุการณ์ที่เก่าเกินไป (หมดอายุแล้ว) สิ่งนี้สามารถเกิดขึ้นได้มากมายในโลกแห่งความเป็นจริงที่คุณอาจติดอยู่ในสภาวะที่ไม่คาดคิด

ตัวอย่างนี้ทำงานบน 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 เครื่อง Mealy เป็นเครื่องรัฐที่เอาท์พุทเป็นฟังก์ชั่นของทั้งสถานะปัจจุบันและอินพุตเมื่อเทียบกับเครื่องมัวร์ซึ่งเอาท์พุทเป็นฟังก์ชั่นของรัฐเท่านั้น

    • เครื่องเขียนรหัสสถานะใน C และ C ++

      ความลุ่มหลงในบทความนี้ของฉันคือพื้นฐานของเครื่องรัฐและแนวทางการตั้งโปรแกรมที่เรียบง่ายสำหรับการเข้ารหัสเครื่องสถานะใน C หรือ C ++ ฉันหวังว่าเทคนิคง่าย ๆ เหล่านี้จะกลายเป็นเรื่องธรรมดามากขึ้นเพื่อให้คุณ (และคนอื่น ๆ ) พร้อมที่จะเห็นโครงสร้างเครื่องรัฐได้จากซอร์สโค้ด


1

ฉันใช้State Machine Compilerใน Java และ Python เพื่อประสบความสำเร็จ


1

นี่เป็นโพสต์เก่าที่มีคำตอบมากมาย แต่ฉันคิดว่าฉันจะเพิ่มวิธีการของตัวเองลงในเครื่องสถานะ จำกัด ใน C. ฉันสร้างสคริปต์ Python เพื่อสร้างรหัสโครงกระดูก C สำหรับจำนวนของรัฐใด ๆ สคริปต์นั้นมีการบันทึกไว้ใน GituHub ที่FsmTemplateC

ตัวอย่างนี้เป็นไปตามวิธีการอื่น ๆ ที่ฉันได้อ่าน มันไม่ได้ใช้คำสั่ง goto หรือ switch แต่แทนที่จะมีฟังก์ชั่นการเปลี่ยนแปลงในเมทริกซ์ตัวชี้ (ตารางค้นหา) รหัสขึ้นอยู่กับแมโครตัวเริ่มต้นหลายบรรทัดขนาดใหญ่และคุณลักษณะ C99 (ตัวกำหนดค่าเริ่มต้นและตัวอักษรผสม) ดังนั้นหากคุณไม่ชอบสิ่งเหล่านี้คุณอาจไม่ชอบวิธีนี้

นี่คือสคริปต์ Python ของตัวอย่างประตูหมุนซึ่งสร้างโครงกระดูก C-code โดยใช้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เป็นเพียงรายการของอินพุต
  • FsmTurnstilestruct เป็นหัวใจของเครื่องรัฐที่มีการตรวจสอบการเปลี่ยนแปลงตารางการค้นหาฟังก์ชั่นของรัฐในปัจจุบันรัฐบัญชาและนามแฝงกับฟังก์ชั่นหลักที่ทำงานเครื่อง
  • ทุกฟังก์ชั่นตัวชี้ (นามแฝง) ในFsmTurnstileควรจะเรียกจาก struct และจะต้องมีการป้อนข้อมูลครั้งแรกของมันเป็นตัวชี้ไปยังตัวเองเพื่อที่จะรักษาสถานะถาวรสไตล์เชิงวัตถุ

ตอนนี้สำหรับการประกาศฟังก์ชั่นในส่วนหัว:

/* 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 แทนที่จะเป็นตัวชี้ฟังก์ชันจะถูกตั้งค่า ในที่สุดความมหัศจรรย์ก็เกิดขึ้นกับมาโคร ที่นี่เราสร้างตารางการเปลี่ยนแปลง (เมทริกซ์ของรัฐ enums) และฟังก์ชั่นการเปลี่ยนสถานะค้นหาตาราง (เมทริกซ์ของตัวชี้ฟังก์ชั่น):

/* 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()จะต้องใช้มาโคร

ตอนนี้ในซอร์สโค้ดทุกฟังก์ชั่นการเปลี่ยนสถานะที่ประกาศไว้ข้างต้นควรจะมีประชากร FsmTurnstileFoptsstruct สามารถใช้ในการส่งผ่านข้อมูลไปยัง / จากเครื่องรัฐ การเปลี่ยนแปลงทุกครั้งจะต้องตั้งค่า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) ทรานสดิวเซอร์แบบ จำกัด สถานะแบบถ่วงน้ำหนักเป็นแบบออโตมาตะซึ่งแต่ละช่วงการเปลี่ยนภาพจะมีฉลากป้อนเข้า, ป้ายกำกับส่งออกและน้ำหนัก ตัวรับขอบเขต จำกัด ที่คุ้นเคยมากขึ้นจะถูกแสดงเป็นตัวแปลงสัญญาณที่มีอินพุตและเอาต์พุตเลเบลเท่ากัน Finite-state acceptors ใช้เพื่อแสดงชุดของสตริง (โดยเฉพาะชุดปกติหรือแบบมีเหตุผล); ตัวแปลงสัญญาณสถานะ จำกัด ใช้เพื่อแสดงความสัมพันธ์แบบไบนารีระหว่างคู่ของสตริง (โดยเฉพาะการแปลงสัญญาณแบบมีเหตุผล) น้ำหนักสามารถนำมาใช้เพื่อเป็นตัวแทนของค่าใช้จ่ายของการเปลี่ยนแปลงเฉพาะ


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

หมายเหตุ: ฉันรู้ว่าเธรดนี้ค่อนข้างเก่า แต่ฉันหวังว่าจะได้รับอินพุตและความคิดเกี่ยวกับการออกแบบเครื่องรัฐรวมถึงความสามารถในการเป็นตัวอย่างสำหรับการออกแบบเครื่องจักรที่เป็นไปได้ในซี


0

คุณสามารถพิจารณาUML รัฐเครื่องในคเป็น "เบา" กรอบกลไกของรัฐใน 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 -ที่

มันมีชื่อว่า "จำลองมัลติทาสกิ้ง" แต่นั่นไม่ได้ใช้เพียงอย่างเดียว

วิธีนี้ใช้โทรกลับเพื่อไปรับสินค้าในแต่ละฟังก์ชั่นที่ค้างไว้ แต่ละฟังก์ชั่นมีรายการสถานะที่ไม่ซ้ำกันสำหรับแต่ละฟังก์ชั่น ส่วนกลาง "idle loop" ใช้เพื่อรันเครื่องสถานะ "idle loop" ไม่มีความคิดว่ากลไกการทำงานของรัฐเป็นหน้าที่ของแต่ละบุคคลที่ "รู้ว่าต้องทำอะไร" เพื่อเขียนโค้ดสำหรับฟังก์ชั่นหนึ่งเพิ่งสร้างรายการของรัฐและใช้แมโครเพื่อ "หยุด" และ "ดำเนินการต่อ" ฉันใช้มาโครเหล่านี้ที่ Cisco เมื่อฉันเขียน Transceiver Library สำหรับสวิตช์ Nexus 7000

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.