Skip to content

Adding Context to tinyfsm #45

@dfleury2

Description

@dfleury2

Hi,
I am experimenting with tinysfsm. Coding a simple traffic light with on/off and three colors is quite easy.

Image

But I wonder, how I can avoid using a global variable to use as a context for sharing state with the outside for more complex FSM.
It this time, I have added a template parameter with an inline static to store this context, but I am not satisfied, even if ti's working well.
Any idea to do better than that ?

Regards,

PS: Code added to have the idea

#include "tinyfsm_ctx.hpp"

#include <fmt/color.h>
#include <fmt/format.h>

#include <chrono>
#include <iostream>
#include <thread>

using Duration = std::chrono::steady_clock::duration;

using namespace std::chrono_literals;

namespace {
inline constexpr Duration DURATION_ORANGE = 2s;
inline constexpr Duration DURATION_RED = 5s;
inline constexpr Duration DURATION_GREEN = 8s;
}   // namespace

struct TriColorLight
{
    Duration remaining = 0s;
    bool green = false;
    bool orange = false;
    bool red = false;

    void off() { green = orange = red = false; }

    void display(const bool is_red) const
    {
        using namespace std::chrono;

        std::cout << "\r\33[2K" << std::flush;
        if (!green && !orange && !red) {
            std::cout << "OFF";
        }
        else {
            std::cout << fmt::format(fg(green ? fmt::color::green : fmt::color::gray), "{:>10}", "GREEN");
            std::cout << fmt::format(fg(orange ? fmt::color::orange : fmt::color::gray), "{:>10}", "ORANGE");
            std::cout << fmt::format(fg(red ? fmt::color::red : fmt::color::gray), "{:>10}", "RED");
            std::cout << fmt::format("{:>18.3f}", duration_cast<duration<double>>(remaining).count());
            std::cout << fmt::format(fg(fmt::color::red), "{:>10}", is_red ? "***" : "");
        }
        std::cout << std::flush;
    }
};

// ------------------------
// Events
struct switch_on_off : tinyfsm::Event
{
};
struct tick : tinyfsm::Event
{
    Duration elapsed_time;
};

// ------------------------
// States
class Stopped;
class Started;
// Substates of Started
class Started_Orange;
class Started_Red;
class Started_Green;

// ------------------------
// FSM
class TricolorLightFSM : public tinyfsm::Fsm<TricolorLightFSM, TriColorLight>
{
  public:
    virtual void react(const tick&) {}
    virtual void react(const switch_on_off&) {}

    virtual void entry() {}
    virtual void exit() {}

    static void reset();
};

// ------------------------
// States
class Stopped final : public TricolorLightFSM
{
    void entry() override { context().off(); }

    void react(const switch_on_off&) override { transit<Started>(); }
};

class Started : public TricolorLightFSM
{
    void entry() override { dispatch(tick{}); }

    void react(const switch_on_off&) override { transit<Stopped>(); }
    void react(const tick&) override { transit<Started_Orange>(); }
};

class Started_Orange final : public Started
{
    void entry() override
    {
        context().remaining = DURATION_ORANGE;
        context().orange = true;
    }
    void exit() override { context().orange = false; }

    void react(const tick& t) override
    {
        context().remaining -= t.elapsed_time;
        if (context().remaining <= 0s) {
            transit<Started_Red>();
        }
    }
};

class Started_Red final : public Started
{
    void entry() override
    {
        context().remaining = DURATION_RED;
        context().red = true;
    }
    void exit() override { context().red = false; }

    void react(const tick& t) override
    {
        context().remaining -= t.elapsed_time;
        if (context().remaining <= 0s) {
            transit<Started_Green>();
        }
    }
};

class Started_Green final : public Started
{
    void entry() override
    {
        context().remaining = DURATION_GREEN;
        context().green = true;
    }

    void exit() override { context().green = false; }

    void react(const tick& t) override
    {
        context().remaining -= t.elapsed_time;
        if (context().remaining <= 0s) {
            transit<Started_Orange>();
        }
    }
};

FSM_INITIAL_STATE_CTX(TricolorLightFSM, TriColorLight, Stopped)

// ------------------------------
int main()
{
    TriColorLight tricolor_light;

    TricolorLightFSM::set_context(tricolor_light);
    TricolorLightFSM::start();

    auto start_time = std::chrono::steady_clock::now();

    TricolorLightFSM::dispatch(switch_on_off{});

    for (int i = 0; i < 1000000; ++i) {
        auto current_time = std::chrono::steady_clock::now();

        TricolorLightFSM::dispatch(tick{.elapsed_time = current_time - start_time});

        tricolor_light.display(TricolorLightFSM::is_in_state<Started_Red>());

        start_time = current_time;
        std::this_thread::sleep_for(25ms);
    }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions