-
Notifications
You must be signed in to change notification settings - Fork 35
Logger Interface
It is often useful to see the history of state transitions and state method calls. One option for that would be by putting log statements in every method, which can be tedious and error prone.
HFSM also provides the interface for attachable logger, for a more convenient solution, removing the need to clutter user state code with the custom logging code:
// enable logger functionality
#define HFSM_ENABLE_STRUCTURE_REPORT
#include <hfsm/machine_single.hpp>
namespace hfsm {
struct LoggerInterface {
enum class Method {
substitute,
enter,
update,
transition,
react,
leave,
};
virtual void record(const std::type_index& state,
const char* const stateName,
const Method method,
const char* const methodName) = 0;
};
Logger can be attached to the FSM on construction, which is required to catch method calls during initial state activations:
Root<>::Root(Context&, LoggerInterface* const = nullptr);
After the construction of the FSM, logger can be detached, or re-attached again:
Root<>::attachLogger(LoggerInterface* const);
} // namespace hfsm
Source code: debug logger interface example
Includes, Context and M::
typedef
// enable logger functionality
#define HFSM_ENABLE_LOG_INTERFACE
#include <hfsm/machine_single.hpp>
#include <iostream>
// data shared between FSM states and outside code
struct Context {};
// convenience typedef
using M = hfsm::Machine<Context>;
hfsm::LoggerInterface
contains a single method record()
, which is called by the framework on every invocation of every state machine state method, provided by the user.
record()
receives 4 arguments, to help identify state type and method id, and their respective names:
struct Logger
: hfsm::LoggerInterface
{
void record(const std::type_index& /*state*/,
const char* const stateName,
const Method /*method*/,
const char* const methodName) override
{
std::cout << stateName << "::" << methodName << "()\n";
}
};
Each of the three states in this example define all six methods available for a state to override.
However, not all of the methods will be called in this example due to the method calling sequence during state transition.
E.g. substitute()
by its nature, is only ever called on transition target states. As such, since neither Top
, nor Top::From
states are ever transitioned into, and neither Top::substitute()
, nor Top::From::substitute()
will ever be invoked by the framework in this sample.
// top-level state in the hierarchy
struct Top
: M::Base // necessary boilerplate!
{
// all state methods:
void substitute(Control&, Context&) {} // not going to be called in this example
void enter(Context&) {}
void update(Context&) {}
void transition(Control&, Context&) {}
template <typename TEvent>
void react(const TEvent&, Control&, Context&) {}
void leave(Context&) {}
The only non-trivial method in this code sample is Top::From::react()
, which unconditionally initiates a transition to Top::To
state:
// forward declared for Red::transition()
struct To;
// initial state
struct From
: M::Base
{
// all state methods:
void substitute(Control&, Context&) {} // not going to be called in this example
void enter(Context&) {}
void update(Context&) {}
void transition(Control&, Context&) {}
template <typename TEvent>
void react(const TEvent&, Control& control, Context&) { control.changeTo<To>(); }
void leave(Context&) {}
};
Top::To
is the target state for the single transition from Top::From
:
// transition target state
struct To
: M::Base
{
// all state methods:
void substitute(Control&, Context&) {}
void enter(Context&) {}
void update(Context&) {}
void transition(Control&, Context&) {}
template <typename TEvent>
void react(const TEvent&, Control&, Context&) {} // not going to be called in this example
void leave(Context&) {}
};
};
FSM instance construction prelude:
int main() {
using FSM = M::Root<Top,
Top::From,
Top::To
>;
// shared data storage instance
Context context;
In this sample, Logger instance is created on the stack..
{
// logger
Logger logger;
std::cout << "--- ctor: ---\n\n";
.. And passed into the FSM root constructor:
// state machine instance - all initial states are activated
FSM machine(context, &logger);
Logger output illustrates implicit initial state activation during FSM instance construction:
Top::enter() Top::From::enter()
std::cout << "\n-- update: --\n\n";
// first update
machine.update();
Update sequence results in the following log:
Top::update() Top::transition() Top::From::update() Top::From::transition()
Here, number 1
is used as an event, as in HFSM anything can be an event, and Top::From::react<TEvent>()
covers all types since it's a unconstrained template method.
std::cout << "\n-- react: ---\n\n";
machine.react(1);
Event reaction sequence is a bit more interesting, as it includes transition between Top::From
and Top::To
:
Top::react() Top::From::react() Top::To::substitute() Top::From::leave() Top::To::enter()
std::cout << "\n-- detach: --\n\n";
// detach logger and update again
machine.attachLogger(nullptr);
machine.update();
Naturally, update()
will not log out anything, after the logger has been detached.
Next, logger will be re-attached again, to record the method call sequence on FSM destruction at the end of the scope:
std::cout << "\n--- dtor: ---\n\n";
// re-attach logger for destruction log
machine.attachLogger(&logger);
// state machine instance gets destroyed
}
The final output block illustrates FSM destruction sequence:
Top::To::leave() Top::leave()
std::cout << "\n--- done! ---\n\n";
return 0;
}
--- ctor: --- Top::enter() Top::From::enter() -- update: -- Top::update() Top::transition() Top::From::update() Top::From::transition() -- react: --- Top::react() Top::From::react() Top::To::substitute() Top::From::leave() Top::To::enter() -- detach: -- --- dtor: --- Top::To::leave() Top::leave() --- done! ---
- 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