/* ** CHSM Language System ** chsm.h -- Run-Time library declarations ** ** Copyright (C) 1996-2007 Paul J. Lucas & Fabio Riccardi ** ** This program is free software; you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation; either version 2 of the License, or ** (at your option) any later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software ** Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #ifndef chsm_H #define chsm_H /** * \mainpage CHSM Language System for C++ API Specification * * Provides classes for implementing the run-time of the * * Concurrent Hierarchical State Machine Language System * for C++. * * \section rel_doc Related Documentation *
some_mutex
is unlocked when
* the lock
variable goes out of scope.
* \code
* void f() {
* mutex_lock const lock( some_mutex );
* // ...
* }
* \endcode
*
* @param m The mutex to lock.
*/
mutex_lock( pthread_mutex_t &m );
/**
* Destroy a mutex_lock and unlock the mutex given to the constructor.
*/
~mutex_lock();
private:
int prev_cancel_state_;
pthread_mutex_t &mutex_;
mutex_lock( mutex_lock& ); // forbid copy
mutex_lock& operator=( mutex_lock const& ); // forbid assignment
};
////////// Inlines ////////////////////////////////////////////////////////////
inline mutex_lock::mutex_lock( pthread_mutex_t &m ) : mutex_( m ) {
pthread_setcancelstate( PTHREAD_CANCEL_DISABLE, &prev_cancel_state_ );
pthread_mutex_lock( &mutex_ );
}
inline mutex_lock::~mutex_lock() {
pthread_mutex_unlock( &mutex_ );
pthread_setcancelstate( prev_cancel_state_, 0 );
pthread_testcancel();
}
#endif /* CHSM_MULTITHREADED */
//=============================================================================
/**
* A state
is the simplest kind of state in a machine: it has no
* child states. A state can either be active (in the state) or inactive (not
* in the state); a state may have a parent, and enter/exit actions.
*
* The state
class can be extended either to add additional data
* members and member functions, or to alter its behavior upon being entered or
* exited.
*
* @author Paul J. Lucas
*/
class state {
public:
//
// The constructor arguments must have horribly long names to avoid a name
// collision with user-defined state names in the mem-initializers in the
// resultant C++ code. We use macros to make life easier.
//
# define CHSM_STATE_ARG_LIST(A) \
A(CHSM_NS::machine&) chsm_machine_, \
A(char const*) chsm_name_, \
A(CHSM_NS::parent*) chsm_parent_, \
A(CHSM_NS::state::action) chsm_enter_action_, \
A(CHSM_NS::state::action) chsm_exit_action_, \
A(CHSM_NS::event*) chsm_enter_event_, \
A(CHSM_NS::event*) chsm_exit_event_
/**
* Defines the constructor arguments for the CHSM::state class.
*
* \hideinitializer
*/
# define CHSM_STATE_ARGS CHSM_STATE_ARG_LIST(CHSM_FORMAL)
/**
* Defines the base-class constructor argument mem-initializers for
* the CHSM::state class.
*
* \hideinitializer
*/
# define CHSM_STATE_INIT CHSM_STATE_ARG_LIST(CHSM_ACTUAL)
/**
* The identification number of a state.
*/
typedef int id;
/**
* An action is a function (machine member function) that is optionally
* called upon enter/exit of a state.
*
* @param s The state that is being entered or exited.
* @param trigger The event that triggered the transition.
*/
typedef void (machine::*action)( state const &s, event const &trigger );
/**
* Construct a state.
* When deriving a class from state
, the macros
* CHSM_STATE_ARGS
and CHSM_STATE_INIT
can be
* used to avoid having to deal with the many constructor arguments, e.g.:
* \code
* class place : public CHSM::state {
* public:
* place( CHSM_STATE_ARGS ) : CHSM::state( CHSM_STATE_INIT ) {
* // ...
* }
* // ...
* };
* \endcode
*/
state( CHSM_STATE_ARGS );
/**
* Destroy a state.
*/
virtual ~state();
/**
* Returns whether this state is active.
*
* @return Returns true
only if the state is active.
*/
bool active() const { return state_ & S_active; }
/**
* Clears the history for all child clusters of a cluster, recursively.
* Plain states have neither children nor a history. Placing this function
* here is a wart on the design; by having it here, however, run-time
* type-identification can be avoided.
*/
virtual void deep_clear();
/**
* Return the name of the state.
*
* @return Returns said name.
*/
char const* name() const { return name_; }
/**
* Return the parent cluster or set.
*
* @return Returns the parent or null
if none.
*/
parent* parent_of() const { return parent_; }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
/**
* The CHSM::machine this event belongs to.
*/
machine& chsm() const { return machine_; }
protected:
/**
* \internal
*
* The CHSM::machine the state belongs to.
*/
machine &machine_;
/**
* Enter a state.
* If this state's parent state (if any) isn't active, enter it first.
* The event $enter(
state)
is broadcast
* and the state's enter-action block is executed, if any.
*
* When overriding in a derived class, the form must be:
* \code
* bool enter( CHSM::event const &trigger, CHSM::state *from_child ) {
* if ( !CHSM::state::enter( trigger, from_child ) )
* return false;
* // ... new functionality here ...
* return true;
* }
* \endcode
*
* @param trigger The event that triggered the transition.
* @param from_child Not used here.
* @return Returns true
only if the state was actually
* entered, i.e., it wasn't already active.
*/
virtual bool enter( event const &trigger, state *from_child = 0 );
/**
* Exit a state.
* If the "to" state doesn't have this state's parent state (if any) as an
* ancestor, exit this state's parent state first. The event
* $exit(
state)
is broadcast and the
* state's exit-action block is executed, if any.
*
* When overriding in a derived class, the form must be:
* \code
* boolean exit( CHSM::event const &trigger, CHSM::state *to ) {
* if ( !CHSM::state::exit( trigger, to ) )
* return false;
* // ... new functionality here ...
* return true;
* }
* \endcode
*
* @param trigger The event that triggered the transition.
* @param to If not null
, the state to transition to.
* @return Returns true
only if the state was actually exited,
* i.e., it wasn't already active.
*/
virtual bool exit( event const &trigger, state *to = 0 );
private:
char const *const name_; //!< State name.
parent *const parent_; //!< Parent, if any.
enum {
S_inactive = 0x00,
S_active = 0x01,
//
// S_active_disabled is used to prevent making more than one
// nondeterministic transition from the same state. See the comments
// in event.c for more information.
//
S_active_disabled = 0x02 | S_active
};
unsigned state_; //!< The state of the state.
/**
* The enter/exit events for the state are non-null only if they are
* actually used in the machine. Their values are determined by the
* CHSM-to-C++ compiler.
*/
event *const enter_event_, *const exit_event_;
/**
* See the comment for the "action" declaration.
*/
action const enter_action_, exit_action_;
friend class cluster;
friend class event;
friend class machine;
friend class parent;
friend class set;
};
//=============================================================================
/**
* A transition
is a simple struct
containing
* information for a transition from one state to another in a machine. The
* information contains whether the transition has an associated condition to
* test prior to taking it, the "from" and "to" states, and an optional action
* to be performed.
*
* All the data members ideally should be declared const
. In the
* code emitted by the CHSM-to-C++ compiler, we want to be able to use
* aggregate initialization, but C++ does not permit aggregates to have
* const
data members (see section 8.5.1 of the ANSI/ISO C++
* reference manual), so we leave the const
out. However, the
* code emitted declares the aggregate as a whole to be const
, so
* it amounts to the same thing.
*
* @author Paul J. Lucas
*/
struct transition {
/**
* A condition is a function to be evaluated prior to a transition being
* performed. The transition is performed only if the function returns
* true
.
*
* @param trigger The event that is causing the transition.
* @return Returns true
only if the condition evaluates to
* true
.
*/
typedef bool (machine::*condition)( event const &trigger );
/**
* An action is a function to be executed upon performing a transition
* from one state to another.
*
* @param trigger The event that caused the transition.
*/
typedef void (machine::*action)( event const &trigger );
/**
* A target is a function to be evaluated prior to a transition being
* performed to determine the state to transition to.
*
* @param trigger The event that caused the transition.
* @return Returns the state that the transition should go to or
* null
to abort the transition.
*/
typedef state* (machine::*target)( event const &trigger );
/**
* The condition to be evaluated prior to a transition being performed, if
* any.
*/
condition condition_;
/**
* The state this transition is transitioning from.
*/
state::id from_id_;
/**
* The state this transition is transitioning to.
*/
state::id to_id_;
/**
* The target function to be evaluated prior to a transition being
* performed to determine the state to transition to, if any.
*/
target target_;
/**
* The action to be performed, if any.
*/
action action_;
/**
* Returns whether this transition is internal.
*
* @return Returns true
only if the transition is internal.
*/
bool is_internal() const;
/**
* Checks whether a transition between the two given statess would be
* legal. A transition is illegal only if the two states' nearest common
* ancestor is a CHSM::set.
*
* @param s1 One of the states.
* @param s2 The other state.
* @return Returns true
only if the transition is legal.
*/
static bool is_legal( state const *s1, state const *s2 );
};
//=============================================================================
/**
* The occurrence of an event ("broadcast") is that which causes transitions in
* a machine. An event has a name, may optionally be derived from another, and
* may optionally have parameters during a broadcast.
*
* @author Paul J. Lucas
*/
class event {
protected:
/*
** Note: Even though this section is protected (as opposed to private), it
** is so that derived classes emitted by the CHSM-to-C++ compiler will have
** access and it is not the intent that end users will.
*/
//
// These typedefs are placed up here because they are used in the
// CHSM_EVENT_ARGS macro used in event's constructor.
//
typedef int transition_id;
typedef transition_id const *transition_list;
/**
* \internal
*
* The CHSM::machine this event belongs to.
*
* This data member is declared up here since it is used inline by
* param_block::chsm(). Some compilers are broken and need it declared
* before use as opposed to obeying the "rewriting rule."
*/
machine &machine_;
public:
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# define CHSM_EVENT_ARG_LIST(A) \
A(CHSM_NS::machine*) chsm_machine_, \
A(transition_list) chsm_transition_list_, \
A(char const*) chsm_name_, \
A(CHSM_NS::event*) chsm_base_event_
/**
* Defines the constructor arguments for the CHSM::event class.
*
* \hideinitializer
*/
# define CHSM_EVENT_ARGS CHSM_EVENT_ARG_LIST(CHSM_FORMAL)
/**
* Defines the base-class constructor argument mem-initializers for the
* CHSM::event class.
*
* \hideinitializer
*/
# define CHSM_EVENT_INIT CHSM_EVENT_ARG_LIST(CHSM_ACTUAL)
/**
* Construct an event.
*/
event( CHSM_EVENT_ARGS );
/**
* Destroy an event.
*/
virtual ~event();
/**
* Broadcast an event to a machine. If it has a precondition, it will be
* evaluated first. Actions may be performed and transitions may occur.
*/
void operator()() { lock_broadcast(); }
/**
* Checks whether this event would trigger a transition.
*
* @return Returns true
only if this event triggers transition
*/
bool active() const;
/**
* Checks whether this event is of a particular event type. This function
* is a convenient shorthand.
*
* @return Returns true
only if this event is of the given
* type.
*/
template< typename EventClass > bool is_type() const {
return dynamic_castchsm
() returning the owning
* event's machine is that only this param_block is a friend of the
* event class: by having a protected function return it, all derived
* classes can obtain it.
*/
machine& chsm() const { return chsm_event_.machine_; }
/**
* Evaluate the precondition for a transition on an event. You should
* never need to call this explicitly. It is called via code generated
* by the CHSM-to-C++ compiler.
*
* @return The default always returns true
.
*/
virtual bool precondition() const;
friend class event;
};
protected:
/*
** Note: Even though this section is protected (as opposed to private),
** it is so that derived classes emitted by the CHSM-to-C++ compiler will
** have access and it is not the intent that end users will.
*/
/**
* \internal
*
* The in_progress_ "flag" is actually a counter because, during a
* transition, the same event (or an event derived from it) can be
* broadcast more than once; however, nothing must be done for "nested"
* broadcasts, i.e., when it's > 0.
*/
unsigned in_progress_;
/**
* \internal
*
* The current param_block, if any.
*/
void *param_block_;
/**
* \internal
*
* Broadcast an event to a machine. If it has a precondition, it will be
* evaluated first. Find transitions that are to be taken in response to
* this event: if one is found, queue it and run the CHSM transition
* algorithm.
*
* @param param_block A pointer to a param_block, if any.
*/
void broadcast( void *param_block );
#ifdef CHSM_MULTITHREADED
/**
* \internal
*
* This extra class is used so the operator()
code generated
* by the CHSM-to-C++ compiler doesn't need direct access to
* machine::machine_lock_
.
*/
class machine_lock : public mutex_lock {
public:
machine_lock( machine& );
};
#endif /* CHSM_MULTITHREADED */
private:
/**
* This is the set of transitions an event possibly triggers. It has to be
* a "native" C++ array of int rather than, say, an STL vector because the
* CHSM-to-C++ compiler emits native arrays that are aggregate initialized.
*/
transition_list const transitions_;
char const *const name_; //!< Event name.
event *const base_event_; //!< Base event, if any.
static int const no_transition_id_; //!< Sentinel for end().
/**
* Returns whether this event has no transitions.
* @return Returns true
only if this event has no transitions.
*/
bool empty() const;
/**
* Do a broadcast, but lock first. This function is called only in
* state::enter() and state::exit().
*/
void lock_broadcast();
/** The type of the value returned by const_iterator. */
typedef transition value_type;
/** A pointer to a const
value_type
. */
typedef value_type const* const_pointer;
/** A reference to a const
value_type
. */
typedef value_type const& const_reference;
class const_iterator;
friend class const_iterator;
/**
* This is an iterator in "STL style" to iterate over the transitions an
* event possibly triggers.
*/
class const_iterator {
public:
/**
* Construct a const_iterator.
*/
const_iterator() { }
/**
* Returns the ID of the transition that the iterator is positioned at.
* @return Returns said ID.
*/
transition_id id() const { return *t_id_; }
/**
* Returns an event::const_reference to the transition the iterator is
* positioned at.
* @return Returns said reference.
*/
const_reference operator* () const { return t_[ *t_id_ ]; }
/**
* Returns an event::const_pointer to the transition the iterator is
* positioned at.
* @return Returns said pointer.
*/
const_pointer operator->() const { return &operator*(); }
/**
* Pre-increment the iterator.
* @return Returns the iterator positioned at the next transition, if
* any.
*/
const_iterator& operator++() {
++t_id_;
bump();
return *this;
}
/**
* Post-increment the iterator.
* @return Returns a new event::const_iterator positioned at the
* transition the original iterator was positioned at prior to the
* increment.
*/
const_iterator operator++(int) {
const_iterator const temp = *this;
++*this;
return temp;
}
/**
* Compare two iterators for equality.
* @param i The first iterator.
* @param j The second iterator.
* @return Returns true
only if the two iterators are
* positioned at the same transition.
*/
friend bool operator==( const_iterator const &i,
const_iterator const &j ) {
return *i.t_id_ == *j.t_id_;
}
/**
* Compare two iterators for inequality.
* @param i The first iterator.
* @param j The second iterator.
* @return Returns true
only if the two iterators are not
* positioned at the same transition.
*/
friend bool operator!=( const_iterator const &i,
const_iterator const &j ) {
return !( i == j );
}
protected:
const_pointer t_; // machine's transitions
transition_list t_id_;
event const *base_event_;
const_iterator( const_pointer, transition_list, event const* );
private:
void bump();
friend class event;
};
/**
* Returns a new const_iterator positioned at the first transition in an
* event.
* @return Returns said iterator.
*/
const_iterator begin() const;
/**
* Returns a new const_iterator positioned one past the last transition in
* an event.
* @return Returns said iterator.
*/
const_iterator end() const {
return const_iterator( 0, &no_transition_id_, 0 );
}
/**
* Do post-broadcast clean-up for a broadcasted event.
* @see Bjarne Stroustrup. "The C++ Programming Language, 3rd ed."
* Addison-Wesley, Reading, MA, 1997. pp. 116-118.
*/
void broadcasted();
friend class machine;
friend bool state::enter( event const&, state* );
friend bool state::exit ( event const&, state* );
};
////////// Inlines ////////////////////////////////////////////////////////////
inline bool event::empty() const {
return *transitions_ == no_transition_id_;
}
/**
* Compare two events for equality.
* Since there is only one instance of each event per machine, it is sufficient
* just to compare event addresses.
* The only caveat is that the same event for different instances of a
* machine will not compare equal.
*
* @param a The first event.
* @param b The second event.
* @return Returns true
only if the events are equal.
*/
inline bool operator==( event const &a, event const &b ) {
return &a == &b;
}
/**
* Compare two events for inequality.
* Since there is only one instance of each event per machine, it is sufficient
* just to compare event addresses.
* The only caveat is that the same event for different instances of a
* machine will compare not equal.
*
* @param a The first event.
* @param b The second event.
* @return Returns true
only if the events are not equal.
*/
inline bool operator!=( event const &a, event const &b ) {
return !( a == b );
}
//=============================================================================
/**
* A machine contains an entire Concurrent, Hierarchical, Finite State machine.
* Every machine is self-contained: there may be multiple instances of the same
* machine (or different machines) in different states in the same program.
*
* @author Paul J. Lucas
*/
class machine {
/**
* This is the event used to start the machine.
*
* This data member is placed up here due to a bug in some C++ compilers
* where an as-of-yet unseen static data member can not be used as a
* default argument for a member function.
*/
static event prime_;
public:
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
//
// The constructor arguments must have horribly long names to avoid a
// name collision with user-defined constructor arguments in derived
// classes. We use macros to make life easier.
//
# define CHSM_MACHINE_ARG_LIST(A) \
A(CHSM_NS::state*) chsm_state_ A([]), \
A(CHSM_NS::cluster&) chsm_root_, \
A(CHSM_NS::transition const) chsm_transition_ A([]), \
A(CHSM_NS::event const*) chsm_taken_ A([]), \
A(CHSM_NS::state*) chsm_target_ A([]), \
A(int) chsm_transitions_in_machine_
/**
* Defines the constructor arguments for the CHSM::machine class.
*
* \hideinitializer
*/
# define CHSM_MACHINE_ARGS CHSM_MACHINE_ARG_LIST(CHSM_FORMAL)
/**
* Defines the base-class constructor argument mem-initializers for
* the CHSM::machine class.
*
* \hideinitializer
*/
# define CHSM_MACHINE_INIT CHSM_MACHINE_ARG_LIST(CHSM_ACTUAL)
/**
* Destroy a machine.
*/
virtual ~machine();
/**
* A machine as a whole is active only if its root cluster is.
*
* @return Returns true
only if this machine is active.
*/
bool active() const;
/**
* Enter a machine via a transition by entering its root cluster.
*
* @param trigger The event to start the machine.
* @return Returns true
only if the machine was actually
* entered, i.e., it wasn't already active.
*/
virtual bool enter( event const &trigger);
virtual bool enter() { return enter(prime_); }
/**
* Exit a machine via a transition by exiting its root cluster.
*
* @param trigger The event to exit the machine.
* @return Returns true
only if the machine was actually
* exited, i.e., it wasn't already inactive.
*/
virtual bool exit( event const &trigger);
virtual bool exit() { return exit(prime_); }
/** The type of the value returned by const_iterator. */
typedef state value_type;
/** A pointer to a const
value_type
. */
typedef value_type const* const_pointer;
/** A reference to a const
value_type
. */
typedef value_type const& const_reference;
/**
* This is an iterator in "STL style" to iterate over the (direct child)
* states a machine has.
*/
class const_iterator {
public:
/**
* Construct a const_iterator.
*/
const_iterator() { }
/**
* Returns a machine::const_reference to the state the iterator is
* positioned at.
* @return Returns said reference.
*/
const_reference operator*() const { return **p_; }
/**
* Returns a machine::const_pointer to the state the iterator is
* positioned at.
* @return Returns said pointer.
*/
const_pointer operator->() const { return *p_; }
/**
* Pre-increment the iterator.
* @return Returns the iterator positioned at the next state, if any.
*/
const_iterator& operator++() { return ++p_, *this; }
/**
* Post-increment the iterator.
* @return Returns a new machine::const_iterator positioned at the
* state the original iterator was positioned at prior to the
* increment.
*/
const_iterator operator++(int) { return const_iterator( p_++ ); }
/**
* Compare two iterators for equality.
* @param i The first iterator.
* @param j The second iterator.
* @return Returns true
only if the two iterators are
* positioned at the same state.
*/
friend bool operator==( const_iterator const &i,
const_iterator const &j ) {
return *i.p_ == *j.p_;
}
/**
* Compare two iterators for inequality.
* @param i The first iterator.
* @param j The second iterator.
* @return Returns true
only if the two iterators are not
* positioned at the same state.
*/
friend bool operator!=( const_iterator const &i,
const_iterator const &j ) {
return !( i == j );
}
private:
typedef const_pointer const *iter_type;
iter_type p_;
const_iterator( iter_type p ) : p_( p ) { }
friend class machine;
};
/**
* Returns a new const_iterator positioned at the first state in a machine.
* @return Returns said iterator.
*/
const_iterator begin() const {
return const_iterator( (const_iterator::iter_type)state_ );
}
/**
* Returns a new const_iterator positioned one past the last state in a
* machine.
* @return Returns said iterator.
*/
const_iterator end() const {
return const_iterator( (const_iterator::iter_type) &nil_);
}
enum {
/**
* Do not print any debugging information.
* \hideinitializer
*/
D_none = 0x00,
/**
* Prints entrances to and exits from states.
* \hideinitializer
*/
D_enex = 0x01,
/**
* Prints events queueing and dequeuing.
* \hideinitializer
*/
D_event = 0x02,
/**
* Reports progress during the broadcast algorithm.
* \hideinitializer
*/
D_alg = 0x04,
/**
* Reports all debugging information.
* \hideinitializer
*/
D_all = D_enex | D_event | D_alg
};
/**
* Gets the current debugging state.
*
* @return Returns said state.
*/
unsigned debug() const { return debug_; }
/**
* Sets the debugging state. Debugging information is printed to system
* error.
*
* @param state The state to set that is the bitwise "or" of the debugging
* states #D_enex, #D_event, #D_alg, or #D_all.
* @return Returns the previous debugging state.
*/
unsigned debug( unsigned state ) {
unsigned const temp = debug_;
debug_ = state;
return temp;
}
/**
* Dumps a printout of the current state to standard error. The dump
* consists of each state's name, one per line, preceded by an asterisk
* only if it is active; a space otherwise.
*/
void dump_state() const;
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
protected:
/**
* Construct a machine.
* When deriving a class from machine
, the macros
* CHSM_MACHINE_ARGS
and CHSM_MACHINE_INIT
can be
* used to avoid having to deal with the many constructor arguments, e.g.:
* \code
* class my_machine : public CHSM::machine {
* public:
* my_machine( CHSM_MACHINE_ARGS ) :
* CHSM::machine( CHSM_MACHINE_INIT ) {
* // ...
* }
* // ...
* };
* \endcode
*/
machine( CHSM_MACHINE_ARGS );
private:
/**
* The type of the queue of events that have been broadcasted.
*/
typedef std::listtrue
only when the transition algorithm is in
* progress and is used to prevent recursive calls.
*/
bool in_progress_;
event_queue event_queue_; //!< Events that have been broadcasted.
unsigned debug_; //!< The current debugging state.
static const_pointer nil_; //!< Sentinel for end().
static int const no_state_id_; //!< Used by internal transitions.
#ifdef CHSM_MULTITHREADED
mutable pthread_mutex_t machine_lock_;
#endif
/**
* Perform all the transitions for all events queued.
*/
void algorithm();
friend class event;
friend class event::machine_lock;
friend class parent;
friend struct transition;
};
//=============================================================================
/**
* A parent
is-an abstract state that has child states.
* It is an implementation detail to factor common data members and member
* functions for the CHSM::cluster and CHSM::set classes.
*
* @author Paul J. Lucas
*/
class parent : public state {
protected:
/**
* \internal
*
* The array of state IDs that are the child states.
*/
typedef int const* child_list;
public:
# define CHSM_PARENT_ARG_LIST(A) \
CHSM_STATE_ARG_LIST(A), \
A(child_list) chsm_children_
/**
* Defines the constructor arguments for the CHSM::parent class.
*
* \hideinitializer
*/
# define CHSM_PARENT_ARGS CHSM_PARENT_ARG_LIST(CHSM_FORMAL)
/**
* Defines the base-class constructor argument mem-initializers for the
* CHSM::parent class.
*
* \hideinitializer
*/
# define CHSM_PARENT_INIT CHSM_PARENT_ARG_LIST(CHSM_ACTUAL)
/** The type of the value returned by iterator and const_iterator. */
typedef state value_type;
/** A pointer to a value_type
. */
typedef value_type* pointer;
/** A pointer to a const
value_type
. */
typedef value_type const* const_pointer;
/** A reference to a value_type
. */
typedef value_type& reference;
/** A reference to a const
value_type
. */
typedef value_type const& const_reference;
/**
* Clears this parent's history. However, since parents don't have a
* history, there is nothing to do for them; however, call
* deep_clear()
for all child states.
*/
virtual void deep_clear();
/**
* Returns whether this parent has any child states.
* @return Returns true
only if this parent has any child
* states.
*/
bool empty() const { return *children_ == no_child_id_; }
/**
* Returns a parent::reference to the first child state.
* @return Returns said reference.
*/
reference front() { return *(machine_.state_)[*children_]; }
/**
* Returns a parent::const_reference to the first child state.
* @return Returns said reference.
*/
const_reference front() const { return *(machine_.state_)[*children_]; }
class iterator;
friend class iterator;
/**
* This is an iterator in "STL style" to iterate over the child states of a
* parent state.
*/
class iterator {
public:
/**
* Construct an iterator.
*/
iterator() { }
/**
* Returns a parent::reference to the state the iterator is positioned
* at.
* @return Returns said reference.
*/
reference operator* () const { return *p_[ *c_ ]; }
/**
* Returns a parent::pointer to the state the iterator is positioned
* at.
* @return Returns said pointer.
*/
pointer operator->() const { return p_[ *c_ ]; }
/**
* Pre-increment the iterator.
* @return Returns the iterator positioned at the next state, if any.
*/
iterator& operator++() {
return ++c_, *this;
}
/**
* Post-increment the iterator.
* @return Returns a new iterator positioned at the state the original
* iterator was positioned at prior to the increment.
*/
iterator operator++(int) {
return iterator( p_, c_++ );
}
/**
* Compare two iterators for equality.
* @param i The first iterator.
* @param j The second iterator.
* @return Returns true
only if the two iterators are
* positioned at the same state.
*/
friend bool operator==( iterator const &i, iterator const &j ) {
return *i.c_ == *j.c_;
}
/**
* Compare two iterators for inequality.
* @param i The first iterator.
* @param j The second iterator.
* @return Returns true
only if the two iterators are not
* positioned at the same state.
*/
friend bool operator!=( iterator const &i, iterator const &j ) {
return !( i == j );
}
protected:
pointer const *p_;
child_list c_;
iterator( pointer const *p, child_list c ) : p_( p ), c_( c ) { }
friend class parent;
};
/**
* Returns a parent::iterator positioned at the first child state of a
* parent.
* @return Returns said iterator.
*/
iterator begin() {
return iterator( machine_.state_, children_ );
}
/**
* Returns a parent::iterator positioned at one past the last child state
* of a parent.
* @return Returns said iterator.
*/
iterator end() {
return iterator( 0, &no_child_id_ );
}
class const_iterator;
friend class const_iterator;
/**
* This is an iterator in "STL style" to iterate over the child states of a
* parent state.
*/
class const_iterator {
public:
/**
* Construct a const_iterator.
*/
const_iterator() { }
/**
* Returns a parent::const_reference to the state the iterator is
* positioned at.
* @return Returns said reference.
*/
const_reference operator*() const { return *p_[ *c_ ]; }
/**
* Returns a parent::const_pointer to the state the iterator is
* positioned at.
* @return Returns said reference.
*/
const_pointer operator->() const { return p_[ *c_ ]; }
/**
* Pre-increment the iterator.
* @return Returns the iterator positioned at the next state, if any.
*/
const_iterator& operator++() {
return ++c_, *this;
}
/**
* Post-increment the iterator.
* @return Returns a new iterator positioned at the state the original
* iterator was positioned at prior to the increment.
*/
const_iterator operator++(int) {
return const_iterator( p_, c_++ );
}
/**
* Compare two iterators for equality.
* @param i The first iterator.
* @param j The second iterator.
* @return Returns true
only if the two iterators are
* positioned at the same state.
*/
friend bool operator==( const_iterator const &i,
const_iterator const &j ) {
return *i.c_ == *j.c_;
}
/**
* Compare two iterators for inequality.
* @param i The first iterator.
* @param j The second iterator.
* @return Returns true
only if the two iterators are not
* positioned at the same state.
*/
friend bool operator!=( const_iterator const &i,
const_iterator const &j ) {
return !( i == j );
}
protected:
pointer const *p_;
child_list c_;
const_iterator( pointer const *p, child_list c ) : p_( p ), c_( c ) { }
friend class parent;
};
/**
* Returns a parent::const_iterator positioned at the first child state of
* a parent.
* @return Returns said iterator.
*/
const_iterator begin() const {
return const_iterator( machine_.state_, children_ );
}
/**
* Returns a parent::const_iterator positioned at one past the last child
* state of a parent.
* @return Returns said iterator.
*/
const_iterator end() const {
return const_iterator( 0, &no_child_id_ );
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
protected:
/**
* \internal
*
* Construct a parent.
*/
parent( CHSM_PARENT_ARGS );
/**
* \internal
*
* The purpose of switch_child() is to allow a child state to ask
* permission of its parent state to be entered and become the active
* state. The default behavior simply returns true; cluster overrides
* it -- see its comment for further details.
*
* @param child The child to switch to.
*/
virtual bool switch_child( state *child );
// for access to switch_child()
friend bool state::enter( event const&, state* );
private:
child_list const children_; //!< The child states.
static int const no_child_id_; //!< Sentinel for end().
};
//=============================================================================
/**
* A cluster
is-a CHSM::parent that can have at most one
* child-state active at any given time.
*
* A cluster may have a history. If a cluster does not have a history, or it
* does have a history but has not previously been active, then the child-state
* entered after it itself is entered is the default child-state; if a
* cluster does have a history and it has been visited before, then the
* child-state entered after it itself is entered is the one that was last
* active.
*
* A cluster may alternatively have a deep history. Such a cluster
* behaves exactly as one with an ordinary history; the difference is that all
* clusters lexically-enclosed by it in the CHSM description also have a
* history.
*
* The cluster
class can be extended either to add additional data
* members and member functions, or to alter its behavior upon being entered or
* exited.
*
* @author Paul J. Lucas
*/
class cluster : public parent {
public:
# define CHSM_CLUSTER_ARG_LIST(A) \
CHSM_PARENT_ARG_LIST(A), \
A(bool) chsm_history_
/**
* Defines the constructor arguments for the CHSM::cluster class.
*
* \hideinitializer
*/
# define CHSM_CLUSTER_ARGS CHSM_CLUSTER_ARG_LIST(CHSM_FORMAL)
/**
* Defines the base-class constructor argument mem-initializers for
* the CHSM::cluster class.
*
* \hideinitializer
*/
# define CHSM_CLUSTER_INIT CHSM_CLUSTER_ARG_LIST(CHSM_ACTUAL)
/**
* Construct a cluster.
* When deriving a class from cluster
, the macros
* CHSM_CLUSTER_ARGS
and CHSM_CLUSTER_INIT
can be
* used to avoid having to deal with the many constructor arguments.
* See CHSM::state for an example.
*/
cluster( CHSM_CLUSTER_ARGS );
/**
* Clear the history.
*/
void clear() { last_child_ = &front(); }
/**
* Clear the history, if any; then clear all child cluster's history, if
* any.
*/
virtual void deep_clear();
/**
* Enter a cluster via a transition and also enter one of its child states.
*
* @param trigger The event that triggered the transition.
* @param from_child If not null
, this child state of the
* cluster is being entered directly.
* @return Returns true
only if the cluster was actually
* entered, i.e., it wasn't already active.
*/
virtual bool enter( event const &trigger, state *from_child = 0 );
/**
* Exit a cluster via a transition, but first exit its active child state.
*
* @param trigger The event that triggered the transition.
* @param to The state that is being transitioned to.
* @return Returns true
only if the cluster actually exited,
* i.e., it was active.
*/
virtual bool exit( event const &trigger, state *to = 0 );
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
private:
bool const history_; // do we have a history?
state *active_child_, *last_child_;
// inherited
virtual bool switch_child( state* );
};
//=============================================================================
/**
* A set
is-a CHSM::parent that has either all or no
* child-states active. All child-states are entered after and exited before
* their parent-state. The order that child-states are entered and exited is
* undefined.
*
* The set
class can be extended either to add additional data
* members and member functions, or to alter its behavior upon being entered or
* exited.
*
* @author Paul J. Lucas
*/
class set : public parent {
public:
# define CHSM_SET_ARG_LIST(A) \
CHSM_PARENT_ARG_LIST(A)
/**
* Defines the constructor arguments for the CHSM::set class.
*
* \hideinitializer
*/
# define CHSM_SET_ARGS CHSM_SET_ARG_LIST(CHSM_FORMAL)
/**
* Defines the base-class constructor argument mem-initializers for
* the CHSM::set class.
*
* \hideinitializer
*/
# define CHSM_SET_INIT CHSM_SET_ARG_LIST(CHSM_ACTUAL)
/**
* Construct a set.
* When deriving a class from set
, the macros
* CHSM_SET_ARGS
and CHSM_SET_INIT
can be used to
* avoid having to deal with the many constructor arguments.
* See CHSM::state for an example.
*/
set( CHSM_SET_ARGS ) : parent( CHSM_PARENT_INIT ) { }
/**
* Enter a set via a transition and also enter all of its child states.
*
* @param trigger The event that triggered the transition.
* @param from_child Not used here.
* @return Returns true
only if the set was actually entered,
* i.e., it wasn't already active.
*/
virtual bool enter( event const &trigger, state *from_child = 0 );
/**
* Exit a set via a transition, but first exit all of its child states.
*
* @param trigger The event that triggered the transition.
* @param to The state we are transitioning to.
* @return Returns true
only if the set actually exited, i.e.,
* it was active.
*/
virtual bool exit( event const &trigger, state *to = 0 );
};
////////// Inlines ////////////////////////////////////////////////////////////
inline event::const_iterator event::begin() const {
//
// This has to be defined down here so that the declaration for the machine
// class has been seen by the C++ compiler.
//
return const_iterator( machine_.transition_, transitions_, base_event_ );
}
#ifdef CHSM_MULTITHREADED
inline event::machine_lock::machine_lock( machine &m ) :
mutex_lock( m.machine_lock_ )
{
}
#endif /* CHSM_MULTITHREADED */
inline bool machine::active() const {
//
// A machine as a whole is active only if its root cluster is.
//
return root_.active();
}
inline bool transition::is_internal() const {
return to_id_ == machine::no_state_id_ && !target_;
}
inline bool event::active() const {
for (const_iterator i = begin(); i != end(); ++i) {
const state* from = machine_.state_[ i->from_id_ ];
if (from->active())
return true;
}
return false;
}
} // namespace
////////// Namespace stuff ////////////////////////////////////////////////////
/**
* If, for whatever reason, you don't want the long namespace name aliased to a
* shorter one, define CHSM_NO_ALIAS_NS
.
*/
#ifndef CHSM_NO_ALIAS_NS
/**
* If you want the short namespace alias name to be something other
* than "CHSM", define CHSM_NS_ALIAS
to be what you want.
*/
# ifndef CHSM_NS_ALIAS
# define CHSM_NS_ALIAS CHSM
# endif
namespace CHSM_NS_ALIAS = CHSM_NS;
#endif /* CHSM_NO_ALIAS_NS */
#endif /* chsm_H */
/* vim:set et sw=4 ts=4: */