-
Notifications
You must be signed in to change notification settings - Fork 35
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.
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
Source code: advanced event handling example
Includes, Context and M::
typedef
#include <hfsm/machine_single.hpp>
#include <iostream>
struct Context {};
using M = hfsm::Machine<Context>;
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
{
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;
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
};
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";
}
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;
};
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";
}
};
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";
}
};
};
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);
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
std::cout<< std::endl;
return 0;
};
- Core principles
- Another FSM lib?
- NoUML compliance
- Proactive vs. reactive approach
- Gamedev requirements
- Alternatives
- Context and M:: 'namespace'
- Basic state methods
- Basic transitions
- Roots and regions
- Transitions within hierarchy
- Active chain
- Quering state activation status
- Substitutions, aka State guards on steroids
- State reuse with injections
- Event handling
- Structure and activity report API
- Assisted debugging with custom .natvis
- Logger interface