State
The state machine components are defined in theState.h
header file. It declares three classes: State, State Machine and Transition. A subclass ofState
can implement the following methods:
Entry()
: implement the entry action of the state;During()
: implement the during action of the state;Exit()
: implement the exit action of the state;Initial()
: return the index of the initial substate.
The transitions of the state are stored in the variabletransitions
. The code for State
and Transition
class is as follows:
//State.h
#ifndef ROBOCALC_STATE_H_
#define ROBOCALC_STATE_H_
#include <vector>
#include <memory>
#define STATE_DEBUG
namespace robochart {
class Transition;
class State {
public:
std::string name;
bool mark;
State(std::string n) : name(n), stage(s_Inactive), mark(false) {}
virtual ~State() { printf("Deleting state %s\n", name.c_str()); }
virtual void Entry() {}
virtual void During() {}
virtual void Exit() {}
virtual int Initial() { return -1; }
enum Stages {
s_Enter, s_Execute, s_Exit, s_Inactive
};
Stages stage;
std::vector<std::shared_ptr<State>> states;
std::vector<std::shared_ptr<Transition>> transitions;
virtual void Execute();
bool TryTransitions();
bool TryExecuteSubstates(std::vector<std::shared_ptr<State>> s);
void CancelTransitions(int i);
};
class StateMachine: public State {
public:
StateMachine(std::string n): State(n) {}
virtual ~StateMachine() {}
};
class Transition {
private:
std::weak_ptr<State> source, target;
public:
std::string name;
public:
Transition(std::string n, std::weak_ptr<State> src, std::weak_ptr<State> tgt) :
name(n), source(src), target(tgt) {
}
virtual void Reg() {}
virtual bool Check() { return true; }
virtual void Cancel() {}
virtual bool Condition() { return true; }
virtual void Action() {}
virtual void ClearEvent() {};
virtual ~Transition() { source.reset(); target.reset(); printf("Deleting transition\n");}
bool Execute();
};
}
#endif
If the state is composite, its substates are stored in the variablestates
. In this case, the functionInitial()
must be implemented and return the index of the initial state. Any state (e.g. a state machine) must extend the class State and can provide entry, during and exit actions.
Notice that it is not possible to define the transitions of the state directly in the class because of a circular dependency between states and transitions. For this reason, transitions must be instantiated and added to the variabletransitions
of the source state.
For example, in our obstacle avoidance example, theTurning
state has reference to the robot (to access theMove
operation), and the state machine (to access variable dir
and clock T
); it provides the entry action that sets the angular speed of the robot.
State Machine
A state machine is just a state. This class exists only to make the notion of a state machine explicit. To execute a state machine, the execute
function is called. Inside this function, the functions try_execute_substates, try_transitions, cancel_transitions are called. A state has three stages: s_Enter
, s_Execute
and s_Exit
.
//State.cpp
#include "State.h"
namespace robochart {
bool Transition::Execute() {
if (Condition() & Check()) { //check condition() first if it is false no need to perform check(); condition() && check()
auto src = source.lock();
src->stage = State::s_Exit;
src->Execute();
Action();
auto tgt = target.lock();
tgt->stage = State::s_Enter;
tgt->Execute();
return true;
}
return false;
}
void State::Execute() {
switch (stage) {
case s_Enter:
#ifdef STATE_DEBUG
printf("Entering State %s\n", this->name.c_str());
#endif
Entry();
if (Initial() >= 0) {
states[Initial()]->stage = s_Enter; //this has already makes sure that every time the state machine is entered, it starts executing from initial state?
states[Initial()]->Execute();
}
stage = s_Execute;
break;
case s_Execute:
#ifdef STATE_DEBUG
printf("Executing a state %s\n", this->name.c_str());
#endif
while(TryExecuteSubstates(states)); //this makes sure more than one transition can happen at one cycle; execute the state from bottom to up
if (TryTransitions() == false) {
#ifdef STATE_DEBUG
printf("Executing during action of %s!\n", this->name.c_str());
#endif
During(); //if no transition is enabled, execute during action in every time step
}
else {
#ifdef STATE_DEBUG
printf("Not Executing during action of %s!\n", this->name.c_str());
#endif
}
break;
case s_Exit:
Exit();
stage = s_Inactive;
break;
}
}
bool State::TryTransitions() {
#ifdef STATE_DEBUG
printf("trying %ld transitions\n", transitions.size());
#endif
for (int i = 0; i < transitions.size(); i++) {
#ifdef STATE_DEBUG
printf("trying transition: %s\n", transitions[i]->name.c_str());
#endif
bool b = transitions[i]->Execute();
if (b) {
this->mark = true;
CancelTransitions(i); //erase OTHER events (in the channel) already registered by the transitions of this state, as the state tried its every possible transitions
#ifdef STATE_DEBUG
printf("transition %s true\n", transitions[i]->name.c_str());
#endif
return true;
}
else {
#ifdef STATE_DEBUG
printf("transition %s false\n", transitions[i]->name.c_str());
#endif
}
}
this->mark = false;
return false;
}
void State::CancelTransitions(int i) {
for (int j = 0; j < transitions.size(); j++) {
if (j != i) {
#ifdef STATE_DEBUG
printf("CANCEL transition: %s\n",transitions[j]->name.c_str());
#endif
transitions[j]->Cancel();
}
}
}
//return false either no sub states or no transitions are enabled in the sub states
bool State::TryExecuteSubstates(std::vector<std::shared_ptr<State>> s) {
for (int i = 0; i < s.size(); i++) {
// printf("state index : %d; stage: %d\n", i, states[i]->stage);
// there should be only one active state in a single state machine
if (s[i]->stage == s_Inactive) continue;
else {
s[i]->Execute();
return s[i]->mark; //keep trying the transitions at the same level if there is transition from one state to another
}
}
return false;
}
}
Transition
Similarly to states, transitions must extend the class Transition. They can provide a number of functions:Reg
,Check
,Cancel
,Condition
andAction
. The first three are necessary if the transition has an event as a trigger, the fourth if the transition has a condition and the final if the transition has an action. A subclass ofTransition
may implement five optional functions:
void Reg()
: this function is only implemented if the transition has a trigger with an event. In this case, the functionReg
of a channel must be called (with appropriate parameters) and the event returned by the call must be stored in a variable such asevent
;bool Check()
: this function is implemented to check the occurrence of the registered event. It calls theCheck
function of the channel on the event produced byReg
;void Cancel()
: this function calls the methodCancel
of the channel with the event produced byReg
as a parameter;bool Condition()
: this function implements the condition of the transition;void Action()
: this function implements the action of the transition. If the transition's trigger contains an event, this function must call the functionAccept
orAcceptAndDelete
of the channel (on the event obtained fromReg
). The functionAccept
should be called if the channel is synchronous, andAcceptAndDelete
should be called if the channel is asynchronous.
In the obstacle avoidance example, the transition t1
has a trigger of the formobstacle?dir
and also reset the clock T
. This transition is implemented as the classt1
, whereReg
is implemented as a function that calls the functionReg
of the channelobstacle
of the state machine with source name StmMovement
(identifying the state machine StmMovement) and undefined parameterOptional<Loc>()
. The result of theReg
function of the channel is a pointer to an event that is recorded in the variableevent
.
TheCheck
function simply returns the result of calling theCheck
function of the channelobstacle
. If the Check
function return true, the first parameter of the event to the state machine variabledir
using the functionget<0>
applied to the event. The event must be accepted to mark it as treated and set to thenullptr
. In the case of the transition t1
, the action finishes with a call toAcceptAndDelete
because the channelobstacle
is asynchronous. If it were synchronous, the methodAccept
should be called, which will only delete the event if both sides of the communication have accepted it. If the check function return false, the functionCancel
calls the functionCancel
of the same channel. All these calls are guarded by a check on the variableevent
that guarantees it is not null.
Transition t2
illustrates the implementation of a transition with a condition.
The condition calculates the time steps elapsed recorded in T
. The time step is increased in every cycle of the simulation followed by the execution of the state machine. If the time passes certain threshold, transition t2
is trigger.