Skip to content

Transitions within Hierarchy

Andrew Gresyk edited this page Jul 5, 2019 · 5 revisions

Transitions in hfsm.dev can be made from any state or region to any other identified by its type.

All external transitions are queued up and processed at the end of M::Root::update() call.

Transitions into States

The simplest form of transition does the following:

  1. Find common parent region for both origin and destination
  2. Deactivate origin branch
  3. Activate destination branch

Example:

using M = hfsm2::Machine;

using FSM = M::PeerRoot<
                struct Origin,
                struct Destination
            >;

struct Origin      : FSM::State {};
struct Destination : FSM::State {};

FSM::Instance fsm;
assert(fsm.isActive<Origin>());

fsm.changeTo<Destination>();
fsm.update();
assert(fsm.isActive<Destination>());

Transitions into Regions

In addition to what transition into a state does, transition into a region also activates its sub-states.

The number and the order of activated sub-states depends on the types of transition and region.

Example:

using M = hfsm2::Machine;

using FSM = M::PeerRoot<
                struct Origin,
                M::Composite<struct Destination,
                    struct SubState1,
                    struct SubState2
                >
            >;

struct Origin      : FSM::State {};
struct Destination : FSM::State {};
struct SubState1   : FSM::State {};
struct SubState2   : FSM::State {};

FSM::Instance fsm;
assert(fsm.isActive<Origin>());

fsm.changeTo<Destination>();
fsm.update();
assert(fsm.isActive<Destination>());
assert(fsm.isActive<SubState1>());

Transition Interface

hfsm.dev allows transitions to be called both from the outside of the FSM, and from within the state of an FSM.

External Transition Interface

All available transitions are available on the FSM::Instance object:

using M = hfsm2::Machine;

using FSM = M::PeerRoot<
                struct Origin,
                struct Destination
            >;

struct Origin       : FSM::State {};
struct Destination : FSM::State {};

FSM::Instance fsm;
assert(fsm.isActive<Origin>());

fsm.changeTo<Destination>(); // external transition
fsm.update();
assert(fsm.isActive<Destination>());

Internal Transition Interface

All available transitions are also available on the M::*Control objects:

using M = hfsm2::Machine;

using FSM = M::PeerRoot<
                struct Origin,
                struct Destination
            >;

struct Origin
    : FSM::State
{
    void update(FullControl& control) {
        control.changeTo<Destination>(); // internal transition
    }
};

struct Destination : FSM::State {};

FSM::Instance fsm;
assert(fsm.isActive<Origin>());

fsm.update();
assert(fsm.isActive<Destination>());

Transition Types

hfsm.dev supports 4 transition types:

  • changeTo() - default transition
  • restart()
  • resume()
  • utilize()
  • randomize()

And 3 region types:

  • Composite
  • Resumable
  • Utilitarian
  • Random

Default Transition

The way the default transition (changeTo<>()) works depends on the region type:

region type meaning of changeTo<>()
Composite restart()
Resumable resume()
Utilitarian utilize()
Random randomize()

The way the non-default transitions (restart(), resume(), utilize() and randomize()) work does not depend on the region type.

'Restart' Transition

Calling restart() into a region of any type, or changeTo<>() into a Composite region - will activate the initial state:

using M = hfsm2::Machine;

using FSM = M::PeerRoot<
                struct State,
                M::Composite<struct Region,
                    struct Initial,
                    struct Secondary
                >
            >;

struct State     : FSM::State {};
struct Region    : FSM::State {};
struct Initial   : FSM::State {};
struct Secondary : FSM::State {};

FSM::Instance fsm;
assert(fsm.isActive<State>());

fsm.changeTo<Region>();
fsm.update();
assert(fsm.isActive<Initial>());

'Resume' Transition

Calling resume() into a region of any type, or changeTo<>() into a Resumable region - will activate the initial sub-state the first time a region is activated.

Resuming a previously activated region will activate the previously active sub-state:

using M = hfsm2::Machine;

using FSM = M::PeerRoot<
                struct State,
                M::Composite<struct Region,
                    struct Initial,
                    struct Secondary
                >
            >;

struct State     : FSM::State {};
struct Region    : FSM::State {};
struct Initial   : FSM::State {};
struct Secondary : FSM::State {};

FSM::Instance fsm;
assert(fsm.isActive<State>());

fsm.changeTo<Secondary>();
fsm.update();
assert(fsm.isActive<Secondary>());

fsm.changeTo<State>();
fsm.update();
assert(fsm.isActive<State>());

fsm.resume<Region>();
fsm.update();
assert(fsm.isActive<Secondary>());

fsm.restart<Region>();
fsm.update();
assert(fsm.isActive<Initial>());

'Utility' Transition - Max Score

Calling utilize() into a region of any type, or changeTo<>() into a Utilitarian region - will:

  • for every sub-state - query its Expected Utility Score by calling utility() method on them, recursively through the entier hierarchy below.
  • activate the sub-state with the highest utility score
using M = hfsm2::Machine;

using FSM = M::PeerRoot<
                struct State,
                M::Composite<struct Region,
                    struct LowRated,
                    struct HighRated
                >
            >;

struct State     : FSM::State {};
struct Region    : FSM::State {};

struct LowRated
    : FSM::State
{
    float utility(const Control&) { return 0.5f; }
};

struct HighRated
    : FSM::State
{
    float utility(const Control&) { return 2.0f; }
};

FSM::Instance fsm;
assert(fsm.isActive<State>());

fsm.utilize<Region>();
fsm.update();
assert(fsm.isActive<HighRated>());

'Utility' Transition - Ranked Weighted Random

Calling randomize() into a region of any type, or changeTo<>() into a Random region - will:

  • for every immediate sub-state - query its Rank by calling rank() method on them, no recursion through the hierarchy.
  • for all the sub-states with the top rank, recursively query its Expected Utility Score by calling utility() method on them
  • using utility scores as relative weights, randomly select and activate a sub-state
using M = hfsm2::Machine;

using FSM = M::PeerRoot<
				struct State,
				M::Composite<struct Region,
					struct FilteredOut,
					struct LowRated,
					struct HighRated
				>
			>;

struct State     : FSM::State {};
struct Region    : FSM::State {};

struct FilteredOut
	: FSM::State
{
	int8_t rank(const Control&) { return 0; } // filter out using low rank

	float utility(const Control&) { return 0.5f; }
};

struct LowRated
	: FSM::State
{
	int8_t rank(const Control&) { return 1; }

	float utility(const Control&) { return 0.5f; }
};

struct HighRated
	: FSM::State
{
	int8_t rank(const Control&) { return 1; }

	float utility(const Control&) { return 2.0f; }
};

FSM::Instance fsm;
assert(fsm.isActive<State>());

fsm.randomize<Region>();
fsm.update();
assert(fsm.isActive<HighRated>()); // note, it could be LowRated if the PRNG is seeded differently

FSM Initialization

Upon initialization, hfsm.dev performs default transition into the root region.

You can’t perform that action at this time.