diff --git a/doc/xml/images/Master-Slave-Semantics.svg b/doc/xml/images/Master-Slave-Semantics.svg
new file mode 100644
index 000000000..1ccfeecbc
--- /dev/null
+++ b/doc/xml/images/Master-Slave-Semantics.svg
@@ -0,0 +1,673 @@
+
+
+
+
diff --git a/doc/xml/images/OperationCallvsSend.svg b/doc/xml/images/OperationCallvsSend.svg
new file mode 100644
index 000000000..196dc4176
--- /dev/null
+++ b/doc/xml/images/OperationCallvsSend.svg
@@ -0,0 +1,1349 @@
+
+
+
+
diff --git a/doc/xml/images/StateMachineFlow.svg b/doc/xml/images/StateMachineFlow.svg
new file mode 100644
index 000000000..ccccb959b
--- /dev/null
+++ b/doc/xml/images/StateMachineFlow.svg
@@ -0,0 +1,503 @@
+
+
+
+
diff --git a/doc/xml/images/StateMachineSteps-SleepAfterEntry.svg b/doc/xml/images/StateMachineSteps-SleepAfterEntry.svg
new file mode 100644
index 000000000..10973f047
--- /dev/null
+++ b/doc/xml/images/StateMachineSteps-SleepAfterEntry.svg
@@ -0,0 +1,751 @@
+
+
+
+
diff --git a/doc/xml/images/StateMachineSteps-SleepAfterRun.svg b/doc/xml/images/StateMachineSteps-SleepAfterRun.svg
new file mode 100644
index 000000000..95f6c2e93
--- /dev/null
+++ b/doc/xml/images/StateMachineSteps-SleepAfterRun.svg
@@ -0,0 +1,747 @@
+
+
+
+
diff --git a/rtt/scripting/StateMachine.cpp b/rtt/scripting/StateMachine.cpp
index aa6bade21..61eda8d9f 100644
--- a/rtt/scripting/StateMachine.cpp
+++ b/rtt/scripting/StateMachine.cpp
@@ -209,14 +209,13 @@ namespace RTT {
bool StateMachine::automatic()
{
TRACE_INIT();
- // if you go from reactive to automatic,
- // first execute the run program, before
- // evaluating transitions.
+ //
if ( smStatus != Status::inactive && smStatus != Status::unloaded && smStatus != Status::error) {
TRACE( "Will start." );
smStatus = Status::running;
os::MutexLock lock(execlock);
runState( current );
+
return true;
}
TRACE( "Won't start." );
@@ -316,30 +315,51 @@ namespace RTT {
return true;
break;
case Status::requesting:
- if ( this->executePending() ) { // if all steps done,
- this->requestNextState();
- this->executePending(); // execute steps of next state
- TRACE("Is active now.");
- smStatus = Status::active;
+ // finish entry and if finished, run the run program:
+ if ( this->executePreCheck() == false) {
+ TRACE("Yielding Entry program...");
+ break;
+ }
+ TRACE("Is active now.");
+ smStatus = Status::active;
+ // check transitions:
+ this->requestNextState();
+ // post-check transitioning:
+ if ( this->executePostCheck() == false) {
+ TRACE("Yielding ...");
+ break;
}
break;
case Status::active:
- this->executePending();
+ this->executePreCheck();
break;
case Status::running:
- if ( this->executePending() == false) {
- TRACE("Yielding...");
+ // finish entry and if finished, run the run program:
+ if ( this->executePreCheck() == false) {
+ TRACE("Yielding Entry program...");
+ break;
+ }
+ // check transitions:
+ this->requestNextState();
+ // post-check transitioning:
+ if ( this->executePostCheck() == false) {
+ TRACE("Yielding ...");
break;
}
- // if all pending done:
- this->requestNextState(); // one state at a time
- this->executePending(); // execute steps of next state
break;
case Status::paused:
if (mstep) {
- if ( this->executePending(true) ) { // if all steps done,
- this->requestNextState(true); // one state at a time
- this->executePending(true); // execute steps of next state
+ // finish entry and if finished, run the run program:
+ if ( this->executePreCheck(true) == false) {
+ TRACE("Yielding Entry program...");
+ break;
+ }
+ // check transitions:
+ this->requestNextState(true);
+ // post-check transitioning:
+ if ( this->executePostCheck(true) == false) {
+ TRACE("Yielding ...");
+ break;
}
TRACE("Did a step.");
mstep = false;
@@ -442,10 +462,10 @@ namespace RTT {
}
else
{
- TRACE("Transition triggered from '"+ (current ? current->getName() : "null") +"' to '"+(newState ? newState->getName() : "null")+"'.");
+ TRACE("Transition triggered from '"+ (current ? current->getName() : "(null)") +"' to '"+(newState ? newState->getName() : "(null)")+"'.");
// reset handle and run, in case it is still set ( during error
// or when an event arrived ).
- currentRun = 0;
+ //currentRun = 0;
currentHandle = 0;
if ( transProg ) {
transProg->reset();
@@ -461,11 +481,6 @@ namespace RTT {
leaveState(current);
assert( currentEntry == 0); // we assume that in executePending, trans and exit get executed first and entry is stil null.
}
- // schedule a run for the next 'step'.
- // if handle above finished, run will be called directly
- // in executePending. if handle was not finished
- // or stepping, it will be called after handle.
- runState( newState );
}
void StateMachine::enableGlobalEvents( )
@@ -633,7 +648,7 @@ namespace RTT {
if( current == 0 )
return 0;
// only a run program may be interrupted...
- if ( !interruptible() || currentTrans ) {
+ if ( !interruptible() || currentTrans || current != next) {
return current; // can not accept request, still in transition.
}
@@ -994,20 +1009,19 @@ namespace RTT {
}
bool StateMachine::executePending( bool stepping )
+ {
+ return executePreCheck(stepping) && executePostCheck(stepping);
+ }
+
+ bool StateMachine::executePreCheck( bool stepping )
{
TRACE_INIT();
- // This function has great resposibility, since it acts like
- // a scheduler for pending requests. It tries to devise what to
- // do on basis of the contents of variables (like current*, next,...).
- // This is a somewhat
- // fragile implementation but requires very little bookkeeping.
- // if returns true : a transition (exit/entry) is done
- // and a new state may be requested.
if ( inError() )
return false;
- // first try to finish the current entry program, if any:
+// TRACE("executePreCheck..." );
+ // first try to finish the current entry program (this only happens if entry was not atomically implemented, ie yielding):
if ( currentEntry ) {
TRACE("Executing entry program of '"+ (current ? current->getName() : "(null)") +"'" );
if ( this->executeProgram(currentEntry, stepping) == false )
@@ -1021,6 +1035,25 @@ namespace RTT {
}
}
+ // Run is executed before the transitions.
+ if ( currentRun ) {
+ TRACE("Executing run program of '"+ (current ? current->getName() : "(null)") +"'" );
+ if ( this->executeProgram(currentRun, stepping) == false )
+ return true;
+ // done.
+ TRACE("Finished run program of '"+ (current ? current->getName() : "(null)") +"'" );
+ }
+ return true;
+ }
+
+ bool StateMachine::executePostCheck( bool stepping )
+ {
+ TRACE_INIT();
+
+ if ( inError() )
+ return false;
+
+// TRACE("executePostCheck..." );
// if a transition has been scheduled, proceed directly instead of doing a run:
if ( currentTrans ) {
TRACE("Executing transition program from '"+ (current ? current->getName() : "(null)") + "' to '"+ ( next ? next->getName() : "(null)")+"'" );
@@ -1074,7 +1107,12 @@ namespace RTT {
current = next;
enterState(next);
+ runState(next);
enableEvents(next);
+ } else {
+ // schedule a new run of the current state, only if previous run finished.
+ if (current && currentRun == 0)
+ runState(current);
}
// finally, execute the current Entry of the new state:
@@ -1109,18 +1147,6 @@ namespace RTT {
}
}
- // Run is executed before the transitions.
- if ( currentRun ) {
- TRACE("Executing run program of '"+ (current ? current->getName() : "(null)") +"'" );
- if ( this->executeProgram(currentRun, stepping) == false )
- return false;
- // done.
- TRACE("Finished run program of '"+ (current ? current->getName() : "(null)") +"'" );
- // in stepping mode, delay 'true' one executePending().
- if ( stepping )
- return false;
- }
-
return true; // all pending is done
}
@@ -1214,9 +1240,7 @@ namespace RTT {
// execute the entry program of the initial state.
if ( !inError() ) {
- enableEvents(current);
-
- if ( this->executePending() ) {
+ if ( this->executePreCheck() ) {
smStatus = Status::active;
TRACE("Activated.");
} else {
diff --git a/rtt/scripting/StateMachine.hpp b/rtt/scripting/StateMachine.hpp
index 80beac9ab..f1aaee103 100644
--- a/rtt/scripting/StateMachine.hpp
+++ b/rtt/scripting/StateMachine.hpp
@@ -380,19 +380,28 @@ namespace RTT
bool requestStateChange( StateInterface * s_n );
/**
- * Execute any pending State (exit, entry, handle) programs.
- * You must executePending, before calling requestState() or
- * requestNextState(). You should only call requestState() or requestNextState()
- * if executePending returns true.
+ * Returns executePreCheck() && executePostCheck()
+ */
+ bool executePending(bool stepping = false);
+
+ /**
+ * Execute only 'yielded entry' and run state programs before any state transitions are checked.
*
- * Due to the pending requests, the currentState() may have changed.
+ * @param stepping provide true if the pending programs should
+ * be executed one step at a time.
+ * @retval true if run was running or pending.
+ * @retval false only if the entry program is yielding.
+ */
+ bool executePreCheck( bool stepping = false );
+ /**
+ * Execute only transition, handle or exit/entry state programs after any state transitions are checked.
*
* @param stepping provide true if the pending programs should
* be executed one step at a time.
- * @retval true if nothing was pending @retval false if there was
- * some program executing.
+ * @retval true if nothing was pending
+ * @retval false if there was some program executing.
*/
- bool executePending( bool stepping = false );
+ bool executePostCheck( bool stepping = false );
/**
* Express a precondition for entering a state. The
diff --git a/tests/state_test.cpp b/tests/state_test.cpp
index f387bf944..59bf6fbac 100644
--- a/tests/state_test.cpp
+++ b/tests/state_test.cpp
@@ -697,10 +697,10 @@ BOOST_AUTO_TEST_CASE( testStateOperations)
StateMachinePtr sm = sa->getStateMachine("x");
BOOST_REQUIRE( sm );
sm->trace(true);
- OperationCaller act = tc->provides("x")->getOperation("activate");
- OperationCaller autom = tc->provides("x")->getOperation("automatic");
- BOOST_CHECK( act(sm.get()) );
- BOOST_CHECK( autom(sm.get()) );
+ OperationCaller act = tc->provides("x")->getOperation("activate");
+ OperationCaller autom = tc->provides("x")->getOperation("automatic");
+ BOOST_CHECK( act(sm) );
+ BOOST_CHECK( autom(sm) );
sleep(1); // we must allow the thread to transition...
@@ -827,12 +827,13 @@ BOOST_AUTO_TEST_CASE( testStateYieldbySend )
+ " initial state INIT {\n"
+ " var double d = 0.0\n"
+ " run { do o_event.send(1.0); test.i = 5; do test.assert(test.i == 5);\n" // asynchronous send on o_event, so signal must be processed when we return.
- + " do yield;\n"
+ + " do yield;\n" // o_event still in the message queue
+ + " do yield;\n" // o_event should have been processed.
+ " test.i = 10;\n"
+ " do test.assert(false); }\n"
+ " transition o_event(d) select NEXT;\n"
+ " transitions {\n"
- + " select FINI\n"
+ + " if test.i == 10 then select FINI\n"
+ " }\n"
+ " }\n"
+ " state NEXT {\n" // Success state.
@@ -1077,10 +1078,25 @@ BOOST_AUTO_TEST_CASE( testStateOperationSignalTransition )
// test event reception from own component
string prog = string("StateMachine X {\n")
+ " var double et = 0.0\n"
+ + " var int cnt = 0, check_exit = 0, check_exit2 = 0, check_run = 0, check_entry = 0, check_trans = 0, check_trans2 = 0\n"
+ " initial state INIT {\n"
- + " transition o_event(et) { test.assert(et == 3.33); } select FINI\n" // test signal transition
- + " }\n"
- + " final state FINI { entry { test.assert( et == 3.33);} } \n"
+ + " transition o_event(et) { cnt=cnt+1; check_trans = cnt; test.assert(et == 3.33); } select CHECKER\n" // test signal transition
+ + " exit { cnt=cnt+1; check_exit = cnt; }\n"
+ + " }\n"
+ + " state CHECKER {\n"
+ + " entry { cnt=cnt+1; check_entry = cnt; test.assert( et == 3.33); } \n"
+ + " run { cnt=cnt+1; check_run = cnt; }\n"
+ + " exit { cnt=cnt+1; check_exit2 = cnt; }\n"
+ + " transition { cnt=cnt+1; check_trans2 = cnt; } select FINI\n"
+ + " }\n"
+ + "final state FINI { entry {\n"
+ + " test.assertEqual( check_trans, 1 );\n"
+ + " test.assertEqual( check_exit, 2 );\n"
+ + " test.assertEqual( check_entry, 3 );\n"
+ + " test.assertEqual( check_run, 4 );\n"
+ + " test.assertEqual( check_trans2, 5 );\n"
+ + " test.assertEqual( check_exit2, 6 );\n"
+ + " } } \n"
+ "}\n"
+ "RootMachine X x()\n";
this->parseState( prog, tc );
@@ -1838,12 +1854,12 @@ void StateTest::runState(const std::string& name, TaskContext* tc, bool trace, b
StateMachine::ChildList children = sm->getChildren();
for( StateMachine::ChildList::iterator it = children.begin(); it != children.end(); ++it)
(*it)->trace(trace);
- OperationCaller act = tc->provides(name)->getOperation("activate");
- OperationCaller autom = tc->provides(name)->getOperation("automatic");
- BOOST_CHECK( act(sm.get()) );
+ OperationCaller act = tc->provides(name)->getOperation("activate");
+ OperationCaller autom = tc->provides(name)->getOperation("automatic");
+ BOOST_CHECK( act(sm) );
BOOST_CHECK( SimulationThread::Instance()->run(1) );
BOOST_CHECK_MESSAGE( sm->isActive(), "Error : Activate Command for '"+sm->getName()+"' did not have effect." );
- BOOST_CHECK( autom(sm.get()) || !test );
+ BOOST_CHECK( autom(sm) || !test );
BOOST_CHECK( SimulationThread::Instance()->run(runs) );
}