Line data Source code
1 : /*
2 : * Copyright (c) 2021 Project CHIP Authors
3 : * Copyright (c) 2021 SmartThings
4 : * All rights reserved.
5 : *
6 : * Licensed under the Apache License, Version 2.0 (the "License");
7 : * you may not use this file except in compliance with the License.
8 : * You may obtain a copy of the License at
9 : *
10 : * http://www.apache.org/licenses/LICENSE-2.0
11 : *
12 : * Unless required by applicable law or agreed to in writing, software
13 : * distributed under the License is distributed on an "AS IS" BASIS,
14 : * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 : * See the License for the specific language governing permissions and
16 : * limitations under the License.
17 : */
18 :
19 : #pragma once
20 :
21 : #include <lib/core/Optional.h>
22 : #include <lib/support/Variant.h>
23 :
24 : namespace chip {
25 : namespace StateMachine {
26 :
27 : /**
28 : * An extension of the Optional class that removes the explicit requirement
29 : * for construction from a T value as a convenience to allow auto construction
30 : * of Optional<T>.
31 : */
32 : template <class T>
33 : class Optional : public chip::Optional<T>
34 : {
35 : public:
36 10 : Optional(const T & value) : chip::Optional<T>(value) {}
37 5 : Optional() : chip::Optional<T>() {}
38 : };
39 :
40 : /**
41 : * An extension of the Variant class offering pattern matching of State types
42 : * to dynamically dispatch execution of the required State interface methods:
43 : * Enter, Exit, GetName, LogTtransition.
44 : */
45 : template <typename... Ts>
46 : struct VariantState : Variant<Ts...>
47 : {
48 :
49 : private:
50 : template <typename T>
51 30 : void Enter(bool & ever)
52 : {
53 30 : if (!ever && chip::Variant<Ts...>::template Is<T>())
54 : {
55 10 : ever = true;
56 10 : chip::Variant<Ts...>::template Get<T>().Enter();
57 : }
58 30 : }
59 :
60 : template <typename T>
61 30 : void Exit(bool & ever)
62 : {
63 30 : if (!ever && chip::Variant<Ts...>::template Is<T>())
64 : {
65 10 : ever = true;
66 10 : chip::Variant<Ts...>::template Get<T>().Exit();
67 : }
68 30 : }
69 :
70 : template <typename T>
71 30 : void GetName(const char ** name)
72 : {
73 30 : if (name && !*name && chip::Variant<Ts...>::template Is<T>())
74 : {
75 10 : *name = chip::Variant<Ts...>::template Get<T>().GetName();
76 : }
77 30 : }
78 :
79 : template <typename T>
80 30 : void LogTransition(bool & ever, const char * previous)
81 : {
82 30 : if (!ever && chip::Variant<Ts...>::template Is<T>())
83 : {
84 10 : ever = true;
85 10 : chip::Variant<Ts...>::template Get<T>().LogTransition(previous);
86 : }
87 30 : }
88 :
89 : public:
90 : template <typename T, typename... Args>
91 16 : static VariantState<Ts...> Create(Args &&... args)
92 : {
93 16 : VariantState<Ts...> instance;
94 16 : instance.template Set<T>(std::forward<Args>(args)...);
95 16 : return instance;
96 : }
97 :
98 10 : void Enter()
99 : {
100 10 : bool ever = false;
101 10 : [](...) {}((this->template Enter<Ts>(ever), 0)...);
102 10 : }
103 :
104 10 : void Exit()
105 : {
106 10 : bool ever = false;
107 10 : [](...) {}((this->template Exit<Ts>(ever), 0)...);
108 10 : }
109 :
110 10 : const char * GetName()
111 : {
112 10 : const char * name = nullptr;
113 10 : [](...) {}((this->template GetName<Ts>(&name), 0)...);
114 10 : return name;
115 : }
116 :
117 10 : void LogTransition(const char * previous)
118 : {
119 10 : bool ever = false;
120 10 : [](...) {}((this->template LogTransition<Ts>(ever, previous), 0)...);
121 10 : }
122 : };
123 :
124 : /**
125 : * The interface for dispatching events into the State Machine.
126 : * @tparam TEvent a variant holding the Events for the State Machine.
127 : */
128 : template <typename TEvent>
129 : class Context
130 : {
131 : public:
132 6 : virtual ~Context() = default;
133 :
134 : /**
135 : * Dispatch an event to the current state.
136 : * @param evt a variant holding an Event for the State Machine.
137 : */
138 : virtual void Dispatch(const TEvent & evt) = 0;
139 : };
140 :
141 : /**
142 : * This is a functional approach to the State Machine design pattern. The design is
143 : * borrowed from http://www.vishalchovatiya.com/state-design-pattern-in-modern-cpp
144 : * and extended for this application.
145 : *
146 : * At a high-level, the purpose of a State Machine is to switch between States. Each
147 : * State handles Events. The handling of Events may lead to Transitions. The purpose
148 : * of this design pattern is to decouple States, Events, and Transitions. For instance,
149 : * it is desirable to remove knowledge of next/previous States from each individual
150 : * State. This allows adding/removing States with minimal code change and leads to a
151 : * simpler implementation.
152 : *
153 : * This State Machine design emulates C++17 features to achieve the functional approach.
154 : * Instead of using an enum or inheritance for the Events, the Events are defined as
155 : * structs and placed in a variant. Likewise, the States are all defined as structs and
156 : * placed in a variant. With the Events and States in two different variants, the
157 : * Transitions table uses the type introspction feature of the variant object to match a
158 : * given state and event to an optional new-state return.
159 : *
160 : * For event dispatch, the State Machine implements the Context interface. The Context
161 : * interface is passed to States to allow Dispatch() of events when needed.
162 : *
163 : * The State held in the TState must provide four methods to support calls from
164 : * the State Machine:
165 : * @code
166 : * struct State {
167 : * void Enter() { }
168 : * void Exit() { }
169 : * void LogTransition(const char *) { }
170 : * const char *GetName() { return ""; }
171 : * }
172 : * @endcode
173 : *
174 : * The TTransitions table type is implemented with an overloaded callable operator method
175 : * to match the combinations of State / Event variants that may produce a new-state return.
176 : * This allows the Transition table to define how each State responds to Events. Below is
177 : * an example of a Transitions table implemented as a struct:
178 : *
179 : * @code
180 : * struct Transitions {
181 : * using State = chip::StateMachine::VariantState<State1, State2>;
182 : * chip::StateMachine::Optional<State> operator()(State &state, Event &event)
183 : * {
184 : * if (state.Is<State1>() && event.Is<Event2>())
185 : * {
186 : * return State::Create<State2>();
187 : * }
188 : * else if (state.Is<State2>() && event.Is<Event1>())
189 : * {
190 : * return State::Create<State1>();
191 : * }
192 : * else
193 : * {
194 : * return {}
195 : * }
196 : * }
197 : * }
198 : * @endcode
199 : *
200 : * The rules for calling Dispatch from within the state machien are as follows:
201 : *
202 : * (1) Only the State::Enter method should call Dispatch. Calls from Exit or
203 : * LogTransition will cause an abort.
204 : * (2) The transitions table may return a new state OR call Dispatch, but must
205 : * never do both. Doing both will cause an abort.
206 : *
207 : * @tparam TState a variant holding the States.
208 : * @tparam TEvent a variant holding the Events.
209 : * @tparam TTransitions an object that implements the () operator for transitions.
210 : */
211 : template <typename TState, typename TEvent, typename TTransitions>
212 : class StateMachine : public Context<TEvent>
213 : {
214 : public:
215 6 : StateMachine(TTransitions & tr) : mCurrentState(tr.GetInitState()), mTransitions(tr), mSequence(0) {}
216 6 : ~StateMachine() override = default;
217 15 : void Dispatch(const TEvent & evt) override
218 : {
219 15 : ++mSequence;
220 15 : auto prev = mSequence;
221 15 : auto newState = mTransitions(mCurrentState, evt);
222 15 : if (newState.HasValue())
223 : {
224 10 : auto oldState = mCurrentState.GetName();
225 10 : mCurrentState.Exit();
226 10 : mCurrentState = newState.Value();
227 10 : mCurrentState.LogTransition(oldState);
228 : // It is impermissible to dispatch events from Exit() or
229 : // LogTransition(), or from the transitions table when a transition
230 : // has also been returned. Verify that this hasn't occurred.
231 10 : VerifyOrDie(prev == mSequence);
232 10 : mCurrentState.Enter();
233 : }
234 15 : }
235 14 : TState GetState() { return mCurrentState; }
236 :
237 : private:
238 : TState mCurrentState;
239 : TTransitions & mTransitions;
240 : unsigned mSequence;
241 : };
242 :
243 : } // namespace StateMachine
244 : } // namespace chip
|