0

Given a state machine that implements a certain protocol. I need multiple instances of this protocol running, so I create multiple instances of this state machine. Each instance is associated with a certain key. The state machine itself doesn't need to know the value of this key in order to do its work (state evaluations and transitions), therefore it is not part of the state machine data structure. In pseudo code that would something like this:

map<Key, StateMachine> state_machines;
Key k = ...;
StateMachine s = ...;
state_machines.add(k, s); // repeat this for multiple key/state_machine combo's

// ...

// event y has occurred for key x
s = map.find(x);
s.event_y_has_occurred = true;
s.evaluate(); // state machine check whether it should transition to a new state based on the current state and the event

Now suppose one of the side effects is to log the transition to a specific state in a file. To do this side effect, it needs to know two things:

  • The key value (make clear which state machine made the transition)
  • The file to write to.

Both of these pieces of data aren't part of the state machine, so the state machine cannot execute the side effect.

Another example: depending on the state transitions, certain data packets need to be sent to a remote machine. To do this, the state machine somehow needs access to a socket and all the data that needs to be in that packet.

How can I make all this data available to the state machine if I don't want to add it to the state machine data structure? Or should the side effect perhaps not be part of the state machine?

3
  • Just for the record. Are you implementing the OO Pattern? Do you have 1 class per state? Is a different STM pattern we should know?
    – Laiv
    Commented Jun 10, 2023 at 20:11
  • 1
    I'm working in C, but I'm trying to apply some OO principles where possible. The state is just an enum variable (states are enum values).
    – PieterV
    Commented Jun 12, 2023 at 9:36
  • Long time not working with C. I wonder if you can achieve polymorphism with structs, so that, every transition results in a new "instance" of a struct. Each "instance" represents the current state (instead of the enum). You can associate concrete structs with enums to keep backward compatibility. The point is, each new "instance" can safely hold the foreign elements you don't want w/o compromising other states. For example, concrete structs could provide functions/constructors with In/out arguments. With each transition, you should release those in/out dependencies (e.g. closing sockets)
    – Laiv
    Commented Jun 12, 2023 at 9:57

3 Answers 3

3

Use the state machine as a "controller", in the MVC sense: have it emit commands (call methods or send messages) to some other piece of code which does have the right information. The commands would be named according to the state machine's view of the transition.

1
  • I would have described it as building the state machine engine with hooks that execute "out of state machine code" that are per state machine instance. But what are suggesting is the correct way to go.
    – Peter M
    Commented Jun 12, 2023 at 17:54
1

It is entirely normal that a state machine needs to be associated with some auxiliary data that isn't part of the "pure" state transition network, unless you're not interested in any transition side effects at all.

How you store the pair of (state_machine, data) is a matter of taste. One possibility is to keep a reference to the data within the state machine, to be used by the state transition functions. Another option is to keep the state machine as part of the data object whose states need to be modeled, and have the state transition function return a function or method selector that should be executed on that object.

1
  • This is pretty much where I am now with my code. The state machine has access to pretty much all the data in the application because it has so much side effects it needs to take care of. There are now al lot of dependencies, making things harder to maintain, so I'm trying to avoid this somewhat.
    – PieterV
    Commented Jun 8, 2023 at 9:03
0

One way:

map.get(key).sideEffect(key);

But that only works from code that knows both the key and when to fire the sideEffect.

Alternatively:

Using sideEffect.now() works because something else packed knowledge of key, state machine, and the effect you want into sideEffect when they built it. That happened somewhere else before they handed it to you here. The only thing this calling code adds is the knowledge that the time to do it is now.

The point I'm making here is there isn't exactly one obvious option. It depends on what details you want mixed together vs. what details you want pushed away.

2
  • I'm not sure I fully understand this answer. Do you mean that the side effects could/should be done outside of the state machine? So let the state machine handle its event, then check whether it moved to a new state and then execute the necessary side effects?
    – PieterV
    Commented Jun 8, 2023 at 9:08
  • @PieterV the behavior of the side effect depends on the state of the state machine right? That's fine. Just provide access to it. But since knowledge of when to fire the event comes from outside there's no reason to make that interface complicated is there? But as for where the works actually done, none of this dictates that. This is about separating things so you don't have to manage them all in the same place. Commented Jun 8, 2023 at 13:34

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.