Skip to content

Adding Context to tinyfsm #45

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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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