How to implement dynamic state machines

Hello!

I am writing a peer-to-peer application and I want to implement the different states the peers can be in. I have seen the various state machine patterns, but my understanding is that they can only be used when the transitions from one state to the other are known statically.

In my case of course, message from a peer come at runtime, so I will probably need something else. I came up with a solution on my own but it is quite convoluted, and I think sub-optimal. So before I spend more time trying to optimise/refactor it I wanted to ask if there are known patterns which fulfill the following (or a "good enough" subject):

  • If I write code that makes a wrong state transition, this will be caught at compile time.
  • If there is a function like send_disconnect_msg I will only be able to call it in states where they are allowed to send a disconnection
  • Be as efficient as possible. I guess this is not a so well defined requirement. I mean that my implementation should be as close to an implementation by a very good programmer that would never make the above two errors I am trying to catch.

If you are interested, for now I have something like:

enum Event {
    Event1,
    Event2,
}
struct CommonData;

trait CanDisconnect {};

#[enum_dispatch::enum_dispatch]
trait State {
    fn handle_event(self, event: Event) -> CurrentState;
};

struct StateA;
struct StateB;

impl CanDisconnect for StateB {}

struct StateMachine<T> {
    state: T,
    common_data: CommonData,
}

impl StateMachine<StateA> {
    pub fn new() -> Self {
        // ...
    }
}

impl StateMachine<StateB> {
    pub fn new() -> Self {
        // ...
    }
}

impl<T: CanDisconnect> StateMachine<T> {
    fn send_disconnect(&mut self) {
    }
}

impl From<StateMachine<StateA>> for StateMachine<StateB> {
    // ...
}

impl State for StateMachine<StateA> {
    // ...
}

impl State for StateMachine<StateB> {
    // ...
}

#[enum_dispatch::enum_dispatch(State)]
enum CurrentState {
    StateA(StateMachine<StateA>),
    StateB(StateMachine<StateB>),
}

The idea is that if you want to go from one state to the other you will have to do something like StateMachine::<StateB>::from(self). So a wrong transition will be caught compile time.

Also, if you want to to make a call to send_disconnect, you will have to be in a state that is marked with the CanDisconnect state, otherwise that method will not be available.

The problem is that i since I use enum_dispatch i need to make a lot of .into() calls inside the event_handler. And since this is something that might be called quite often, I imagine it might impose a big overhead. Also the code seems to be a bit convoluted.

Another problem, but probably orthogonal, is that since I consume the current state I can't just have a CurrentState be a member of some other struct, since if i had a &mut reference to that struct, I couldn't partially move the CurrentState out. Also I think that the common_data field is probably moved every time I make a handle_event call, but I am not sure how move semantics work exactly.

You can use derive macro to add custom private functions to states, for example only state marked with #[derive(CanDisconnect)] has function send_disconnect.

In fact you can use derive macro to build the entire state tree, both for state tree hierarchy and their implementations.

P.S. I think you implementation of state machine is a little odd to me, if you are interested, I highly suggest to investigate this, it's the state machine used in Android, and has a detailed doc.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.