I frequently use the concept of Active Objects (https://www.state-machine.com/active-object) combined with state machines when designing code. The key idea behind these is that only "events" are fed into (and out of) an object and state machines "react to" these events. This leads to a very different type of design from a sequential program. Whereas a sequential program is designed in a "do this, then this returning some results, then do something with those results, ..." an active object/event-based design is "when event A happens do this and emit event B."
This works great for the vast majority of problems, but I occasionally get stuck when I need to "retrieve" some value from an active object/state machine. Let me illustrate with a simple example.
Suppose I am designing a toaster state machine.
Incoming Events:
- Set Temperature
- Insert Bread
- Remove Bread
- Start
- Stop
Outgoing Events:
- Started
- Stopped
- Done
Essentially, the toaster (and any active state machine) is a "black box" which can be interacted with by "dispatching" events to it and responding to events "emitted" by it.
Now, imagine Person A interacts with the toaster by setting the temperature and starting it. This person leaves and Person B steps up to the toaster. Since Person B did not perform the original interactions with the toaster, they have no knowledge of what the toaster is doing. Thus, this person wants some way to "query" the internal state of the toaster to make some decision. For example, what if Person B is very inquisitive and would like to know the internal temperature of the toaster right now.
This third type of interaction, "query", is the very essence of sequential programming (via return values) but seems foreign to event-based design. To me, since a state machine should be a black box that is only recognizable by its interactions to the outside, "getting" a value "returned" from a state machine is essentially breaking into this black box to look at the internal state. That being said, I still find that I need to do it (perhaps this is a design problem).
One solution I have used is the idea of "callbacks." This transforms the synchronous query into an asynchronous one. You are essentially telling the state machine, "I would like some information, let me know via this callback when you have it."
stateMachine.getSomething([](int result){
//Do something with result
});
While this works, it can result in very complex chains of callbacks and requires a fair amount of dynamic memory allocation making it unfriendly for embedded systems.
Another solution would be the idea of "futures" and "promises." (I apologize if I get the terminology slightly wrong as I have never actually used this pattern.) The "getter" method would "return" a "promise" immediately on which the caller will block waiting for a result. Meanwhile, the asynchronous active state machine will perform the processing necessary to get the result. When finished, it will place the value in the future/promise object allowing the caller to continue. In this way, the caller is written in a traditional, sequential, call-and-return fashion, while the state machine remains unchanged.
auto future = statemachine.getSomething();
auto value = future.get(); //Will block here
The main problem here is the requirement that the state machine must be running within a different thread from the caller otherwise a deadlock will result. In other words, if a state machine were to block on a future from another state machine, these state machines would have to be on different threads. Again, a thread for every object is very resource intensive and not so suitable for embedded systems.
Are there other solutions? I admit that I may be hybridizing sequential and event-based programming in my head, but I fail to think of another solution to this type of problem. I would appreciate resources to any helpful reading on this topic.