SObjectizer  5.5
so_5/state_deep_history/main.cpp
/*
* An example of usage of agent's state with deep history.
*/
#include <iostream>
#include <string>
#include <cctype>
// Main SObjectizer header file.
#include <so_5/all.hpp>
// Messages to be used for interaction with console agent.
struct key_cancel final : public so_5::signal_t {};
struct key_bell final : public so_5::signal_t {};
struct key_grid final : public so_5::signal_t {};
struct key_digit
{
char m_value;
};
// Main agent.
class console final : public so_5::agent_t
{
// States of agent's statechart.
state_t
dialog{ this, "dialog", deep_history },
wait_activity{
initial_substate_of{ dialog }, "wait_activity" },
number_selection{ substate_of{ dialog }, "number_selection" },
special_code_selection{
substate_of{ dialog }, "special_code_selection" },
special_code_selection_0{
initial_substate_of{ special_code_selection },
"special_code_selection_0" },
user_code_selection{
substate_of{ special_code_selection },
"user_code_selection" },
user_code_apartment_number{
initial_substate_of{ user_code_selection },
"apartment_number" },
user_code_secret{
substate_of{ user_code_selection },
"secret_code" },
service_code_selection{
substate_of{ special_code_selection },
"service_code" },
operation_completed{ substate_of{ dialog }, "op_completed" },
show_error{ this, "error" }
;
public :
console( context_t ctx ) : so_5::agent_t{ ctx }
{
// Setting up statechart.
dialog
.event( &console::dialog_on_grid )
.event( &console::dialog_on_cancel );
wait_activity
.on_enter( &console::wait_activity_on_enter )
.transfer_to_state< key_digit >( number_selection );
number_selection
.on_enter( &console::apartment_number_on_enter )
.event( &console::apartment_number_on_digit )
.event( &console::apartment_number_on_bell )
.event( &console::apartment_number_on_grid );
special_code_selection_0
.transfer_to_state< key_digit >( user_code_selection )
.just_switch_to< key_grid >( service_code_selection );
user_code_apartment_number
.on_enter( &console::user_code_apartment_number_on_enter )
.event( &console::apartment_number_on_digit )
.event( &console::user_code_apartment_number_on_bell )
.event( &console::user_code_apartment_number_on_grid );
user_code_secret
.on_enter( &console::user_code_secret_on_enter )
.event( &console::user_code_secret_on_digit )
.event( &console::user_code_secret_on_bell )
.event( &console::user_code_secret_on_grid );
service_code_selection
.on_enter( &console::service_code_on_enter )
.event( &console::service_code_on_digit )
.event( &console::service_code_on_bell )
.event( &console::service_code_on_grid );
operation_completed
.on_enter( &console::op_completed_on_enter )
.time_limit( std::chrono::seconds{3}, wait_activity );
show_error
.on_enter( &console::show_error_on_enter )
.on_exit( &console::show_error_on_exit )
.time_limit( std::chrono::seconds{2}, dialog );
}
void so_evt_start() override
{
// Agent starts in the default state. Switch to the appropriate one.
this >>= dialog;
}
private :
// Helper class for imitiation of console's display.
class display
{
public :
void show( const std::string & what )
{
std::cout << "display, msg: '" << what << "'" << std::endl;
}
void show_error( const std::string & what )
{
std::cout << "display, ERR: '" << what << "'" << std::endl;
}
void clear()
{
std::cout << "display cleared" << std::endl;
}
};
// Limitations for various pieces of user input.
static const std::size_t apartment_number_size = 3u;
static const std::size_t secret_code_size = 4u;
static const std::size_t service_code_size = 5u;
// Accumutator for apartment number.
std::string m_apartment_number;
// Accumutator for user's secret code.
std::string m_user_secret_code;
// Accumutator for service code.
std::string m_service_code;
// Message to be displayed in show_error state.
std::string m_error_message;
// Message to be displayed in operation_completed state.
std::string m_op_result_message;
// Display imitator.
display m_display;
void dialog_on_cancel( mhood_t< key_cancel > )
{
// key_cancel always leads to wait_activity state.
// We must switch to wait_activity but not to dialog state
// because dialog has history and switch to dialog state will
// lead to restoration of last active dialog's substate.
this >>= wait_activity;
}
void dialog_on_grid( mhood_t< key_grid > )
{
this >>= special_code_selection;
}
void wait_activity_on_enter()
{
// We can enter in that state only on the very first enter into dialog
// state (because wait_activity is the initial substate). Or after
// completion of some operator. Or after cancelation of any operation.
// Because of that all accumulators can be cleared here.
m_apartment_number.clear();
m_user_secret_code.clear();
m_service_code.clear();
m_display.clear();
}
void apartment_number_on_enter()
{
// Show the current value of accumulator on reentry into this state.
if( !m_apartment_number.empty() )
m_display.show( m_apartment_number );
}
void apartment_number_on_digit( const key_digit & msg )
{
if( m_apartment_number.size() < apartment_number_size )
{
m_apartment_number += msg.m_value;
m_display.show( m_apartment_number );
}
else
initiate_error( "apartment number must be " +
std::to_string( apartment_number_size ) + " digits long" );
}
void apartment_number_on_bell( mhood_t< key_bell > )
{
if( m_apartment_number.size() == apartment_number_size )
{
complete_operation( "dial to apartment #" + m_apartment_number );
}
else
initiate_error( "apartment number must be " +
std::to_string( apartment_number_size ) + " digits long" );
}
void apartment_number_on_grid( mhood_t< key_grid > )
{
initiate_error( "enter apartment number, then 'b'" );
}
void user_code_apartment_number_on_enter()
{
// Show the current value of accumulator on reentry into this state.
if( !m_apartment_number.empty() )
m_display.show( m_apartment_number );
}
void user_code_apartment_number_on_bell( mhood_t< key_bell > )
{
initiate_error( "enter apartment number, then '#', then secret code, "
"then 'b'" );
}
void user_code_apartment_number_on_grid( mhood_t< key_grid > )
{
if( m_apartment_number.size() == apartment_number_size )
{
this >>= user_code_secret;
}
else
initiate_error( "apartment number must be " +
std::to_string( apartment_number_size ) + " digits long" );
}
void user_code_secret_on_enter()
{
// Show the current value of accumulator on reentry into this state.
if( !m_user_secret_code.empty() )
m_display.show( std::string( m_user_secret_code.size(), '*' ) );
}
void user_code_secret_on_digit( const key_digit & msg )
{
if( m_user_secret_code.size() < secret_code_size )
{
m_user_secret_code += msg.m_value;
m_display.show( std::string( m_user_secret_code.size(), '*' ) );
}
else
initiate_error( "secret code must be " +
std::to_string( secret_code_size ) + " digits long" );
}
void user_code_secret_on_bell( mhood_t< key_bell > )
{
if( m_user_secret_code.size() == secret_code_size )
{
complete_operation( "open the door via user secret code: " +
m_apartment_number + "#" + m_user_secret_code );
}
else
initiate_error( "secret code be " +
std::to_string( secret_code_size ) + " digits long" );
}
void user_code_secret_on_grid( mhood_t< key_grid > )
{
initiate_error( "enter user secret code, then 'b'" );
}
void service_code_on_enter()
{
// Show the current value of accumulator on reentry into this state.
if( !m_service_code.empty() )
m_display.show( std::string( m_service_code.size(), '#' ) );
}
void service_code_on_digit( const key_digit & msg )
{
if( m_service_code.size() < service_code_size )
{
m_service_code += msg.m_value;
m_display.show( std::string( m_service_code.size(), '#' ) );
}
else
initiate_error( "service code be " +
std::to_string( service_code_size ) + " digits long" );
}
void service_code_on_bell( mhood_t< key_bell > )
{
initiate_error( "enter service code, then '#'" );
}
void service_code_on_grid( mhood_t< key_grid > )
{
if( m_service_code.size() == service_code_size )
{
complete_operation( "open the door via service code: " + m_service_code );
}
else
initiate_error( "service code must be " +
std::to_string( service_code_size ) + " digits long" );
}
void op_completed_on_enter()
{
m_display.show( m_op_result_message );
}
void show_error_on_enter()
{
m_display.show_error( m_error_message );
}
void show_error_on_exit()
{
m_display.clear();
}
void initiate_error( const std::string & what )
{
m_error_message = what;
this >>= show_error;
}
void complete_operation( const std::string & what )
{
m_op_result_message = what;
this >>= operation_completed;
}
};
// Helper for creation of coop with console agent.
so_5::mbox_t create_console( so_5::environment_t & env )
{
return env.introduce_coop( []( so_5::coop_t & coop ) {
return coop.make_agent< console >()->so_direct_mbox();
} );
}
void demo()
{
// A SObjectizer instance.
[]( so_5::environment_t & ) {},
[]( so_5::environment_params_t & params ) {
params.message_delivery_tracer( so_5::msg_tracing::std_cerr_tracer() );
} }*/;
auto console_mbox = create_console( sobj.environment() );
while( true )
{
std::cout << "enter digit or 'c' or 'b' or '#' (or 'exit' to stop): "
<< std::flush;
std::string choice;
std::cin >> choice;
if( "c" == choice ) so_5::send< key_cancel >( console_mbox );
else if( "b" == choice ) so_5::send< key_bell >( console_mbox );
else if( "#" == choice ) so_5::send< key_grid >( console_mbox );
else if( "exit" == choice ) break;
else if( 1 == choice.size() && std::isdigit( choice[ 0 ] ) )
so_5::send< key_digit >( console_mbox, choice[ 0 ] );
}
// SObjectizer will be stopped automatically.
}
int main()
{
try
{
demo();
}
catch( const std::exception & ex )
{
std::cerr << "Error: " << ex.what() << std::endl;
}
return 0;
}