Matter SDK Coverage Report
Current view: top level - lib/support - StateMachine.h (source / functions) Coverage Total Hit
Test: SHA:4d2388ac7eed75b2fe5e05e20de377999c632502 Lines: 100.0 % 57 57
Test Date: 2025-07-27 07:17:09 Functions: 96.8 % 31 30

            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
        

Generated by: LCOV version 2.0-1