|
4 years ago | |
---|---|---|
c++-mode | 4 years ago | |
doc | 6 years ago | |
examples | 6 years ago | |
.gitignore | 6 years ago | |
LICENSE | 6 years ago | |
README.md | 6 years ago | |
yasnippet_function.el | 4 years ago |
Meta state machine is part of boost and is according to the boost documentation
A library allowing you to easily and quickly
define state machines of very high performance.
Since I'm a big supporter for state-machines this seemed like a really
nice library, but unfortunately it turned out to be a real pain when it
boiled down to coding by-hand. As the name says the this is based on templates
which in it self can be pretty daunting. So after done a couple of the
state machines I realized the that either this needs to be done by some
kind of GUI or at least some simplified manner. Since I'm either a
great GUI designer or writer, I did it my way which I find easy and
satisfying, I also icluded a nice feature creting a picture of the
statechart using doxygen and plantuml e.g
Using Emacs and YA-Snippets. Since there aren't many other tools for doing this job, i thought i make this open source.
As mentioned before this work is based on Yasnippets, and emacs. So
YAsnippet needs to be installed in emacs (see Yasnippet easily installed
via Melp ). Assuming that you installed YAsnippet and enabled it
(M-x yas-global-mode)
or insert into .emacs
(yas-global-mode 1)
its time to download the latest release of MsmState.
Uncompress the file and add it to the yas-installed-snippets-dir
(checkout (C-h C-v) yas-installed-snippets-dir
)
directory (in my case it is "~/.emacs.d/snippets").
Edit your emacs startup file by adding the following lines
(yas-global-mode 1)
(load-file "~./.emacs.d/snippets/yasnippet_function.el")
The next section describes how to setup doxygen
This is not a tutorial how to setup DoxyGen since there are already several great once. In the later versions (Im using 1.8.11) there is support for Plantuml, which is a java that allows you to create uml diagrams in an easy and comprehensiv manner. As mentioned before doxygen has support for it, all one needs to do is to download plantuml.jar create a doxygen configuration file by issuing:
doxygen -g
This will create a Doxyfile
which needs to be edit.
By opening the Doxyfile
and search for
PLANTUML_JAR_PATH =
Add the path to the jar file. Make sure you don't include the actual jar file name.
If you want to test if it works paste this into a new file.
/**
* \brief Test
*
* \startuml
* Sender->Receiver
* Receiver->Sender
* \enduml
*
* \param param
* \return return type
*/
class Test
{
public:
Test();
virtual ~Test();
};
then run doxygen
This should produce a html directory where you should be able to find
the index.html
and class reference to Test.
and a uml sequence diagram:
If successful you should be ready to go.
This section will describe the development of a msm state machine using yasnippet provided. We start by creating a UML state chart diagrams for which we want to produce a msm state machine.
This is what we want, so lets get to it.
First we start by creating CmdKeyStateMachine.hpp
file by issuing:
emacs CmdKeyStateMachine.hpp
Make sure you have the menu YASnippet->c++-mode->msm->statemachine
visible.
now type the command
statemachine<TAB>
This should produce a skeleton for the statemachine, the cursor will
now be placed on the doxygen comment for which name the statemachine
should have, by default it uses the same name as the file excluding
the extension. In this case it should be CmdKeyStatemachine
, this
can obviously be changed to whatever name you want. The change will be
reflected throughout the file. When done editing the name press the
TAB key and the cursor will jump to the next editing section, in this
case its the initial state. There is always an inital state, where the
statemachine will start executing. In the statechart shown above the
Initial state is WaitForInput
, so lets edit the initial to
WaitForInput
and then press the TAB key. This time the cursor jumps
into the transitiontable. This is where the yasnippet ends for
statemachine.
The transition table describes transitions between states, and what events that triggers the transtions together with guards and actions. to add a new row to the transition table type
msmRow<TAB>
This should produce an output where the cursor is put infront of
source. If we take a look at our statemachine we can see that there
are two events that are triggered from WaitforInput
as the following
table shows.
Source | event | Target | Action | guard |
---|---|---|---|---|
WaitForInput | evCtrlKeyPressed | WaitForKey | SetCtrlKey | - |
WaitForInput | evShiftKeyPressed | WaitForKey | SetShiftKey | - |
This table can be translated directly into the msmRow snippet as follows:
/**
* WaitForInput - Source <TAB>
* evCtrlKeyPressed - Event <TAB>
* WaitForKey - Target <TAB>
* SetCtrlKey - Action <TAB>
* none - Guard <TAB>
*/
msmf::Row<WaitForInput ,evCtrlKeyPressed,WaitForKey ,SetCtrlKey ,msmf::none >,
If there is no action/event or guard the word none is entered, this will be translated accordingly. The last TAB after the guard insertion will jump to the end of the Row on ~,~. If its the last row in the transition table you can erase the ~,~ if there are more rows to follow, then leave it.
We can now continue with the events from WaitForKey
using the
following table.
Source | event | Target | Action | guard |
---|---|---|---|---|
WaitForKey | evKeyPressed | WaitForKey | OutputKey | |
WaitForKey | evCmdKeyReleased | WaitForInput |
By issuing msmRow
snippet. This will produce an output as follows:
/**
* WaitForKey - Source
* evKeyPressed - Event
* WaitForKey - Target
* OutputKey - Action
* none - Guard
*/
msmf::Row<WaitForKey ,evKeyPressed ,WaitForKey ,OutputKey ,msmf::none >,
/**
* WaitForKey - Source
* evCmdKeyReleased - Event
* WaitForInput - Target
* none - Action
* none - Guard
*/
msmf::Row<WaitForKey ,evCmdKeyReleased,WaitForInput ,msmf::none ,msmf::none >
Thats it, we produced the transition table. But there are still a
couple of things missing. First of all, right now we just have one
state. The WaitForInput
. So lets continue creating states.
The state are easily done using state
snippet. The best place to put
it is to search for
Defined states
and underneath the comment add
state<TAB>
this will produce a skeleton for a state, the cursor is placed on the class name, this is where you enter the state name (WaitForKey). By pressing TAB the cursor will now jump to different sections where its possible to add/change name. For this tutorial the default is enough just tab through it. We are not quite done yet, though for the YASnippets we are. Now lets continue creating Actions and guards.
Most of the statechart is now implemented, but we need the action,guards and events to be implemented. Lets start with Events
Events can be constructed using a lisp functions implented in
yasnippet_function.el
. By execting the following function
(M-x) col/msm-implement-events
This will open a new buffer which is named as the statemachine but
adding <name>_Events.hpp
In this tutorial the name will be
CmdKeystatemachine_Events.hpp
The buffer is not saved, so if you want to keep it just make sure you save the buffer to either the named file or as another. The next part is to include the events in the state machine hpp file.
File: CmdKeyStateMachine.hpp add the following line
#include <boost/msm/back/state_machine.hpp>
#include <boost/msm/front/state_machine_def.hpp>
#include <boost/msm/front/functor_row.hpp>
//Added include file
#include "CmdKeyStateMachine_Events.hpp"
The function also updates the doxygen Uml diagram which is located under the class description or to be precise:
\startuml "<statemachine name>"
\enduml
Its not perfect in anyway and needs some more work, but it will give a basic UML statechart on the doxygen.
The next part is to create action and guards.
This is also a lisp function, by executing
(M-x) col/msm-implement-action
a new buffer will open, just like with the events. In the same manner its possible to save the file as it is. Or copy the output and paste it into the statemachine directly. If saved as a file, one needs to include the file somewhere in the statemachine class. For example:
#include "CmdKeyStateMachine_GuardAndActions.hpp"
/**
* \class transition_table
* \brief Transition table struture
.
.
Now the statemachine is done!
In the above example there were no guards included. Since guards are function objects that returns true or false, these are somewhat different from action function objects, which don't return anything. In all other aspects guards behave the same way as action objects. Lets take an example.
In the above statemachine we start at Init State. Lets first take a look at the transition table:
struct transition_table: public mpl::vector<
msmf::Row<Init ,evInit ,State1 ,msmf::none ,isOkToProceed >, (ref:proceed)
msmf::Row<State1 ,evBack ,Init ,msmf::none ,IsGotoInit >, (ref:GotoInit)
msmf::Row<State1 ,evBack ,State1 ,msmf::none ,IsNotGotoInit > (ref:NotGotoInit)
>{};
As one can notice from the transition table is that conditions are not
supported. Instead two guards are created, one for when IsGotoInit
and one that is IsNotGotoinit
.
In this example the statemachine has kept states as variables, its also possible to use events as a conditional variable, that is storing the condition inside the event object, and then extracting out of the event. The described example can be found at GuardMachine.
Main file for the GuardMachine:
#include <iostream>
#include "GuardMachine.hpp"
#include "GuardMachine_Events.hpp"
using namespace std;
int main(int argc, char *argv[])
{
GuardMachine stm;
cout << " Start" << endl;
stm.start();
stm.process_event(evInit());
cout << "send Init Event okToProceed: " << ( stm.okToProceed_ ? "True":"False") << endl;
stm.okToProceed_ = true;
cout << "send Init Event okToProceed: " << ( stm.okToProceed_ ? "True":"False") << endl;
stm.process_event(evInit());
cout << "Sending evBack gotoInit ==" << (stm.gotoInit_ ? "True":"False") << endl;
stm.process_event(evBack());
cout << "Setting gotoInit = true" << endl;
stm.gotoInit_ = true;
stm.process_event(evBack());
return 0;
}
And the correponding output
./GuardMachine
Start
Enter GuardMachine
Entry Init
send Init Event okToProceed: False
send Init Event okToProceed: True
Exit Init
Entry State1
Sending evBack gotoInit ==False
Exit State1
Entry State1
Setting gotoInit = true
Exit State1
Entry Init
It now time to start using the statemachine. Its quite easy, so lets get right to it.
#include <iostream>
#include "CmdKeyStateMachine.hpp"
#include "CmdKeyStateMachine_Events.hpp"
using namespace std;
int main(int argc, char *argv[])
{
CmdKeyStateMachine stm; (ref:Create)
cout << " Start" << endl;
stm.start(); (ref:start)
cout << "Send CtrlKey" << endl;
stm.process_event( evCtrlKeyPressed() ); (ref:evCtrlKey)
cout << "Send keyPressed" << endl;
stm.process_event( evKeyPressed() ); (ref:evKeyPressed)
cout << "send release cmd key" << endl;
stm.process_event(evCmdKeyReleased()); (ref:evReleased)
return 0;
}
Lets go through what happens.
Line 9, Creates the object stm which is a CmdKeystatemachine (the one we just created)
Line 11, starts the statemachine, this will call on_entry
on Cmdkeystatemachine, and then continues to WaitForinput
which
was our first initial state and call on_entry
for that one.
The statemachine now waits for an event.
Line 15, Create a event object of type evCtrlkeypressed
and send it to the statemachine, this will trigger an transition
to WaitForInput
and calling the action SetCntrlKey
. After the
action is performed the statemachine will now be in state WaitForKey
.
Line 18, Creates a event object of type evKeyPressed
and sends it to the statemachine, this will trigger an transaction
to itself and execute the action OutputKey
. The statemachine is
now back to WaitForKey
.
Line 21, creates a event object of type
evCmdKeyReleased
and sends it to the statemachine, this will trigger a transaction
back to WaitForinput
, but no action is taken place.
The whole example code can be found under <./examples/CmdKeyMachine>.
The only difference is that I've added console output, to be able to
see what happens. To build the examples go into the directory and
create a new directory .eg build
and issue cmake build.
>cd exmaples/CmdKeyMachine
>mkdir build
>cd build
>cmake ..
the output from the CmdKeymachine
example is
bash>./CmdMachine
Start
Statemachine entry
WaitForInput Entry
Send CtrlKey
WaitForInput Exit
SetCrtlKey
WaitForKey Entry
Send keyPressed
WaitForKey Exit
Output key
WaitForKey Entry
send release cmd key
WaitForKey Exit
WaitForInput Entry