Skip to content
This repository has been archived by the owner on Sep 26, 2018. It is now read-only.

Event Handling

Andrew Gresyk edited this page Jun 30, 2018 · 15 revisions

Event handling

Even though HFSM does not rely on events for its basic operation (state transitions are requested directly), they can be used to delegate the reaction to the changes in the environment to the currently active states.

Use cases

In the context of video games, events can be used for:

  • Damage handling, eventually transitioning into 'dead' state when the damage taken exceeds 'health'.
  • Reactions to physics collision
  • AI reactions to the changed tactical situation

Example code walkthrough

Source code: advanced event handling example

#include <hfsm/machine_single.hpp>
#include <iostream>

struct Context {};

using M = hfsm::Machine<Context>;

Event declarations

An event can be any C++ type:

struct PrimaryEvent {};
struct SecondaryEvent { int payload; };

using TransitionEvent = char;

Reactive is a top-level state, showcasing event reaction subsequently causing state transition:

// forward declared for Reactive::transition()
struct Target;

struct Reactive
    : M::Base
{

Event handling API

react() method, just line transition() receives Control& parameter, that provides an interface for a state to initiate a transition:

    // handle a single event type - TransitionEvent
    void react(const TransitionEvent&, Control& control, Context&)  {
        std::cout << "  Reactive: reacting to TransitionEvent\n";

        control.changeTo<Target>();
    }

    // and ignore the other event types
    using M::Base::react;

Opt-in event handling

Since react() methods (just like all other state methods) are completely optional, it's ok for the NonHandler state to not provide any:

    struct NonHandler
        : M::Base
    {
        // events are totally opt-in
    };

Catching specific events

ConcreteHandler illustrates the first option for handling events - provide one ore more react() methods accepting concrete event types as their first argument:

    struct ConcreteHandler
        : M::Base
    {
        // handle two event types - PrimaryEvent
        void react(const PrimaryEvent&, Control&, Context&)  {
            std::cout << "  ConcreteHandler: reacting to PrimaryEvent\n";
        }

        // and SecondaryEvent
        void react(const SecondaryEvent&, Control&, Context&)  {
            std::cout << "  ConcreteHandler: reacting to SecondaryEvent\n";
        }

Complete event coverage

Whenever a state has at least one react() method, it has to cover all possible event types that ever get passed into the state machine. This can be achieved either by:

  • either declaring empty react() methods for the remaining event types
  • or bringing M::Base::react() method (which will do this for you) into the scope of the state:
        // and ignore the other event types
        using M::Base::react;
    };

Catching all events

TemplateHandler illustrates the second option for handling events - provide a templated react() method, which will 'catch' all possible events:

    struct TemplateHandler
        : M::Base
    {
        // handle all possible event types
        template <typename TEvent>
        void react(const TEvent&, Control&, Context&)  {
            std::cout << "  TemplateHandler: reacting to TEvent\n";
        }
    };

Catching event groups

As EnableIfHandler does, a state can also define multiple templated react() methods for different groups of types using std::enable_if<>. Like before, a state still has to cover all possible event types:

    struct EnableIfHandler
        : M::Base
    {
        // use std::enable_if to build more complex conditional event handling
        template <typename TEvent>
        typename std::enable_if<std::is_class<TEvent>::value>::type
        react(const TEvent&, Control&, Context&)  {
            std::cout << "  EnableIfHandler: reacting to a <class event>\n";
        }

        // but remember to cover all the remaining cases
        template <typename TEvent>
        typename std::enable_if<!std::is_class<TEvent>::value>::type
        react(const TEvent&, Control&, Context&)  {
            std::cout << "  EnableIfHandler: reacting to a <non-class event>\n";
        }
    };
};

Remaining boilerplate

Target is a trivial transition destination state:

struct Target
    : M::Base
{
    void enter(Context&) {
        std::cout << "    changed to Target\n";
    }
};

int main() {
    Context context;

    M::PeerRoot<
        M::Orthogonal<Reactive,
            Reactive::NonHandler,
            Reactive::ConcreteHandler,
            Reactive::TemplateHandler,
            Reactive::EnableIfHandler
        >,
        Target
    > machine(context);

Passing events to state machine

hfsm::Machine<TContext>::Root::react(const TEvent&) method is used to pass an event to all active states in the state machine:

    std::cout << "sending PrimaryEvent:\n";
    machine.react(PrimaryEvent{});

Output:

sending PrimaryEvent:
  ConcreteHandler: reacting to PrimaryEvent
  TemplateHandler: reacting to TEvent
  EnableIfHandler: reacting to a <class event>
    std::cout << "\nsending SecondaryEvent:\n";
    machine.react(SecondaryEvent{});

Output:

sending SecondaryEvent:
  ConcreteHandler: reacting to SecondaryEvent
  TemplateHandler: reacting to TEvent
  EnableIfHandler: reacting to a <class event>
    std::cout << "\nsending TransitionEvent (aka 'char'):\n";
    machine.react(TransitionEvent{});

Output:

sending TransitionEvent:
  Reactive: reacting to TransitionEvent (aka 'char')
  TemplateHandler: reacting to TEvent
  EnableIfHandler: reacting to a <non-class event>
    changed to Target

The end

    std::cout<< std::endl;
    return 0;
};

Documentation

Design

  • Core principles
  • Another FSM lib?
  • NoUML compliance
  • Proactive vs. reactive approach
  • Gamedev requirements
  • Alternatives

Basic features

  • Context and M:: 'namespace'
  • Basic state methods
  • Basic transitions
  • Roots and regions
  • Transitions within hierarchy
  • Active chain
  • Quering state activation status

Advanced features

  • Substitutions, aka State guards on steroids
  • State reuse with injections
  • Event handling

Debugging

  • Structure and activity report API
  • Assisted debugging with custom .natvis
  • Logger interface
Clone this wiki locally