Tutorial: A more robust door#
So far, we have created the door state machine with two states (Open and Closed), but we don’t like the fact that the current implementation does not check if the door is already open or already closed when someone attempts to open/close it.
We want the implementation to report an error when such cases happen. For that, we will use custom event handlers.
We will also use this opportunity to simplify the diagnostics of the state machine by having the states themselves report when enter/leave them. For that we will use the OnEnter() method. OnEnter/OnLeave are optional, but when they are present, they get called during state transitions. For this example, we will omit the OnLeave method as we just want to report the new state.
1#include <iostream>
2
3#include <fsm/fsm.h>
4
5using asap::fsm::ByDefault;
6using asap::fsm::Continue;
7using asap::fsm::DoNothing;
8using asap::fsm::On;
9using asap::fsm::StateMachine;
10using asap::fsm::Status;
11using asap::fsm::TransitionTo;
12using asap::fsm::Will;
13
14namespace {
15struct OpenEvent {};
16struct CloseEvent {};
17
18struct ClosedState;
19struct OpenState;
20
21struct ClosedState
22 : Will<ByDefault<DoNothing>, On<OpenEvent, TransitionTo<OpenState>>> {
23 using Will::Handle;
24
25 template <typename Event>
26 static auto OnEnter(const Event & /*event*/) -> Status {
27 std::cout << " > door is closed\n";
28 return Continue{};
29 }
30
31 [[nodiscard]] static auto Handle(const CloseEvent & /*event*/) -> DoNothing {
32 std::cerr << "Error: the door is already closed!\n";
33 return DoNothing{};
34 }
35};
36
37struct OpenState
38 : Will<ByDefault<DoNothing>, On<CloseEvent, TransitionTo<ClosedState>>> {
39 using Will::Handle;
40
41 template <typename Event>
42 static auto OnEnter(const Event & /*event*/) -> Status {
43 std::cout << " > door is open\n";
44 return Continue{};
45 }
46
47 [[nodiscard]] [[maybe_unused]] static auto Handle(const OpenEvent & /*event*/)
48 -> DoNothing {
49 std::cerr << "Error: the door is already open!\n";
50 return DoNothing{};
51 }
52};
53} // namespace
54
55using Door = StateMachine<ClosedState, OpenState>;
56
57auto main() -> int {
58 try {
59 Door door{ClosedState{}, OpenState{}};
60 std::cout << "-- Starting\n";
61
62 std::cout << "-- sending close event\n";
63 door.Handle(CloseEvent{});
64
65 std::cout << "-- sending open event\n";
66 door.Handle(OpenEvent{});
67
68 std::cout << "-- sending open event\n";
69 door.Handle(OpenEvent{});
70
71 std::cout << "-- sending close event\n";
72 door.Handle(CloseEvent{});
73 } catch (const std::exception &err) {
74 std::cerr << "An exception was thrown: " << err.what() << std::endl;
75 } catch (...) {
76 std::cerr << "An unknown exception was thrown" << std::endl;
77 }
78}
Notice how we define a template method for OnEnter that gets called on any type of event. We could also write OnEnter handlers for a specific type of event. Such handlers will only get called when the event type matches the handler’s signature (templates SFINAE).
If we run that example, this is what we get:
-- Starting
-- sending close event
Error: the door is already closed!
-- sending open event
> door is open
-- sending open event
Error: the door is already open!
-- sending close event
> door is closed
Now, we want to make our door more secure by adding a lock pad and a code that will be required to unlock it once it is locked.
Head over to the next step to see how we can do that.