{"id":21274160,"url":"https://github.com/endurodave/asyncstatemachine","last_synced_at":"2025-03-15T12:43:36.440Z","repository":{"id":259951203,"uuid":"879843820","full_name":"endurodave/AsyncStateMachine","owner":"endurodave","description":"Asynchronous State Machine in C++","archived":false,"fork":false,"pushed_at":"2025-03-05T23:12:10.000Z","size":393,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-06T00:20:52.397Z","etag":null,"topics":["asynchronous-programming","cpp","cross-platform","delegates","finite-state-machine","state-machine","threads"],"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-10-28T16:36:52.000Z","updated_at":"2025-03-05T23:11:13.000Z","dependencies_parsed_at":"2024-10-28T19:47:01.498Z","dependency_job_id":"829de2e5-2e17-4d18-af8b-fd5ee5201385","html_url":"https://github.com/endurodave/AsyncStateMachine","commit_stats":null,"previous_names":["endurodave/asyncstatemachine"],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/endurodave%2FAsyncStateMachine","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/endurodave%2FAsyncStateMachine/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/endurodave%2FAsyncStateMachine/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/endurodave%2FAsyncStateMachine/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/endurodave","download_url":"https://codeload.github.com/endurodave/AsyncStateMachine/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243732252,"owners_count":20338831,"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-programming","cpp","cross-platform","delegates","finite-state-machine","state-machine","threads"],"created_at":"2024-11-21T09:19:11.287Z","updated_at":"2025-03-15T12:43:36.431Z","avatar_url":"https://github.com/endurodave.png","language":"C++","readme":"![License MIT](https://img.shields.io/github/license/BehaviorTree/BehaviorTree.CPP?color=blue)\n[![conan Ubuntu](https://github.com/endurodave/AsyncStateMachine/actions/workflows/cmake_ubuntu.yml/badge.svg)](https://github.com/endurodave/AsyncStateMachine/actions/workflows/cmake_ubuntu.yml)\n[![conan Ubuntu](https://github.com/endurodave/AsyncStateMachine/actions/workflows/cmake_clang.yml/badge.svg)](https://github.com/endurodave/AsyncStateMachine/actions/workflows/cmake_clang.yml)\n[![conan Windows](https://github.com/endurodave/AsyncStateMachine/actions/workflows/cmake_windows.yml/badge.svg)](https://github.com/endurodave/AsyncStateMachine/actions/workflows/cmake_windows.yml)\n\n# Asynchronous State Machine Design in C++\n\nAn asynchronous C++ state machine implemented using an asynchronous delegate library.\n\n# Table of Contents\n- [Asynchronous State Machine Design in C++](#asynchronous-state-machine-design-in-c)\n- [Table of Contents](#table-of-contents)\n- [Introduction](#introduction)\n  - [References](#references)\n- [CMake Build](#cmake-build)\n  - [Windows Visual Studio](#windows-visual-studio)\n  - [Linux Make](#linux-make)\n- [Asynchronous Delegates](#asynchronous-delegates)\n- [AsyncStateMachine](#asyncstatemachine)\n- [Motor Example](#motor-example)\n- [Self-Test Subsystem Example](#self-test-subsystem-example)\n  - [SelfTestEngine](#selftestengine)\n  - [CentrifugeTest](#centrifugetest)\n  - [Timer](#timer)\n  - [Run-Time](#run-time)\n- [Conclusion](#conclusion)\n\n# Introduction\n\nA 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 “thread”. More complex systems use multithreading to divvy up the processing.\n\nThis repository covers how to use a C++ state machines within the context of a multithreaded environment. A new `AsyncStateMachine` class extends the functionality of `StateMachine` documented in [State Machine Design in C++](https://github.com/endurodave/StateMachine). The asynchronous state machine utilizes a C++ `std::thread` for thread support as covered within [C++ std::thread Event Loop](https://github.com/endurodave/StdWorkerThread). Any OS thread is easy adapted. The `std::thread` was convenient for cross-platform Windows and Linux usage.\n\nThe `AsyncStateMachine` leverages the asynchronous delegate library documented in [DelegateMQ](https://github.com/endurodave/DelegateMQ). In short, the library is capable of targeting any callable function synchronously or asynchronously.\n\nThe 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 state machines.\n\nThe state machine and delegate implementations will not be reexplained here. This article focuses on the `AsyncStateMachine` enhancement and integration with the `DelegateMQ` library.\n\nCMake is used to create the build files. CMake is free and open-source software. Windows, Linux and other toolchains are supported. See the `CMakeLists.txt` file for more information.\n\n## References\n\n* [State Machine Design in C++](https://github.com/endurodave/StateMachine) - A compact C++ finite state machine (FSM) implementation.\n\n* [DelegateMQ in C++](https://github.com/endurodave/DelegateMQ) - A C++ delegate library capable of targeting any callable function synchronously or asynchronously.\n\n* [C++ std::thread Event Loop](https://github.com/endurodave/StdWorkerThread) - A worker thread using the C++ thread support library.\n\n# CMake Build\n[CMake](https://cmake.org/) is used to create the project build files. See `CMakeLists.txt` for more information.\n\n## Windows Visual Studio\n\n`cmake -G \"Visual Studio 17 2022\" -A Win32 -B Build -S .`\n\n## Linux Make \n\n`cmake -G \"Unix Makefiles\" -B Build -S .`\n\n# Asynchronous Delegates\n\nIf you’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's no pointer type capable of pointing to all the possible function variations: instance member, virtual, const, static, lambda, and free (global). A function pointer can’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.\n\nAsynchronous delegates take the concept 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. The repository [DelegateMQ](https://github.com/endurodave/DelegateMQ) covers usage patterns in detail.\n\nThe `AsyncStateMachine` uses asynchronous delegates to inject external events into a state machine instance.\n\n# AsyncStateMachine\n\nThe `AsyncStateMachine` inherits from `StateMachine`. Create an state machine thread using `CreateThread()` or alternatively attach an existing thread using `SetThread()`. \n\n```cpp\nclass AsyncStateMachine : public StateMachine\n{\npublic:\n    ///\tConstructor.\n    ///\t@param[in] maxStates - the maximum number of state machine states.\n    AsyncStateMachine(BYTE maxStates, BYTE initialState = 0);\n\n    /// Destructor\n    virtual ~AsyncStateMachine();\n\n    /// Create a new thread for this state machine\n    /// @param[in] threadName - the thread name\n    void CreateThread(const std::string\u0026 threadName);\n\n    /// Set a thread for this state machine\n    /// @param[in] thread - a Thread instance\n    void SetThread(std::shared_ptr\u003cThread\u003e thread) { m_thread = thread;  }\n\n    /// Get the thread attached to this state machine\n    /// @return A Thread instance\n    std::shared_ptr\u003cThread\u003e GetThread() { return m_thread; }\n\nprotected:\n    /// @see StateMachine::ExternalEvent()\n    void ExternalEvent(BYTE newState, const EventData* pData = NULL);\n\nprivate:\n    // The worker thread instance the state machine executes on\n    std::shared_ptr\u003cThread\u003e m_thread = nullptr;\n};\n```\n\nConverting a state machine from `StateMachine` to `AsyncStateMachine` requires these changes:\n\n1. Inherit from `AsyncStateMachine`.\n2. Add `ASYNC_INVOKE()` to all external event functions.\n3. Call `CreateThread()` or `SetThread()` to attach a thread.\n\n# Motor Example\n\nThe `Motor` state machine diagram is shown below.\n\n![Motor State Machine](Motor.png)\n\n`Motor` inherits from `AsyncStateMachine` and implements two external events and four states.\n\n```cpp\n#ifndef _MOTOR_H\n#define _MOTOR_H\n\n#include \"AsyncStateMachine.h\"\n\nclass MotorData : public EventData\n{\npublic:\n\tINT speed;\n};\n\n// Motor is an asynchronous state machine. All external events are executed\n// on the Motor thread of control. The DelegateMQ library handles invoking \n// functions asynchronously.\nclass Motor : public AsyncStateMachine\n{\npublic:\n\tMotor();\n\n\t// External events taken by this state machine\n\tvoid SetSpeed(MotorData* data);\n\tvoid Halt();\n\nprivate:\n\tINT m_currentSpeed; \n\n\t// State enumeration order must match the order of state method entries\n\t// in the state map.\n\tenum States\n\t{\n\t\tST_IDLE,\n\t\tST_STOP,\n\t\tST_START,\n\t\tST_CHANGE_SPEED,\n\t\tST_MAX_STATES\n\t};\n\n\t// Define the state machine state functions with event data type\n\tSTATE_DECLARE(Motor, \tIdle,\t\t\tNoEventData)\n\tSTATE_DECLARE(Motor, \tStop,\t\t\tNoEventData)\n\tSTATE_DECLARE(Motor, \tStart,\t\t\tMotorData)\n\tSTATE_DECLARE(Motor, \tChangeSpeed,\tMotorData)\n\n\t// State map to define state object order. Each state map entry defines a\n\t// state object.\n\tBEGIN_STATE_MAP\n\t\tSTATE_MAP_ENTRY(\u0026Idle)\n\t\tSTATE_MAP_ENTRY(\u0026Stop)\n\t\tSTATE_MAP_ENTRY(\u0026Start)\n\t\tSTATE_MAP_ENTRY(\u0026ChangeSpeed)\n\tEND_STATE_MAP\t\n};\n\n#endif\n```\n\nThe `Motor::SetSpeed()` external event uses the `ASYNC_INVOKE()` macro to invoke the function on the `Motor` thread of control using the `DelegateMQ` library. `ASYNC_INVOKE()` is non-blocking and inserts a message into the `Motor` thread's event queue with pointers to the function and argument, then early returns. Later, the `Motor` thread dequeues the message and calls `Motor::SetSpeed()` on `Motor`'s thread context. The `SetSpeed()` external event function is thread-safe callable by anyone.\n\n```cpp\nvoid Motor::SetSpeed(MotorData* data)\n{\n\t/* ASYNC_INVOKE below effectively executes the following code:\n\t// Is this function call executing on this state machine thread?\n\tif (GetThreadId() != Thread::GetCurrentThreadId())\n\t{\n\t\t// Asynchronously re-invoke the SetSpeed() event on Motor's thread\n\t\tAsyncInvoke(this, \u0026Motor::SetSpeed, *GetThread(), data);\n\t\treturn;\n\t}*/\n\n\t// Asynchronously invoke Motor::SetSpeed on the Motor thread of control\n\tASYNC_INVOKE(Motor, SetSpeed, data);\n\n\tBEGIN_TRANSITION_MAP\t\t\t              \t\t\t// - Current State -\n\t\tTRANSITION_MAP_ENTRY (ST_START)\t\t\t\t\t\t// ST_IDLE\n\t\tTRANSITION_MAP_ENTRY (CANNOT_HAPPEN)\t\t\t\t// ST_STOP\n\t\tTRANSITION_MAP_ENTRY (ST_CHANGE_SPEED)\t\t\t\t// ST_START\n\t\tTRANSITION_MAP_ENTRY (ST_CHANGE_SPEED)\t\t\t\t// ST_CHANGE_SPEED\n\tEND_TRANSITION_MAP(data)\n}\n```\n\nThe `Motor` constructor creates the thread at runtime.\n\n```cpp\nMotor::Motor() :\n\tAsyncStateMachine(ST_MAX_STATES),\n\tm_currentSpeed(0)\n{\n\tCreateThread(\"Motor\");\n}\n```\n\n# Self-Test Subsystem Example\n\nSelf-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:\n\n![Self-Test Subsystem](Figure_1.png)\n\n## SelfTestEngine\n\n`SelfTestEngine` is thread-safe and the main point of contact for client’s utilizing the self-test subsystem. `CentrifugeTest` and `PressureTest` are members of `SelfTestEngine`. `SelfTestEngine` is responsible for sequencing the individual self-tests in the correct order as shown in the state diagram below. \n\n![Self-Test Engine](Figure_2.png)\n\nThe `Start` event initiates the self-test engine. `SelfTestEngine::Start()` is an asynchronous function that reinvokes the function on the state machine's execution thread. \n\n```cpp\nvoid SelfTestEngine::Start(const StartData* data)\n{\n\t// Asynchronously invoke SelfTestEngine::Start on the SelfTestEngine thread of control\n\tASYNC_INVOKE(SelfTestEngine, Start, data);\n\n\tBEGIN_TRANSITION_MAP\t\t\t              \t\t\t// - Current State -\n\t\tTRANSITION_MAP_ENTRY (ST_START_CENTRIFUGE_TEST)\t\t// ST_IDLE\n\t\tTRANSITION_MAP_ENTRY (CANNOT_HAPPEN)\t\t\t\t// ST_COMPLETED\n\t\tTRANSITION_MAP_ENTRY (CANNOT_HAPPEN)\t\t\t\t// ST_FAILED\n\t\tTRANSITION_MAP_ENTRY (EVENT_IGNORED)\t\t\t\t// ST_START_CENTRIFUGE_TEST\n\t\tTRANSITION_MAP_ENTRY (EVENT_IGNORED)\t\t\t\t// ST_START_PRESSURE_TEST\n\tEND_TRANSITION_MAP(data)\n}\n```\n\nWhen each self-test completes, the `Complete` event fires causing the next self-test to start. After all of the tests are done, the state machine transitions to `Completed` and back to `Idle`. If the `Cancel` event is generated at any time during execution, a transition to the `Failed` state occurs.\n\nThe `SelfTest` base class provides three states common to all `SelfTest`-derived state machines: `Idle`, `Completed`, and `Failed`. `SelfTestEngine` then adds two more states: `StartCentrifugeTest` and `StartPressureTest`.\n\n`SelfTestEngine` has one public event function, `Start()`, that starts the self-tests. `SelfTestEngine::StatusCallback` is an asynchronous callback allowing client’s to register for status updates during testing.\n\n```cpp\nclass SelfTestEngine : public SelfTest\n{\npublic:\n\t// Clients register for asynchronous self-test status callbacks\n\tstatic MulticastDelegateSafe\u003cvoid(const SelfTestStatus\u0026)\u003e StatusCallback;\n\n\t// Singleton instance of SelfTestEngine\n\tstatic SelfTestEngine\u0026 GetInstance();\n\n\t// Start the self-tests. This is a thread-safe asycnhronous function. \n\tvoid Start(const StartData* data);\n\n\tstatic void InvokeStatusCallback(std::string msg);\n\nprivate:\n\tSelfTestEngine();\n\tvoid Complete();\n\n\t// Sub self-test state machines \n\tCentrifugeTest m_centrifugeTest;\n\tPressureTest m_pressureTest;\n\n\tStartData m_startData;\n\n\t// State enumeration order must match the order of state method entries\n\t// in the state map.\n\tenum States\n\t{\n\t\tST_START_CENTRIFUGE_TEST = SelfTest::ST_MAX_STATES,\n\t\tST_START_PRESSURE_TEST,\n\t\tST_MAX_STATES\n\t};\n\n\t// Define the state machine state functions with event data type\n\tSTATE_DECLARE(SelfTestEngine, \tStartCentrifugeTest,\tStartData)\n\tSTATE_DECLARE(SelfTestEngine, \tStartPressureTest,\t\tNoEventData)\n\n\t// State map to define state object order. Each state map entry defines a\n\t// state object.\n\tBEGIN_STATE_MAP\n\t\tSTATE_MAP_ENTRY(\u0026Idle)\n\t\tSTATE_MAP_ENTRY(\u0026Completed)\n\t\tSTATE_MAP_ENTRY(\u0026Failed)\n\t\tSTATE_MAP_ENTRY(\u0026StartCentrifugeTest)\n\t\tSTATE_MAP_ENTRY(\u0026StartPressureTest)\n\tEND_STATE_MAP\t\n};\n```\n\nAs mentioned previously, the `SelfTestEngine` registers for asynchronous callbacks from each sub self-tests (i.e. `CentrifugeTest` and `PressureTest`) as shown below. When a sub self-test state machine completes, the `SelfTestEngine::Complete()` function is called. When a sub self-test state machine fails, the `SelfTestEngine::Cancel()` function is called.\n\nNote that `m_centrifugeTest` and `m_pressureTest` share the same `SelfTestEngine` thread instance. This means all self-tests execute on the same thread.\n\n```cpp\nSelfTestEngine::SelfTestEngine() :\n\tSelfTest(\"SelfTestEngine\",  ST_MAX_STATES)\n{\n\t// Set owned state machines to execute on SelfTestEngine thread of control\n\tm_centrifugeTest.SetThread(GetThread());\n\tm_pressureTest.SetThread(GetThread());\n\n\t// Register for callbacks when sub self-test state machines complete or fail\n\tm_centrifugeTest.CompletedCallback += MakeDelegate(this, \u0026SelfTestEngine::Complete);\n\tm_centrifugeTest.FailedCallback += MakeDelegate\u003cSelfTest\u003e(this, \u0026SelfTest::Cancel);\n\tm_pressureTest.CompletedCallback += MakeDelegate(this, \u0026SelfTestEngine::Complete);\n\tm_pressureTest.FailedCallback += MakeDelegate\u003cSelfTest\u003e(this, \u0026SelfTest::Cancel);\n}\n```\n\nThe `SelfTest` base class generates the `CompletedCallback` and `FailedCallback` within the `Completed` and `Failed` states respectively as seen below:\n\n```cpp\nSTATE_DEFINE(SelfTest, Completed, NoEventData)\n{\n\tSelfTestEngine::InvokeStatusCallback(\"SelfTest::ST_Completed\");\n\n\tif (CompletedCallback)\n\t\tCompletedCallback();\n\n\tInternalEvent(ST_IDLE);\n}\n\nSTATE_DEFINE(SelfTest, Failed, NoEventData)\n{\n\tSelfTestEngine::InvokeStatusCallback(\"SelfTest::ST_Failed\");\n\n\tif (FailedCallback)\n\t\tFailedCallback();\n\n\tInternalEvent(ST_IDLE);\n}\n```\n\nOne 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: `SelfTestEngine` calls `CentrifugeTest` calls back `SelfTestEngine`. An asynchronous callback allows the stack to unwind and prevents this unwanted behavior.\n\n## CentrifugeTest\n\nThe `CentrifugeTest` state machine diagram shown below implements the centrifuge self-test. `CentrifugeTest` uses state machine inheritance by inheriting the `Idle`, `Completed` and `Failed` states from the `SelfTest` class. The `Timer` class is used to provide `Poll` events via asynchronous delegate callbacks.\n\n![Centrifuge Test State Machine](CentrifugeTest.png)\n\n## Timer\n\nThe `Timer` class provides a common mechanism to receive function callbacks by registering with `Expired`. `Start()` starts the callbacks at a particular interval. `Stop()` stops the callbacks.\n\n```cpp\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\n\t/// Gets the enabled state of a timer.\n\t/// @return\t\tTRUE if the timer is enabled, FALSE otherwise.\n\tbool Enabled() { return m_enabled; }\n\n\t/// Get the current time in ticks. \n\t/// @return The current time in ticks. \n    static std::chrono::milliseconds GetTime();\n\n    // etc...\n```\n\n## Run-Time\n\nThe program’s `main()` function is shown below. It creates the two threads, registers for callbacks from `SelfTestEngine`, then calls `Start()` to start the self-tests.\n\n```cpp\nint main(void)\n{\t\n\ttry\n\t{\t\n\t\t// Create the worker thread\n\t\tuserInterfaceThread.CreateThread();\n\n\t\t// *** Begin async Motor test ***\n\t\tMotor motor;\n\n\t\tauto data = new MotorData();\n\t\tdata-\u003espeed = 100;\n\t\tmotor.SetSpeed(data);\n\n\t\tdata = new MotorData();\n\t\tdata-\u003espeed = 200;\n\t\tmotor.SetSpeed(data);\n\n\t\tmotor.Halt();\n\t\t// *** End async Motor test ***\n\n\t\t// *** Begin async self test ***\t\t\n\t\t// Register for self-test engine callbacks\n\t\tSelfTestEngine::StatusCallback += MakeDelegate(\u0026SelfTestEngineStatusCallback, userInterfaceThread);\n\t\tSelfTestEngine::GetInstance().CompletedCallback += MakeDelegate(\u0026SelfTestEngineCompleteCallback, userInterfaceThread);\n\n\t\t// Start self-test engine\n\t\tStartData startData;\n\t\tstartData.shortSelfTest = TRUE;\n\t\tSelfTestEngine::GetInstance().Start(\u0026startData);\n\n\t\t// Wait for self-test engine to complete \n\t\twhile (!selfTestEngineCompleted)\n\t\t\tstd::this_thread::sleep_for(std::chrono::milliseconds(10));\n\n\t\t// Unregister for self-test engine callbacks\n\t\tSelfTestEngine::StatusCallback -= MakeDelegate(\u0026SelfTestEngineStatusCallback, userInterfaceThread);\n\t\tSelfTestEngine::GetInstance().CompletedCallback -= MakeDelegate(\u0026SelfTestEngineCompleteCallback, userInterfaceThread);\n\t\t// *** End async self test **\n\n\t\t// Exit the worker thread\n\t\tuserInterfaceThread.ExitThread();\n\t}\n\tcatch (...)\n\t{\n\t\tstd::cerr \u003c\u003c \"Exception!\" \u003c\u003c std::endl;\n\t}\n\n\treturn 0;\n}\n```\n\n`SelfTestEngine` generates asynchronous callbacks on the `UserInteface` thread. The `SelfTestEngineStatusCallback()` callback outputs the message to the console.\n\n```cpp\nvoid SelfTestEngineStatusCallback(const SelfTestStatus\u0026 status)\n{\n\t// Output status message to the console \"user interface\"\n\tcout \u003c\u003c status.message.c_str() \u003c\u003c endl;\n}\n\nvoid SelfTestEngineCompleteCallback()\n{\n\tselfTestEngineCompleted = TRUE;\n}\n```\n\n# Conclusion\n\nThe `AsyncStateMachine` class allows state machine objects to operate in their own thread context. The `DelegateMQ` library offer cross-thread event generation for easy collaboration between state machines.\n\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fendurodave%2Fasyncstatemachine","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fendurodave%2Fasyncstatemachine","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fendurodave%2Fasyncstatemachine/lists"}