Boost logo

Boost Users :

From: Andreas Huber (ahd6974-spamgroupstrap_at_[hidden])
Date: 2005-07-19 11:50:31


Dave <pplppp <at> gmail.com> writes:

[snip]
> > I'm not sure whether I understand 100% but it seems if you use the
> > technique above this shouldn't be a problem.
>
> I followed your example/suggestions and wrote the following test program.
> Conceptually speaking, the PumpBase FSM has 2 states, Idle and Running, the
> designer of this FSM want to allow customization of these 2 states, which is
> limited to adding inner states. The customization for the Idle state (MyIdle)
> could be developed by Bob, while the customization for the Running state
> (MyRunning) could be developed by Mary and since Bob and Mary are adding
inner
> states to Idle and Running respectively, their code should not depend on each
> other. However, as my test program shows, MyPump class needs to have
knowledge
> of both MyIdle and MyRunning and provide the implementation for the react
> functions.

Ahh, now I understand. I first thought you want to have one person working on
one state machine each (i.e. one on PumpBase and one on MyPump). By
reorganizing the source code a bit you can do even what you describe:

*** PumpBase.hpp ***

#ifndef PUMP_BASE_HPP_INCLUDED
#define PUMP_BASE_HPP_INCLUDED

#include <boost/statechart/event.hpp>
#include <boost/statechart/state_machine.hpp>
#include <boost/statechart/state.hpp>
#include <boost/statechart/custom_reaction.hpp>

namespace sc = boost::statechart;

struct EvStart : sc::event< EvStart > {};
struct EvPreIdleStart : sc::event< EvPreIdleStart > {};
struct EvPreRunningStart : sc::event< EvPreRunningStart > {};

class EntryExitDisplayer
{
  public:
    // Dummy implementation
    EntryExitDisplayer( const char * ) {}
};

//////////////////////////////////////////////////////////////////////////////
// Base FSM
//////////////////////////////////////////////////////////////////////////////
struct PreIdle;
struct PreRunning;
struct PumpBase : sc::state_machine< PumpBase, PreIdle >
{
  virtual sc::result react(
    PreIdle & preIdle, const EvPreIdleStart & ) const;
  virtual sc::result react(
    PreRunning & preRunning, const EvPreRunningStart & ) const;
};

struct PreIdle : sc::state< PreIdle, PumpBase >, EntryExitDisplayer
{
  typedef sc::custom_reaction< EvPreIdleStart > reactions;

  PreIdle( my_context ctx ) : my_base( ctx ), EntryExitDisplayer( "PreIdle" )
  {
    // Posting is now also possible with stack-allocated events
    post_event( EvPreIdleStart() );
  }

  sc::result react( const EvPreIdleStart & evt )
  {
    // Instead of mentioning the state machine directly with context< .. >,
    // the following is often more convenient
    return outermost_context().react( *this, evt );
  }
};

struct PreRunning : sc::state< PreRunning, PumpBase >, EntryExitDisplayer
{
  typedef sc::custom_reaction< EvPreRunningStart > reactions;

  PreRunning( my_context ctx ) :
    my_base( ctx ), EntryExitDisplayer( "PreRunning" )
  {
    post_event( EvPreRunningStart() );
  }

  sc::result react( const EvPreRunningStart & evt )
  {
    return outermost_context().react( *this, evt );
  }
};

#endif

*** MyPump.hpp ***

#ifndef MY_PUMP_HPP_INCLUDED
#define MY_PUMP_HPP_INCLUDED

#include "PumpBase.hpp"

namespace sc = boost::statechart;

///////////////////////////////////////////////////////////////////////////////
// Custom FSM
///////////////////////////////////////////////////////////////////////////////
struct MyPump : PumpBase
{
  virtual sc::result react(
    PreIdle & preIdle, const EvPreIdleStart & ) const;

  virtual sc::result react(
    PreRunning & preRunning, const EvPreRunningStart & ) const;
};

#endif

*** Idle.cpp ***

#include <boost/statechart/simple_state.hpp>

#include "PumpBase.hpp"
#include "MyPump.hpp"

// PumpBase customization
struct Idle : sc::simple_state< Idle, PumpBase >, EntryExitDisplayer
{
  typedef sc::transition< EvStart, PreRunning > reactions;

  Idle() : EntryExitDisplayer( "Idle" ) {}
};

sc::result PumpBase::react(
  PreIdle & preIdle, const EvPreIdleStart & ) const
{
  return preIdle.transit< Idle >();
}

// MyPump customization
struct MyIdle : sc::simple_state< MyIdle, PumpBase >, EntryExitDisplayer
{
  typedef sc::transition< EvStart, PreRunning > reactions;

  MyIdle() : EntryExitDisplayer( "MyIdle" ) {}
};

sc::result MyPump::react(
  PreIdle & preIdle, const EvPreIdleStart & ) const
{
  return preIdle.transit< MyIdle >();
}

*** Running.cpp: ***

#include <boost/statechart/simple_state.hpp>

#include "PumpBase.hpp"
#include "MyPump.hpp"

// PumpBase customization
struct Running : sc::simple_state< Running, PumpBase >, EntryExitDisplayer
{
  Running() : EntryExitDisplayer( "Running" ) {}
};

sc::result PumpBase::react(
  PreRunning & preRunning, const EvPreRunningStart & ) const
{
  return preRunning.transit< Running >();
}

// MyPump customization
struct MyRunning : sc::simple_state< MyRunning, PumpBase >, EntryExitDisplayer
{
  MyRunning() : EntryExitDisplayer( "MyRunning" ) {}
};

sc::result MyPump::react(
  PreRunning & preRunning, const EvPreRunningStart & ) const
{
  return preRunning.transit< MyRunning >();
}

*** Main.cpp ***

#include "MyPump.hpp"

#include <iostream>

int main( int, char** )
{
  MyPump p;
  p.initiate();
  std::cout << "External event EvStart posted...\n";
  p.process_event( EvStart() );
  return 0;
}

Now, one person can work on Running.cpp and the other can work on Idle.cpp and
they will never have to disturb each other. IIUC, this should be pretty much
what you want, no?

> This means the 2 points of customization cannot be viewed as separate and
> independent. If MyPump only customized the Idle state but not the Running
state,
> and a third developer with no access to MyPump's source code will have to
> customize MyPump to add inner states for the Running state.
>
> PumpBase
> +--> MyPump
> +--> CustomMyPump

Ok, if you want to have both (i.e. independent development of additional
derived state machines *and* independent customization on a per-state basis)
the reorganization above won't help you. It pretty much trades one for the
other. I.e. now it is no longer possible to develop additional derived FSMs
without changing Idle.cpp and Running.cpp. However, I'm confident that even
that is possible if you introduce an additional layer of indirection. That is,
in your original code instead of:

sc::result MyPump::react(
  PreRunning & preRunning, const EvPreRunningStart & ) const
{
  return preRunning.transit< MyRunning >();
}

you write something like that:

sc::result MyPump::react(
  PreRunning & preRunning, const EvPreRunningStart & ) const
{
  return StateFactory::MakeTransition( MyPump, preRunning );
}

I haven't thought much about the implementation of StateFactory, but it seems
this should allow you to register arbitrary transitions depending on the
current state and the actual derived FSM class, and give you the freedom you
want?

HTH & Regards,

Andreas


Boost-users list run by williamkempf at hotmail.com, kalb at libertysoft.com, bjorn.karlsson at readsoft.com, gregod at cs.rpi.edu, wekempf at cox.net