Skip to content

Commit

Permalink
Snapshot for the 1.0.4 release with updated docs and fixed param for …
Browse files Browse the repository at this point in the history
…the onEnteredState
  • Loading branch information
shuvalov-mdb committed Oct 30, 2020
1 parent dd853d7 commit 5a78ee2
Show file tree
Hide file tree
Showing 9 changed files with 544 additions and 308 deletions.
15 changes: 14 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ using [Xstate](https://github.com/davidkpiano/xstate) into C++ generated SM, no
* Arbitrary user-defined data structure (called Context) can be stored in the SM
* Any event can have an arbitrary user-defined payload attached. The event payload is propagated to related callbacks

## Resources
* Quick start is right below
* [Tutorial](TUTORIAL.md)

## Install and Quick Start Tutorial

### 1. Install the xstate-cpp-generator TypeScript package, locally (or globally with `-g` option):
Expand Down Expand Up @@ -82,8 +86,14 @@ CppGen.generateCpp({
```
To visualize this State Machine copy-paste the 'Machine' method call to the [online vizualizer](https://xstate.js.org/viz/).

### 3. And generate C++ with
### 3. Generate C++
Install all required dependencies:

```bash
npm install
```

And run the C++ generator:
```bash
ts-node engineer.ts
```
Expand Down Expand Up @@ -122,3 +132,6 @@ and run it with:
### V 1.0.3
* Full support of entry, exit and transition Actions
* Multi-threading bugfixes
### V 1.0.4
* Converted `onEnteredState()` from move sematics `&&` to shared_ptr
* Started Tutorial
58 changes: 58 additions & 0 deletions TUTORIAL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# C++ State Machine code generator for Xstate Tutorial
[Back to README page](README.md) for introduction.

This tutorial is based on the model [engineer.ts](demo-project/engineer.ts) and the demo project [engineer_demo.cpp](demo-project/engineer_demo.cpp).

## Install the package and generate the code

Please follow the [Quick Start guide](README.md#install-and-quick-start-tutorial) to generate the code from `engineer.ts` model.

## The generated header walkthrough

### What happens when an Event is posted

The State Machine header generated by the demo model is [engineer_sm.h](demo-project/engineer_sm.h). Let's follow a fragment [engineer.ts](demo-project/engineer.ts) to find how it maps to the generated C++ code. In the model we have one of the state transitions declared as:

```TypeScript
sleeping: {
entry: 'startWakeupTimer',
exit: 'morningRoutine',
on: {
'TIMER': { target: 'working', actions: ['startHungryTimer', 'startTiredTimer'] },
}
},
```
it means that if the Engineer SM is in state `sleeping`, it will transition to the `working` state when the `TIMER` event is posted.

The State Machine is declared as:
```C++
template <typename SMSpec = DefaultEngineerSMSpec<std::nullptr_t>>
class EngineerSM {
...
}
```
The template argument `SMSpec` has a convenient default already generated, but it can be replaced with another template struct to do the full customization of the State Machine at compile time.
To post the `TIMER` event call this method:
```C++
void postEventTimer(std::shared_ptr<TimerPayload> payload);
```
Here the `TimerPayload` is declared in the `SMSpec` and by declaring this struct you can use an arbitrary class as `TimerPayload`.

When `TIMER` is posted, and the machine is in `sleeping` state, the generated State Machine engine will do the following steps:

* Call the method `morningRoutine(EngineerSM<DefaultEngineerSMSpec>* sm)`, which is an exit action from the `sleeping` state
* Call the virtual method `onLeavingSleepingState(State nextState)`. Such exit methods are generated for every state
* Call method `startHungryTimer(EngineerSM<DefaultEngineerSMSpec>* sm, std::shared_ptr<EventTimerPayload>)` for the transition action. Here the `std::shared_ptr<EventTimerPayload>)` is the same event payload that was sent with the `postEventTimer()` call
* Call method `startTiredTimer(EngineerSM<DefaultEngineerSMSpec>* sm, std::shared_ptr<EventTimerPayload>)`, which is another modeled transition action
* Call method `void onEnteringStateWorkingOnTIMER(State nextState, std::shared_ptr<TimerPayload> payload)`. Again, the `payload` is probagated to this callback as well.
* As `working` state was declared with the following entry events:
```TypeScript
working: {
entry: ['checkEmail', 'startHungryTimer', 'checkIfItsWeekend' ],
```
the action callbacks `checkEmail(EngineerSM<DefaultEngineerSMSpec>* sm)`, `startHungryTimer(EngineerSM<DefaultEngineerSMSpec>* sm)` and `checkIfItsWeekend(EngineerSM<DefaultEngineerSMSpec>* sm)` will be invoked as well
* Note: all those actions above were invoked while the SM state is still `sleepng`
* Transition to the new state `working`. This involves changing the internal data structures protected under `std::mutex` lock. Thus the SM is transitioned to the next state atomically
* Invoke the callback `onEnteredStateWorkingOnTIMER(std::shared_ptr<TimerPayload> payload)`
26 changes: 13 additions & 13 deletions demo-project/engineer_sm.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* https://github.com/shuvalov-mdb/xstate-cpp-generator , @author Andrew Shuvalov
*
* Please do not edit. If changes are needed, regenerate using the TypeScript template 'engineer.ts'.
* Generated at Fri Oct 30 2020 01:59:52 GMT+0000 (Coordinated Universal Time) from Xstate definition 'engineer.ts'.
* Generated at Fri Oct 30 2020 16:38:31 GMT+0000 (Coordinated Universal Time) from Xstate definition 'engineer.ts'.
* The simplest command line to run the generation:
* ts-node 'engineer.ts'
*/
Expand Down Expand Up @@ -315,19 +315,19 @@ class EngineerSM {
* It is safe to call postEvent*() to trigger the next transition from this method.
* @param payload ownership is transferred to the user.
*/
virtual void onEnteredStateWorkingOnTIMER(TimerPayload&& payload) {
virtual void onEnteredStateWorkingOnTIMER(std::shared_ptr<TimerPayload> payload) {
std::lock_guard<std::mutex> lck(_lock);
logTransition(EngineerSMTransitionPhase::ENTERED_STATE, _currentState.currentState, State::working);
}
virtual void onEnteredStateEatingOnHUNGRY(HungryPayload&& payload) {
virtual void onEnteredStateEatingOnHUNGRY(std::shared_ptr<HungryPayload> payload) {
std::lock_guard<std::mutex> lck(_lock);
logTransition(EngineerSMTransitionPhase::ENTERED_STATE, _currentState.currentState, State::eating);
}
virtual void onEnteredStateSleepingOnTIRED(TiredPayload&& payload) {
virtual void onEnteredStateSleepingOnTIRED(std::shared_ptr<TiredPayload> payload) {
std::lock_guard<std::mutex> lck(_lock);
logTransition(EngineerSMTransitionPhase::ENTERED_STATE, _currentState.currentState, State::sleeping);
}
virtual void onEnteredStateWeekendOnENOUGH(EnoughPayload&& payload) {
virtual void onEnteredStateWeekendOnENOUGH(std::shared_ptr<EnoughPayload> payload) {
std::lock_guard<std::mutex> lck(_lock);
logTransition(EngineerSMTransitionPhase::ENTERED_STATE, _currentState.currentState, State::weekend);
}
Expand Down Expand Up @@ -617,23 +617,23 @@ void EngineerSM<SMSpec>::_transitionActionsHelper(State fromState, Event event,
template<typename SMSpec>
void EngineerSM<SMSpec>::_enteredStateHelper(Event event, State newState, void* payload) {
if (event == Event::TIMER && newState == State::working) {
TimerPayload* typedPayload = static_cast<TimerPayload*>(payload);
onEnteredStateWorkingOnTIMER(std::move(*typedPayload));
std::shared_ptr<TimerPayload>* typedPayload = static_cast<std::shared_ptr<TimerPayload>*>(payload);
onEnteredStateWorkingOnTIMER(*typedPayload);
return;
}
if (event == Event::HUNGRY && newState == State::eating) {
HungryPayload* typedPayload = static_cast<HungryPayload*>(payload);
onEnteredStateEatingOnHUNGRY(std::move(*typedPayload));
std::shared_ptr<HungryPayload>* typedPayload = static_cast<std::shared_ptr<HungryPayload>*>(payload);
onEnteredStateEatingOnHUNGRY(*typedPayload);
return;
}
if (event == Event::TIRED && newState == State::sleeping) {
TiredPayload* typedPayload = static_cast<TiredPayload*>(payload);
onEnteredStateSleepingOnTIRED(std::move(*typedPayload));
std::shared_ptr<TiredPayload>* typedPayload = static_cast<std::shared_ptr<TiredPayload>*>(payload);
onEnteredStateSleepingOnTIRED(*typedPayload);
return;
}
if (event == Event::ENOUGH && newState == State::weekend) {
EnoughPayload* typedPayload = static_cast<EnoughPayload*>(payload);
onEnteredStateWeekendOnENOUGH(std::move(*typedPayload));
std::shared_ptr<EnoughPayload>* typedPayload = static_cast<std::shared_ptr<EnoughPayload>*>(payload);
onEnteredStateWeekendOnENOUGH(*typedPayload);
return;
}
}
Expand Down
Loading

0 comments on commit 5a78ee2

Please sign in to comment.