jeudi 27 janvier 2022

Where to place an actor within the state pattern?

I have problems finding the right place for an actor and a timer used in a state machine.

I found some inspiration from this site about the state pattern: State Design Pattern in Modern C++ and created a small example:

Simple door state machine

There might be more transitions possible but I kept it short and simple.

class Door 
{  
    void open() {}
    void close() {}
};

Events:

class EventOpenDoor
{
public:
    OpenDoor(Door* door) : m_pDoor(door) {}
    Door* m_pDoor;
};

class EventOpenDoorTemporary
{
public:
    EventOpenDoorTemporary(Door* door) : m_pDoor(door) {}
    Door* m_pDoor;
};

class EventOpenDoorTimeout
{
public:
    EventOpenDoorTimeout(Door* door) : m_pDoor(door) {}
    Door* m_pDoor;
};

class EventCloseDoor
{
public:
    EventCloseDoor(Door* door) : m_pDoor(door) {}
    Door* m_pDoor;
};

using Event = std::variant<EventOpenDoor,
                           EventOpenDoorTemporary,
                           EventOpenDoorTimeout,
                           EventCloseDoor>;

States:

class StateClosed {};

class StateOpen {};

class StateTemporaryOpen {};

using State = std::variant<StateClosed,
                           StateOpen,
                           StateTemporaryOpen>;

Transitions (not complete):

struct Transitions {

    std::optional<State> operator()(StateClosed &s, const EventOpenDoor &e) {
        if (e.m_pDoor)
        {
            e.m_pDoor->open();
        }
        auto newState = StateOpen{};
        return newState;
    }

    std::optional<State> operator()(StateClosed &s, const EventOpenDoorTemporary &e) {
        if (e.m_pDoor)
        {
            e.m_pDoor->open();
            **// start timer here?**
        }
        auto newState = StateOpen{};
        return newState;
    }

    std::optional<State> operator()(StateTemporaryOpen &s, const EventOpenDoorTimeout &e) {
        if (e.m_pDoor)
        {
            e.m_pDoor->close();
        }
        auto newState = StateOpen{};
        return newState;
    }

    std::optional<State> operator()(StateTemporaryOpen &s, const EventOpenDoor &e) {
        if (e.m_pDoor)
        {
            e.m_pDoor->open();
            **// stop active timer here?**
        }
        auto newState = StateOpen{};
        return newState;
    }

    /* --- default ---------------- */
    template <typename State_t, typename Event_t>
    std::optional<State> operator()(State_t &s, const Event_t &e) const {
        // "Unknown transition!";
        return std::nullopt;
    }
};

Door controller:

template <typename StateVariant, typename EventVariant, typename Transitions>
class DoorController {
    StateVariant m_curr_state;
    void dispatch(const EventVariant &Event)
    {
        std::optional<StateVariant> new_state = visit(Transitions{this}, m_curr_state, Event);
        if (new_state)
        {
            m_curr_state = *move(new_state);
        }
    }

    public:
    template <typename... Events>
    void handle(Events... e) 
{ (dispatch(e), ...); }

    void setState(StateVariant s)
    {
        m_curr_state = s;
    }
};

The events can be triggered by a client which holds an instance to the "DoorController"

door_controller->handle(EventOpenDoor{door*});

In the events I pass a pointer to the door itself so it's available in the transitions. The door is operated within the transitons only.

I have problems now with modelling the 20s timeout/timer. Where to have such a timer, which triggers the transition to close the door? Having a timer within the door instance means, I have a circular dependency, because in case of a timeout it needs to call "handle()" of the "door_controller".

I can break the circular dependency with a forward declarations. But is there a better solution?

Maybe I have modelled it not well. I'm open to improving suggetions. Thanks a lot!

Aucun commentaire:

Enregistrer un commentaire