{"id":21274157,"url":"https://github.com/endurodave/statemachinewithmoderndelegates","last_synced_at":"2025-07-11T06:33:47.651Z","repository":{"id":218373708,"uuid":"746255607","full_name":"endurodave/StateMachineWithModernDelegates","owner":"endurodave","description":"C++ State Machine with Modern Delegates","archived":false,"fork":false,"pushed_at":"2024-11-13T14:19:25.000Z","size":269,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2024-11-13T15:26:31.182Z","etag":null,"topics":["asynchronous-callbacks","asynchronous-function-calling","asynchronous-programming","cpp","cpp17","cross-platform","delegates","embedded-systems","linux","multi-thread","state-machine","windows"],"latest_commit_sha":null,"homepage":"","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/endurodave.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-01-21T14:36:20.000Z","updated_at":"2024-11-13T14:19:28.000Z","dependencies_parsed_at":"2024-11-04T18:30:53.315Z","dependency_job_id":"44acddb6-f5b5-44bf-9b18-27eba20756af","html_url":"https://github.com/endurodave/StateMachineWithModernDelegates","commit_stats":null,"previous_names":["endurodave/statemachinewithmoderndelegates"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/endurodave%2FStateMachineWithModernDelegates","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/endurodave%2FStateMachineWithModernDelegates/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/endurodave%2FStateMachineWithModernDelegates/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/endurodave%2FStateMachineWithModernDelegates/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/endurodave","download_url":"https://codeload.github.com/endurodave/StateMachineWithModernDelegates/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":225701538,"owners_count":17510553,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["asynchronous-callbacks","asynchronous-function-calling","asynchronous-programming","cpp","cpp17","cross-platform","delegates","embedded-systems","linux","multi-thread","state-machine","windows"],"created_at":"2024-11-21T09:19:10.315Z","updated_at":"2025-07-11T06:33:47.644Z","avatar_url":"https://github.com/endurodave.png","language":"C++","funding_links":[],"categories":[],"sub_categories":[],"readme":"![License MIT](https://img.shields.io/github/license/BehaviorTree/BehaviorTree.CPP?color=blue)\n[![conan Ubuntu](https://github.com/endurodave/StateMachineWithModernDelegates/actions/workflows/cmake_ubuntu.yml/badge.svg)](https://github.com/endurodave/StateMachineWithModernDelegates/actions/workflows/cmake_ubuntu.yml)\n[![conan Ubuntu](https://github.com/endurodave/StateMachineWithModernDelegates/actions/workflows/cmake_clang.yml/badge.svg)](https://github.com/endurodave/StateMachineWithModernDelegates/actions/workflows/cmake_clang.yml)\n[![conan Windows](https://github.com/endurodave/StateMachineWithModernDelegates/actions/workflows/cmake_windows.yml/badge.svg)](https://github.com/endurodave/StateMachineWithModernDelegates/actions/workflows/cmake_windows.yml)\n\n# C++ State Machine with Asynchronous Delegates\n\nA framework combining [C++ State Machine](https://github.com/endurodave/StateMachine) with the [DelegateMQ](https://github.com/endurodave/DelegateMQ) asynchronous delegate library.\n\n# Table of Contents\n\n- [C++ State Machine with Asynchronous Delegates](#c-state-machine-with-asynchronous-delegates)\n- [Table of Contents](#table-of-contents)\n- [Introduction](#introduction)\n- [Getting Started](#getting-started)\n- [Asynchronous Delegate Callbacks](#asynchronous-delegate-callbacks)\n- [Self-Test Subsystem](#self-test-subsystem)\n  - [SelfTestEngine](#selftestengine)\n  - [CentrifugeTest](#centrifugetest)\n  - [Timer](#timer)\n- [Poll Events](#poll-events)\n- [User Interface](#user-interface)\n- [Run-Time](#run-time)\n- [Conclusion](#conclusion)\n- [References](#references)\n\n\n# Introduction\n\n\u003cp\u003eA software-based Finite State Machines (FSM) is an implementation method used to decompose a design into states and events. Simple embedded devices with no operating system employ single threading such that the state machines run on a single \u0026ldquo;thread\u0026rdquo;. More complex systems use multithreading to divvy up the processing.\u003c/p\u003e\n\n\u003cp\u003eThis repository combines state machines and asynchronous delegates into a single project. The goal for the article is to provide a complete working project with threads, timers, events, and state machines all working together. To illustrate the concept, the example project implements a state-based self-test engine utilizing asynchronous communication between threads.\u003c/p\u003e\n\n\u003cp\u003eRelated GitHub repositories:\u003c/p\u003e\n\n\u003cul\u003e\n    \u003cli\u003e\u003ca href=\"https://github.com/endurodave/DelegateMQ\"\u003eDelegateMQ in C++\u003c/a\u003e - a header-only library enables function invocations on any callable, either synchronously, asynchronously, or on a remote endpoint\u003c/li\u003e\n    \u003cli\u003e\u003ca href=\"https://github.com/endurodave/StateMachine\"\u003eState Machine Design in C++\u003c/a\u003e - a compact C++ state machine\u003c/li\u003e\n\u003c/ul\u003e\n\n# Getting Started\n[CMake](https://cmake.org/) is used to create the project build files on any Windows or Linux machine.\n\n1. Clone the repository.\n2. From the repository root, run the following CMake command:   \n   `cmake -B Build .`\n3. Build and run the project within the `Build` directory. \n\n# Asynchronous Delegate Callbacks\n\n\u003cp\u003eIf you\u0026rsquo;re not familiar with a delegate, the concept is quite simple. A delegate can be thought of as a super function pointer. In C++, there\u0026#39;s no pointer type capable of pointing to all the possible function variations: instance member, virtual, const, static, and free (global). A function pointer can\u0026rsquo;t point to instance member functions, and pointers to member functions have all sorts of limitations. However, delegate classes can, in a type-safe way, point to any function provided the function signature matches. In short, a delegate points to any function with a matching signature to support anonymous function invocation.\u003c/p\u003e\n\n\u003cp\u003eAsynchronous delegates take the concept a bit further and permits anonymous invocation of any function on a client specified thread of control. The function and all arguments are safely called from a destination thread simplifying inter-thread communication and eliminating cross-threading errors.\u0026nbsp;\u003c/p\u003e\n\n\u003cp\u003eThe DelegateMQ library is used throughout to provide asynchronous callbacks making\u0026nbsp;an effective publisher and subscriber mechanism. A publisher exposes a delegate container interface and one or more subscribers add delegate instances to the container to receive anonymous callbacks.\u0026nbsp;\u003c/p\u003e\n\n\u003cp\u003eThe first place it\u0026#39;s used is within the \u003ccode\u003eSelfTest\u003c/code\u003e class where the \u003ccode\u003eSelfTest::CompletedCallback\u003c/code\u003e\u0026nbsp;delegate container allows subscribers to add delegates. Whenever a self-test completes a \u003ccode\u003eSelfTest::CompletedCallback\u003c/code\u003e callback is invoked notifying\u0026nbsp;registered clients. \u003ccode\u003eSelfTestEngine\u003c/code\u003e registers with both\u0026nbsp;\u003ccode\u003eCentrifugeTest\u003c/code\u003e and \u003ccode\u003ePressureTest\u003c/code\u003e to get asynchronously informed when the test is complete.\u003c/p\u003e\n\n\u003cp\u003eThe second location is the user interface registers\u0026nbsp;with \u003ccode\u003eSelfTestEngine::StatusCallback\u003c/code\u003e. This allows a client, running on another thread, to register and receive status callbacks during execution. \u003ccode\u003eMulticastDelegateSafe\u0026lt;\u0026gt;\u003c/code\u003e allows the client to specify the exact callback thread making is easy to avoid cross-threading errors.\u003c/p\u003e\n\n\u003cp\u003eThe final location is within the \u003ccode\u003eTimer\u003c/code\u003e class, which fires periodic callbacks on a registered callback function. A generic, low-speed timer capable of calling a function on the client-specified thread is quite useful for event driven state machines where you might want to poll for some condition to occur. In this case, the \u003ccode\u003eTimer\u003c/code\u003e class is used to inject poll events into the state machine instances.\u003c/p\u003e\n\n# Self-Test Subsystem\n\n\u003cp\u003eSelf-tests execute a series of tests on hardware and mechanical systems to ensure correct operation. In this example, there are four state machine classes implementing our self-test subsystem as shown in the inheritance diagram below:\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\u003cimg height=\"191\" src=\"Figure_1.png\" width=\"377\" /\u003e\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\u003cstrong\u003eFigure 1: Self-Test Subsystem Inheritance Diagram\u003c/strong\u003e\u003c/p\u003e\n\n## SelfTestEngine\n\n\u003cp\u003e\u003ccode\u003eSelfTestEngine\u003c/code\u003e is thread-safe and the main point of contact for client\u0026rsquo;s utilizing the self-test subsystem. \u003ccode\u003eCentrifugeTest \u003c/code\u003eand \u003ccode\u003ePressureTest \u003c/code\u003eare members of SelfTestEngine. \u003ccode\u003eSelfTestEngine \u003c/code\u003eis responsible for sequencing the individual self-tests in the correct order as shown in the state diagram below. \u0026nbsp;\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\u003cimg height=\"370\" src=\"Figure_2.png\" width=\"530\" /\u003e\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\u003cstrong\u003eFigure 2: SelfTestEngine State Machine\u003c/strong\u003e\u003c/p\u003e\n\n\u003cp\u003eThe \u003ccode\u003eStart \u003c/code\u003eevent initiates the self-test engine.\u0026nbsp;\u0026nbsp;\u003ccode\u003eSelfTestEngine::Start()\u003c/code\u003e is an asynchronous function that reinvokes the \u003ccode\u003eStart()\u003c/code\u003e function if the caller is not on the correct execution thread. Perform a simple check whether the caller is executing on the desired thread of control. If not, a temporary asynchronous delegate is created on the stack and then invoked. The delegate and all the caller\u0026rsquo;s original function arguments are duplicated on the heap and the function is reinvoked on \u003ccode\u003em_thread\u003c/code\u003e. This is an elegant way to create asynchronous API\u0026rsquo;s with the absolute minimum of effort. Since \u003ccode\u003eStart()\u003c/code\u003e is asynchronous, \u0026nbsp;it is thread-safe to be called by any client running on any thread.\u0026nbsp;\u003c/p\u003e\n\n\u003cpre lang=\"c++\"\u003e\nvoid SelfTestEngine::Start(const StartData* data)\n{\n    // Is the caller executing on m_thread?\n    if (m_thread.GetThreadId() != Thread::GetCurrentThreadId())\n    {\n        // Create an asynchronous delegate and reinvoke the function call on m_thread\n        auto delegate = MakeDelegate(this, \u0026amp;SelfTestEngine::Start, m_thread);\n        delegate(data);\n        return;\n    }\n\n    BEGIN_TRANSITION_MAP                                    // - Current State -\n        TRANSITION_MAP_ENTRY (ST_START_CENTRIFUGE_TEST)     // ST_IDLE\n        TRANSITION_MAP_ENTRY (CANNOT_HAPPEN)                // ST_COMPLETED\n        TRANSITION_MAP_ENTRY (CANNOT_HAPPEN)                // ST_FAILED\n        TRANSITION_MAP_ENTRY (EVENT_IGNORED)                // ST_START_CENTRIFUGE_TEST\n        TRANSITION_MAP_ENTRY (EVENT_IGNORED)                // ST_START_PRESSURE_TEST\n    END_TRANSITION_MAP(data)\n}\u003c/pre\u003e\n\n\u003cp\u003eWhen each self-test completes, the \u003ccode\u003eComplete \u003c/code\u003eevent fires causing the next self-test to start. After all of the tests are done, the state machine transitions to \u003ccode\u003eCompleted\u0026nbsp;\u003c/code\u003eand back to \u003ccode\u003eIdle\u003c/code\u003e. If the \u003ccode\u003eCancel \u003c/code\u003eevent is generated at any time during execution, a transition to the \u003ccode\u003eFailed \u003c/code\u003estate occurs.\u003c/p\u003e\n\n\u003cp\u003eThe \u003ccode\u003eSelfTest \u003c/code\u003ebase class provides three states common to all \u003ccode\u003eSelfTest\u003c/code\u003e-derived state machines: \u003ccode\u003eIdle\u003c/code\u003e, \u003ccode\u003eCompleted\u003c/code\u003e, and \u003ccode\u003eFailed\u003c/code\u003e. \u003ccode\u003eSelfTestEngine \u003c/code\u003ethen adds two more states: \u003ccode\u003eStartCentrifugeTest \u003c/code\u003eand \u003ccode\u003eStartPressureTest\u003c/code\u003e.\u003c/p\u003e\n\n\u003cp\u003e\u003ccode\u003eSelfTestEngine \u003c/code\u003ehas one public event function, \u003ccode\u003eStart()\u003c/code\u003e, that starts the self-tests. \u003ccode\u003eSelfTestEngine::StatusCallback\u003c/code\u003e is an asynchronous callback allowing client\u0026rsquo;s to register for status updates during testing. A \u003ccode\u003eThread \u003c/code\u003einstance is also contained within the class. All self-test state machine execution occurs on this thread.\u003c/p\u003e\n\n\u003cpre lang=\"c++\"\u003e\nclass SelfTestEngine : public SelfTest\n{\npublic:\n    // Clients register for asynchronous self-test status callbacks\n    static MulticastDelegateSafe\u0026lt;void(const SelfTestStatus\u0026amp;)\u0026gt; StatusCallback;\n\n    // Singleton instance of SelfTestEngine\n    static SelfTestEngine\u0026amp; GetInstance();\n\n    // Start the self-tests. This is a thread-safe asycnhronous function. \n    void Start(const StartData* data);\n\n    Thread\u0026amp; GetThread() { return m_thread; }\n    static void InvokeStatusCallback(std::string msg);\n\nprivate:\n    SelfTestEngine();\n    void Complete();\n\n    // Sub self-test state machines \n    CentrifugeTest m_centrifugeTest;\n    PressureTest m_pressureTest;\n\n    // Worker thread used by all self-tests\n    Thread m_thread;\n\n    StartData m_startData;\n\n    // State enumeration order must match the order of state method entries\n    // in the state map.\n    enum States\n    {\n        ST_START_CENTRIFUGE_TEST = SelfTest::ST_MAX_STATES,\n        ST_START_PRESSURE_TEST,\n        ST_MAX_STATES\n    };\n\n    // Define the state machine state functions with event data type\n    STATE_DECLARE(SelfTestEngine,     StartCentrifugeTest,    StartData)\n    STATE_DECLARE(SelfTestEngine,     StartPressureTest,      NoEventData)\n\n    // State map to define state object order. Each state map entry defines a\n    // state object.\n    BEGIN_STATE_MAP\n        STATE_MAP_ENTRY(\u0026amp;Idle)\n        STATE_MAP_ENTRY(\u0026amp;Completed)\n        STATE_MAP_ENTRY(\u0026amp;Failed)\n        STATE_MAP_ENTRY(\u0026amp;StartCentrifugeTest)\n        STATE_MAP_ENTRY(\u0026amp;StartPressureTest)\n    END_STATE_MAP    \n};\u003c/pre\u003e\n\n\u003cp\u003eAs mentioned previously, the \u003ccode\u003eSelfTestEngine \u003c/code\u003eregisters for asynchronous callbacks from each sub self-tests (i.e. \u003ccode\u003eCentrifugeTest \u003c/code\u003eand \u003ccode\u003ePressureTest\u003c/code\u003e) as shown below. When a sub self-test state machine completes, the \u003ccode\u003eSelfTestEngine::Complete()\u003c/code\u003e function is called. When a sub self-test state machine fails, the \u003ccode\u003eSelfTestEngine::Cancel()\u003c/code\u003e function is called.\u003c/p\u003e\n\n\u003cpre lang=\"c++\"\u003e\nSelfTestEngine::SelfTestEngine() :\n    SelfTest(ST_MAX_STATES),\n    m_thread(\u0026quot;SelfTestEngine\u0026quot;)\n{\n    // Register for callbacks when sub self-test state machines complete or fail\n    m_centrifugeTest.CompletedCallback += MakeDelegate(this, \u0026amp;SelfTestEngine::Complete, m_thread);\n    m_centrifugeTest.FailedCallback += MakeDelegate\u0026lt;SelfTest\u0026gt;(this, \u0026amp;SelfTest::Cancel, m_thread);\n    m_pressureTest.CompletedCallback += MakeDelegate(this, \u0026amp;SelfTestEngine::Complete, m_thread);\n    m_pressureTest.FailedCallback += MakeDelegate\u0026lt;SelfTest\u0026gt;(this, \u0026amp;SelfTest::Cancel, m_thread);\n}\u003c/pre\u003e\n\n\u003cp\u003eThe \u003ccode\u003eSelfTest\u0026nbsp;\u003c/code\u003ebase class generates the \u003ccode\u003eCompletedCallback \u003c/code\u003eand \u003ccode\u003eFailedCallback \u003c/code\u003ewithin the \u003ccode\u003eCompleted \u003c/code\u003eand \u003ccode\u003eFailed\u003c/code\u003e states respectively as seen below:\u003c/p\u003e\n\n\u003cpre lang=\"c++\"\u003e\nSTATE_DEFINE(SelfTest, Completed, NoEventData)\n{\n    SelfTestEngine::InvokeStatusCallback(\u0026quot;SelfTest::ST_Completed\u0026quot;);\n\n    if (CompletedCallback)\n        CompletedCallback();\n\n    InternalEvent(ST_IDLE);\n}\n\nSTATE_DEFINE(SelfTest, Failed, NoEventData)\n{\n    SelfTestEngine::InvokeStatusCallback(\u0026quot;SelfTest::ST_Failed\u0026quot;);\n\n    if (FailedCallback)\n        FailedCallback();\n\n    InternalEvent(ST_IDLE);\n}\u003c/pre\u003e\n\n\u003cp\u003eOne might ask why the state machines use asynchronous delegate callbacks. If the state machines are on the same thread, why not use a normal, synchronous callback instead? The problem to prevent is a callback into a currently executing state machine, that is, the call stack wrapping back around into the same class instance. For example, the following call sequence should be prevented: \u003ccode\u003eSelfTestEngine \u003c/code\u003ecalls \u003ccode\u003eCentrifugeTest \u003c/code\u003ecalls back \u003ccode\u003eSelfTestEngine\u003c/code\u003e. An asynchronous callback allows the stack to unwind and prevents this unwanted behavior.\u003c/p\u003e\n\n## CentrifugeTest\n\n\u003cp\u003eThe \u003ccode\u003eCentrifugeTest \u003c/code\u003estate machine diagram shown below implements the centrifuge self-test described in \u0026quot;\u003ca href=\"https://github.com/endurodave/StateMachine\"\u003e\u003cstrong\u003eState Machine Design in C++\u003c/strong\u003e\u003c/a\u003e\u0026quot;. \u003ccode\u003eCentrifugeTest\u003c/code\u003e uses\u0026nbsp;state machine inheritance by inheriting the \u003ccode\u003eIdle\u003c/code\u003e, \u003ccode\u003eCompleted\u003c/code\u003e and \u003ccode\u003eFailed\u003c/code\u003e states from the \u003ccode\u003eSelfTest\u003c/code\u003e class.\u0026nbsp;The difference here is that the \u003ccode\u003eTimer\u003c/code\u003e class is used to provide \u003ccode\u003ePoll \u003c/code\u003eevents via asynchronous delegate callbacks.\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\u003cimg height=\"765\" src=\"CentrifugeTest.png\" width=\"520\" /\u003e\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\u003cstrong\u003eFigure 3: CentrifugeTest State Machine\u003c/strong\u003e\u003c/p\u003e\n\n## Timer\n\n\u003cp\u003eThe \u003ccode\u003eTimer \u003c/code\u003eclass provides a common mechanism to receive function callbacks by registering with \u003ccode\u003eExpired\u003c/code\u003e. \u003ccode\u003eStart()\u003c/code\u003e starts the callbacks at a particular interval. \u003ccode\u003eStop()\u003c/code\u003e stops the callbacks.\u003c/p\u003e\n\n\u003cpre lang=\"c++\"\u003e\n/// @brief A timer class provides periodic timer callbacks on the client's \n/// thread of control. Timer is thread safe.\nclass Timer \n{\npublic:\n\t/// Client's register with Expired to get timer callbacks\n\tSinglecastDelegate\u003cvoid(void)\u003e Expired;\n\n\t/// Constructor\n\tTimer(void);\n\n\t/// Destructor\n\t~Timer(void);\n\n\t/// Starts a timer for callbacks on the specified timeout interval.\n\t/// @param[in]\ttimeout - the timeout in milliseconds.\n\tvoid Start(std::chrono::milliseconds timeout);\n\n\t/// Stops a timer.\n\tvoid Stop();\n...\u003c/pre\u003e\n\n\u003cp\u003eAll \u003ccode\u003eTimer \u003c/code\u003einstances are stored in a private static list. The \u003ccode\u003eThread::Process()\u003c/code\u003e loop periodically services all the timers within the list by calling \u003ccode\u003eTimer::ProcessTimers()\u003c/code\u003e. Client\u0026rsquo;s registered with \u003ccode\u003eExpired \u003c/code\u003eare invoked whenever the timer expires.\u003c/p\u003e\n\n\u003cpre lang=\"c++\"\u003e\n        case MSG_TIMER:\n            Timer::ProcessTimers();\n            break;\u003c/pre\u003e\n\n# Poll Events\n\n\u003cp\u003e\u003ccode\u003eCentrifugeTest \u003c/code\u003ehas a \u003ccode\u003eTimer\u003cstrong\u003e \u003c/strong\u003e\u003c/code\u003einstance and registers for callbacks. The callback function, a thread instance and a this pointer is provided to \u003ccode\u003eRegister()\u003c/code\u003e facilitating the asynchronous callback mechanism.\u003c/p\u003e\n\n\u003cpre lang=\"c++\"\u003e\n// Register for timer callbacks\nm_pollTimer.Expired = MakeDelegate(this, \u0026amp;CentrifugeTest::Poll, \u0026amp;SelfTestEngine::GetInstance().GetThread());\u003c/pre\u003e\n\n\u003cp\u003eWhen the timer is started using \u003ccode\u003eStart()\u003c/code\u003e, the \u003ccode\u003ePoll()\u003c/code\u003e event function is\u0026nbsp;periodically called at the interval specified. Notice that when the \u003ccode\u003ePoll()\u003c/code\u003e external event function is called, a transition to either WaitForAcceleration or WaitForDeceleration\u0026nbsp;is performed based on the current state of the state machine. If \u003ccode\u003ePoll()\u003c/code\u003e is called at the wrong time, the event is silently ignored.\u003c/p\u003e\n\n\u003cpre lang=\"c++\"\u003e\nvoid CentrifugeTest::Poll()\n{\n\u0026nbsp; \u0026nbsp; BEGIN_TRANSITION_MAP \u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; // - Current State -\n\u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; TRANSITION_MAP_ENTRY (EVENT_IGNORED) \u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; // ST_IDLE\n\u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; TRANSITION_MAP_ENTRY (EVENT_IGNORED) \u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; // ST_COMPLETED\n\u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; TRANSITION_MAP_ENTRY (EVENT_IGNORED) \u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; // ST_FAILED\n\u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; TRANSITION_MAP_ENTRY (EVENT_IGNORED) \u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; // ST_START_TEST\n\u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; TRANSITION_MAP_ENTRY (ST_WAIT_FOR_ACCELERATION) \u0026nbsp; \u0026nbsp;// ST_ACCELERATION\n\u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; TRANSITION_MAP_ENTRY (ST_WAIT_FOR_ACCELERATION) \u0026nbsp; \u0026nbsp;// ST_WAIT_FOR_ACCELERATION\n\u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; TRANSITION_MAP_ENTRY (ST_WAIT_FOR_DECELERATION) \u0026nbsp; \u0026nbsp;// ST_DECELERATION\n\u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; TRANSITION_MAP_ENTRY (ST_WAIT_FOR_DECELERATION) \u0026nbsp; \u0026nbsp;// ST_WAIT_FOR_DECELERATION\n\u0026nbsp; \u0026nbsp; END_TRANSITION_MAP(NULL)\n}\n\nSTATE_DEFINE(CentrifugeTest, Acceleration, NoEventData)\n{\n\u0026nbsp; \u0026nbsp; SelfTestEngine::InvokeStatusCallback(\u0026quot;CentrifugeTest::ST_Acceleration\u0026quot;);\n\n\u0026nbsp; \u0026nbsp; // Start polling while waiting for centrifuge to ramp up to speed\n\u0026nbsp; \u0026nbsp; m_pollTimer.Start(10);\n}\n\u003c/pre\u003e\n\n# User Interface\n\n\u003cp\u003eThe project doesn\u0026rsquo;t have a user interface except the text console output. For this example, the \u0026ldquo;user interface\u0026rdquo; just outputs self-test status messages on the user interface thread via the \u003ccode\u003eSelfTestEngineStatusCallback()\u003c/code\u003e function:\u003c/p\u003e\n\n\u003cpre lang=\"c++\"\u003e\nThread userInterfaceThread(\u0026quot;UserInterface\u0026quot;);\n\nvoid SelfTestEngineStatusCallback(const SelfTestStatus\u0026amp; status)\n{\n    // Output status message to the console \u0026quot;user interface\u0026quot;\n    cout \u0026lt;\u0026lt; status.message.c_str() \u0026lt;\u0026lt; endl;\n}\u003c/pre\u003e\n\n\u003cp\u003eBefore the self-test starts, the user interface registers with the \u003ccode\u003eSelfTestEngine::StatusCallback\u003c/code\u003e callback.\u003c/p\u003e\n\n\u003cpre\u003e\nSelfTestEngine::StatusCallback += \n\u0026nbsp;     MakeDelegate(\u0026amp;SelfTestEngineStatusCallback, userInterfaceThread);\u003c/pre\u003e\n\n\u003cp\u003eThe user interface thread here is just used to simulate callbacks to a GUI library normally running in a separate thread of control.\u003c/p\u003e\n\n# Run-Time\n\n\u003cp\u003eThe program\u0026rsquo;s \u003ccode\u003emain()\u003c/code\u003e function is shown below. It creates the two threads, registers for callbacks from \u003ccode\u003eSelfTestEngine\u003c/code\u003e, then calls \u003ccode\u003eStart()\u003c/code\u003e to start the self-tests.\u003c/p\u003e\n\n\u003cpre lang=\"c++\"\u003e\nint main(void)\n{    \n    // Create the worker threads\n    userInterfaceThread.CreateThread();\n    SelfTestEngine::GetInstance().GetThread().CreateThread();\n\n    // Register for self-test engine callbacks\n    SelfTestEngine::StatusCallback += MakeDelegate(\u0026amp;SelfTestEngineStatusCallback, userInterfaceThread);\n    SelfTestEngine::GetInstance().CompletedCallback += \n\u0026nbsp;        MakeDelegate(\u0026amp;SelfTestEngineCompleteCallback, userInterfaceThread);\n    \n    // Start the worker threads\n    ThreadWin::StartAllThreads();\n\n    // Start self-test engine\n    StartData startData;\n    startData.shortSelfTest = TRUE;\n    SelfTestEngine::GetInstance().Start(\u0026amp;startData);\n\n    // Wait for self-test engine to complete \n    while (!selfTestEngineCompleted)\n        Sleep(10);\n\n    // Unregister for self-test engine callbacks\n    SelfTestEngine::StatusCallback -= MakeDelegate(\u0026amp;SelfTestEngineStatusCallback, userInterfaceThread);\n    SelfTestEngine::GetInstance().CompletedCallback -= \n\u0026nbsp;        MakeDelegate(\u0026amp;SelfTestEngineCompleteCallback, userInterfaceThread);\n\n    // Exit the worker threads\n    userInterfaceThread.ExitThread();\n    SelfTestEngine::GetInstance().GetThread().ExitThread();\n\n    return 0;\n}\u003c/pre\u003e\n\n\u003cp\u003e\u003ccode\u003eSelfTestEngine \u003c/code\u003egenerates asynchronous callbacks on the \u003ccode\u003eUserInteface \u003c/code\u003ethread. The \u003ccode\u003eSelfTestEngineStatusCallback()\u003c/code\u003e callback outputs the message to the console.\u003c/p\u003e\n\n\u003cpre lang=\"c++\"\u003e\nvoid SelfTestEngineStatusCallback(const SelfTestStatus\u0026amp; status)\n{\n      // Output status message to the console \u0026quot;user interface\u0026quot;\n      cout \u0026lt;\u0026lt; status.message.c_str() \u0026lt;\u0026lt; endl;\n}\u003c/pre\u003e\n\n\u003cp\u003eThe \u003ccode\u003eSelfTestEngineCompleteCallback()\u003c/code\u003e callback sets a flag to let the \u003ccode\u003emain()\u003c/code\u003e loop exit.\u003c/p\u003e\n\n\u003cpre lang=\"c++\"\u003e\nvoid SelfTestEngineCompleteCallback()\n{\n      selfTestEngineCompleted = TRUE;\n}\u003c/pre\u003e\n\n\u003cp\u003eRunning the project outputs the following console messages:\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\u003cimg height=\"385\" src=\"Figure_4.png\" width=\"628\" /\u003e\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\u003cstrong\u003eFigure 4: Console Output\u003c/strong\u003e\u003c/p\u003e\n\n# Conclusion\n\n\u003cp\u003eThe \u003ccode\u003eStateMachine\u003c/code\u003e and \u003ccode\u003eDelegate\u0026lt;\u0026gt;\u003c/code\u003e implementations can be used separately. Each is useful unto itself. However, combining the two offers a novel framework for multithreaded state-driven application development. The article has shown how to coordinate the behavior of state machines when multiple threads are used,\u0026nbsp;which may not be entirely obvious when looking at simplistic, single threaded examples.\u003c/p\u003e\n\n\u003cp\u003eI\u0026rsquo;ve successfully used ideas similar to this on many different PC and embedded projects. The code is portable to any platform with a small amount of effort. I particularly like idea of asynchronous delegate callbacks because it effectively hides inter-thread communication and the organization of the state machines makes creating and maintaining self-tests easy.\u003c/p\u003e\n\n# References\n\n\u003cul\u003e\n\t\u003cli\u003e\u003ca href=\"https://github.com/endurodave/StateMachine\"\u003e\u003cstrong\u003eState Machine Design in C++\u003c/strong\u003e\u003c/a\u003e - by David Lafreniere\u003c/li\u003e\n    \u003cli\u003e\u003ca href=\"https://github.com/endurodave/DelegateMQ\"\u003e\u003cstrong\u003eAsynchronous Multicast Delegates in Modern C++\u003c/strong\u003e\u003c/a\u003e - by David Lafreniere\u003c/li\u003e\n\t\u003cli\u003e\u003ca href=\"https://github.com/endurodave/StateMachineWithThreads\"\u003e\u003cstrong\u003eC++ State Machine with Threads\u003c/strong\u003e\u003c/a\u003e \u0026ndash; by David Lafreniere\u003c/li\u003e\n\t\u003cli\u003e\u003ca href=\"https://github.com/endurodave/StdWorkerThread\"\u003e\u003cstrong\u003eC++ std::thread Event Loop with Message Queue and Timer\u003c/strong\u003e\u003c/a\u003e - by David Lafreniere\u003c/li\u003e\n\u003c/ul\u003e\n\n\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fendurodave%2Fstatemachinewithmoderndelegates","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fendurodave%2Fstatemachinewithmoderndelegates","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fendurodave%2Fstatemachinewithmoderndelegates/lists"}