This is the second part of the serie of presentations with deep introduction into features of SObjectizer-5.5.
This part is dedicated to such important feature as agent's states.
2. The main features of SObjectizer-5.5, like agents,
cooperations, messages/signal, mboxes,
dispatchers and delayed delivery, were described
in the previous part.
The next important feature is agent’s state.
SObjectizer Team, Jan 2016
3. Agent in SObjectizer is a finite-state machine.
The behaviour of an agent depends on the current
state of the agent and the received message.
SObjectizer Team, Jan 2016
4. An agent can receive and process different messages
in each state. In other words an agent can receive a
message in one state but ignore it in another state.
Or, if an agent receives the same message in several
states, it can handle the message differently in each
state.
SObjectizer Team, Jan 2016
5. Where it can be useful?
SObjectizer Team, Jan 2016
6. Let’s imagine a simple agent which controls LED
indicator on some device.
It receives just one signal turn_on_off.
When LED indicator is off this signal should turn
indicator on. If LED indicator is on this signal should
turn indicator off.
SObjectizer Team, Jan 2016
7. This logic can be represented by simple statechart:
SObjectizer Team, Jan 2016
off on
enter/turn_led_on
exit/turn_led_off
turn_on_off
turn_on_off
8. It's easy to see that led_indicator agent requires two
states: off and on.
They can be directly expressed in C++ code via
usage of so_5::state_t class.
The definition of new state for an agent means
creation of new instance of state_t.
SObjectizer Team, Jan 2016
9. States are usually represented as members of agent’
s class:
class led_indicator final : public so_5::agent_t
{
state_t off{ this }, on{ this };
SObjectizer Team, Jan 2016
10. A state can have a textual name:
class led_indicator final : public so_5::agent_t
{
state_t off{ this, "off" }, on{ this, "on" };
It could be useful for debugging and logging.
SObjectizer Team, Jan 2016
11. There are several ways of changing agent’s state:
// Very old and basic way.
so_change_state( off );
// More modern and short way.
off.activate();
// Yet more modern way.
this >>= off;
The current state can be obtained via so_current_state()
method:
if( off == so_current_state() ) …
SObjectizer Team, Jan 2016
12. Every agent already has one state: the default one.
The default state can be accessed via
so_default_state() method:
// Returning agent to the default state.
this >>= so_default_state();
SObjectizer Team, Jan 2016
13. Even ad-hoc agents have the default state.
But it is the only one state they have.
Because there is no user-defined class for an ad-hoc
agent then there is no possibility to define new states
for ad-hoc agent. Thus there is no possibility to
change state of ad-hoc agent.
SObjectizer Team, Jan 2016
14. The most important part of usage of agent’s states is
subscription to a message with respect to a specific
state...
SObjectizer Team, Jan 2016
15. The simple usage of so_subscribe() and
so_subscribe_self() methods leads to subscription
only for the default agent’s state.
It means that:
so_subscribe_self().event(…);
is the equivalent of:
so_subscribe_self().in( so_default_state() ).event(…);
SObjectizer Team, Jan 2016
16. To make subscription to a message for a specific
state it is necessary to use in() method in a
subscription chain:
so_subscribe_self().in( off ).event(…);
so_subscribe_self().in( on ).event(…);
SObjectizer Team, Jan 2016
17. The in() methods can be chained if the same event
handler is used in several states:
so_subscribe_self()
.in( off )
.in( on )
.event< get_name >( [] -> std::string { return "led_indicator"; } );
so_subscribe_self()
.in( off )
.event< get_status >( [] -> std::string { return "off"; } );
SObjectizer Team, Jan 2016
18. There is another way to make subscription for a
specific state:
off.event< get_name >( [] -> std::string { return "led_indicator"; } )
.event< get_status >( [] -> std::string { return "off"; } );
on.event< get_name >( [] -> std::string { return "led_indicator"; } )
.event(...)
...;
SObjectizer Team, Jan 2016
19. There are also on_enter/on_exit methods in state_t class.
Method on_enter sets up an enter handler. Enter handler
is automatically called when the state is activated.
Contrary on_exit method sets an exit handler. Exit handler
is automatically called when the state is activated.
Enter and exit handler can be a lambda-function or a
pointer to member method of agent's class.
SObjectizer Team, Jan 2016
20. In the led_indicator example enter and exit handlers
are necessary for on state:
on.on_enter( [this]{ /* some device-dependent code */ } )
.on_exit( [this]{ /* some device-dependent code */ } )
...
SObjectizer Team, Jan 2016
21. Let’s see a full example of led_indicator agent.
SObjectizer Team, Jan 2016
22. The full led_indicator agent code:
class led_indicator final : public so_5::agent_t
{
state_t off{ this, "off" }, on{ this, "on" };
public :
struct turn_on_off : public so_5::signal_t {};
led_indicator( context_t ctx ) : so_5::agent_t{ ctx }
{
this >>= off;
off.event< turn_on_off >( [this]{ this >>= on; } );
on.on_enter( [this]{ /* some device-dependent code */ } )
.on_exit( [this]{ /* some device-dependent code */ } )
.event< turn_on_off >( [this]{ this >>= off; } );
}
};
SObjectizer Team, Jan 2016
23. A few more things must be mentioned in the code
above...
SObjectizer Team, Jan 2016
24. The first one is the place where all subscription is
defined.
In led_indicator example agent definition is performed in
the agent's constructor.
Usually all agent definition is done in so_define_agent()
method. It has sense if class inheritance is used. But
led_indicator is final class so we can do all these actions
in led_indicator's constructor.
SObjectizer Team, Jan 2016
25. The second one is switching of led_indicator agent to
off state.
By default the initial state of any agent is the default state
(which is accessible via so_default_state() method). But
we need to start led_indicator agent in off state. So we
change agent's state at the very beginning.
SObjectizer Team, Jan 2016
26. The third one is the guarantee of calling exit handler
for state.
If an agent enters into state S and state S has exit handler
then SObjectizer guarantees the invocation of that exit
handler in case of:
● switching of agent to different state;
● so_evt_finish() for agent is called (as a result of
agent's coop deregistration or SObjectizer
Environment shutdown).
SObjectizer Team, Jan 2016
27. The example shown above demonstrate simple finite-
state machine.
Since v.5.5.15 SObjectizer supports more advanced
features of agents' states like composite states,
shallow- and deep-history, time limitations and so on.
These advanced features allow to implement agents
as hierarchical state machines.
SObjectizer Team, Jan 2016
28. Let's see an ability to create hierarchical state
machines on a slightly complex example: an agent
which implements blinking of LED indicator.
This agent receives `turn_on_off` signal for turning
blinking on and off. When blinking is turned on then
agent switches LED indicator on for 1s then switches
it off for 1s then switches on again and so on until the
agent receives next `turn_on_off` signal.
SObjectizer Team, Jan 2016
29. A statechart for that agent can be represented as:
SObjectizer Team, Jan 2016
off
blinking
enter/turn_timer_on
exit/turn_timer_off
turn_on_off
turn_on_off
blink_on
enter/turn_led_on
exit/turn_led_off
blink_off
timer
timer
30. Declaration of agent's states:
class blinking_led final : public so_5::agent_t
{
state_t off{ this, "off" },
blinking{ this, "blinking" },
blink_on{ initial_substate_of{ blinking }, "on" },
blink_off{ substate_of{ blinking }, "off" };
Agent has two top level states: off and blinking.
State blinking is a composite state with two substates:
blink_on and blink_off.
SObjectizer Team, Jan 2016
31. Substate blink_on is marked as initial substate of
composite state blinking.
It means that when state blinking is activated then
substate blink_on is activated too.
Moreover so_current_state() method will return a
reference to blink_on, not to blinking. It is because
so_current_state() always returns a reference to a leaf
state in agent's state tree.
SObjectizer Team, Jan 2016
32. Every composite state must have the initial substate.
It means that exactly one substate must be declared by
using initial_substate_of indicator:
blink_on{ initial_substate_of{ blinking }, "on" },
blink_off{ substate_of{ blinking }, "off" };
An attempt to activate a composite state without the initial
substate defined will lead to an error at run-time.
SObjectizer Team, Jan 2016
33. Behaviour of a state can be defined anywhere in the
agent's code. Usually it is done in agent's constructor
or in so_define_agent() method.
Definition of state's behaviour inside so_define_agent() is
preferable if inheritance of agent's classes is required or
indented.
In this example inheritance is not used so we define
behaviour of agent's state in the agent's constructor:
SObjectizer Team, Jan 2016
34. Definition of states behaviour:
off
.event< turn_on_off >( [this]{ this >>= blinking; } );
blinking
.on_enter( [this] {
m_timer = so_5::send_periodic< timer >(
*this, std::chrono::seconds::zero(), std::chrono::seconds{1} );
} )
.on_exit( [this]{ m_timer.release(); } )
.event< turn_on_off >( [this]{ this >>= off; } );
blink_on
.on_enter( &blinking_led::led_on )
.on_exit( &blinking_led::led_off )
.event< timer >( [this]{ this >>= blink_off; } );
blink_off
.event< timer >( [this]{ this >>= blink_on; } );
SObjectizer Team, Jan 2016
35. It's easy to see that many events look like:
event<Message>([this] { this >>= State; });
This is very typical case in complex statecharts.
There is a special method just_switch_to() which can
simplify such cases. By using this method we can rewrite
states behaviour that way:
SObjectizer Team, Jan 2016
36. Definition of states behaviour with just_switch_to:
off
.just_switch_to< turn_on_off >( blinking );
blinking
.on_enter( [this] {
m_timer = so_5::send_periodic< timer >(
*this, std::chrono::seconds::zero(), std::chrono::seconds{1} );
} )
.on_exit( [this]{ m_timer.release(); } )
.just_switch_to< turn_on_off >( off );
blink_on
.on_enter( &blinking_led::led_on )
.on_exit( &blinking_led::led_off )
.just_switch_to< timer >( blink_off );
blink_off
.just_switch_to< timer >( blink_on );
SObjectizer Team, Jan 2016
37. Note that reaction to turn_on_off signal is defined
only in off and blinking states.
There are no such handles in substates blink_on and
blink_off.
It is not necessary because substates inherit event
handlers from their parent states.
SObjectizer Team, Jan 2016
38. Inheritance of event handlers means that event
handler will be searched in the current state, then in
its parent state, then in its parent state and so on...
In blinking_led agent an event handler for turn_on_off
signal will be searched in blink_on and blink_off states
and then in blinking state.
That is why we don't need to create subscription for
turn_on_off in blink_on and blink_off states.
SObjectizer Team, Jan 2016
39. The full source for blinking_led agent (1/3):
class blinking_led final : public so_5::agent_t
{
state_t
off{ this, "off" },
blinking{ this, "blinking" },
blink_on{ initial_substate_of{ blinking }, "on" },
blink_off{ substate_of{ blinking }, "off" };
struct timer : public so_5::signal_t {};
public :
struct turn_on_off : public so_5::signal_t {};
SObjectizer Team, Jan 2016
40. The full source for blinking_led agent (2/3):
blinking_led( context_t ctx ) : so_5::agent_t{ ctx }
{
this >>= off;
off
.just_switch_to< turn_on_off >( blinking );
blinking
.on_enter( [this] {
m_timer = so_5::send_periodic< timer >(
*this, std::chrono::seconds::zero(), std::chrono::seconds{1} );
} )
.on_exit( [this]{ m_timer.release(); } )
.just_switch_to< turn_on_off >( off );
SObjectizer Team, Jan 2016
41. The full source for blinking_led agent (3/3):
blink_on
.on_enter( &blinking_led::led_on )
.on_exit( &blinking_led::led_off )
.just_switch_to< timer >( blink_off );
blink_off
.just_switch_to< timer >( blink_on );
}
private :
so_5::timer_id_t m_timer;
void led_on() { /* some device-dependent code */ }
void led_off() { /* some device-dependent code */ }
};
SObjectizer Team, Jan 2016
42. There are several other features which simplify
implementation of complex state machines.
One of them can simplify blinking_led example show
above.
It is time_limit for agent's state.
SObjectizer Team, Jan 2016
43. Let's imagine that blinking_led agent have to switch
LED on for 1.5s and then switch it off for 0.7s. How
can we do that?
The obvious way is to use delayed signals with different
timeouts in enter handlers for blink_on and blink_off
states. But this way is not very easy in fact...
Usage of time_limit is much simpler.
SObjectizer Team, Jan 2016
44. Just remove timer signal and m_timer_id from blinking_led
agent and rewrite states behaviour that way:
off
.just_switch_to< turn_on_off >( blinking );
blinking
.just_switch_to< turn_on_off >( off );
blink_on
.on_enter( &blinking_led::led_on )
.on_exit( &blinking_led::led_off )
.time_limit( std::chrono::milliseconds{1500}, blink_off );
blink_off
.time_limit( std::chrono::milliseconds{750}, blink_on );
SObjectizer Team, Jan 2016
45. The time_limit feature dictates SObjectizer to limit
time spent in the specific state.
A clause like:
SomeState.time_limit(Timeout, AnotherState);
tells the SObjectizer that agent must be automatically
switched from SomeState to AnotherState after Timeout
spent in SomeState.
SObjectizer Team, Jan 2016
46. The blinking_led example shows just few advanced
features of agent's states.
Some features like shallow- and deep-history and
transfer_to_state/suppress methods are not described
here. Please see the more detailed description of these
features in the Project's Wiki.
SObjectizer Team, Jan 2016