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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + Master-Slave Execution Order + + Update step + + + OwnThreadOperations + EventPortCallbacks + + ScriptingFunctions + + updateHook() + + + + + + + MASTER + + + TaskContextExecutionEngine + + + + ACTIVITY + + + + Update step + + + OwnThreadOperations + EventPortCallbacks + + ScriptingFunctions + + updateHook() + + + + + + SLAVE + + + TaskContextExecutionEngine + + + + SlaveActivity + + + + update() ( from C++ ) + + TaskContext Interface + + + + + TaskContext Interface + + + + update() ( from a script ) + + + + 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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + Thread ofComponent A + + + call() : executed immediately ! + + OwnThreadOperation of B + + Thread ofComponent B + + + script or updateHook() + + + script or updateHook() + time + time + + + Thread ofComponent A + + + send() : executed immediately ! + OwnThreadOperation of B + + Thread ofComponent B + + + script or updateHook() + + time + time + + Blocks + + + script or updateHook() + + Operations: call() versus send() + Continues + + Continues + + Continues + + + + script or updateHook() + + + + script or updateHook() + + + + send() : delayed until updateHook finishes ! + OwnThreadOperation of B + + + + script or updateHook() + + Continues + + + + Executes + + + + Executes + + Continues + + + + Thread ofComponent A + Thread ofComponent B + + + script or updateHook() + + time + time + + + ClientThreadOperation of B + + + + Executes + Continues + + + + script or updateHook() + + + + call() : delayed until updateHook finishes ! + + + + script or updateHook() + + + Blocks + OwnThreadOperation of B + + + script or updateHook() + + + ClientThreadOperation of B + + + + + Executes + Continues + + + + + script or updateHook() + + ClientThread does not influence thread of Component BBUT must be implemented thread-safe : use mutexesto protect reading/writing data from Component B ! + + 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 @@ + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + entry Program + + + + (sleep) + + + + run Program + + + + check Transitions + + + + exit Program + + + + handle Program + + + + transition Program + + + + + + + + + + + No transition matches + Transitionmatches + + Transitionto other state + Transitionto same state(self-transition) + + + Orocos Scripting State Machines (RTT v2.9) + This diagram shows all possible stepsin an Orocos State Machine. If a certain programis not present in a state, it is skipped and theState Machine goes on to the next step. + Here we switch from one stateto another state + + 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 @@ + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + entry Program + + + + + run Program + + + + CheckTransitions + + + + + exit Program + + + + + run Program + + + + + CheckTransitions + + + + + + entry Program + + + + First match cause toselect 'state B' + initial state + + + + + + + + Steps + 0 + 1 + 2 + (sleep in state A) + (sleep in state B) + (sleep in state B) + . . . + state A + + state A + + state B + + state B + + + + No match cause tohandle + + + handle Program + + + + + + run Program + + + + + CheckTransitions + + + + state B + + + (sleep ... ) + 3 + state B + + Orocos State Machine transitions example (RTT v2.9) + This example shows in which step which programs of which states are executed.Once for a step where a transition to a next state succeeds ( from A to B in step 1),and once for a step where no transition to another state succeed ( stay in B in step 2).If a certain program is not present in a state, it is treated as an empty program. + + 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 @@ + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + entry Program + + + + run Program + + + + CheckTransitions + + + + exit Program + + + + run Program + + + + + CheckTransitions + + + + + entry Program + + + + First match cause toselect 'state B' + initial state + + + + + + + + Steps + 0 + 1 + 2 + (sleep in state A) + (sleep in state B) + (sleep in state B) + . . . + state A + + state A + + state B + + state B + + + + No match cause tohandle + + + handle Program + + + + + run Program + + + + + CheckTransitions + + + state B + + + (sleep ... ) + 3 + state B + + Orocos State Machine transitions example (RTT v2.8) + This example shows in which step which programs of which states are executed.Once for a step where a transition to a next state succeeds ( from A to B in step 1),and once for a step where no transition to another state succeed ( stay in B in step 2).If a certain program is not present in a state, it is treated as an empty program. + + 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) ); }