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

Logger Interface

Andrew Gresyk edited this page Jul 2, 2018 · 5 revisions

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

Example code walkthrough

Source code: debug logger interface example

// 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

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";
    }
};

FSM States

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

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);

FSM construction log

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 log

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);

State transition log

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.

FSM destruction log

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;
}

Full log output

--- 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! ---

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