Program Listing for File FSM.h

Program Listing for File FSM.h#

Return to documentation for file (include/Karana/KUtils/FSM.h)

/*
 * Copyright (c) 2024-2026 Karana Dynamics Pty Ltd. All rights reserved.
 *
 * NOTICE TO USER:
 *
 * This source code and/or documentation (the "Licensed Materials") is
 * the confidential and proprietary information of Karana Dynamics Inc.
 * Use of these Licensed Materials is governed by the terms and conditions
 * of a separate software license agreement between Karana Dynamics and the
 * Licensee ("License Agreement"). Unless expressly permitted under that
 * agreement, any reproduction, modification, distribution, or disclosure
 * of the Licensed Materials, in whole or in part, to any third party
 * without the prior written consent of Karana Dynamics is strictly prohibited.
 *
 * THE LICENSED MATERIALS ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND.
 * KARANA DYNAMICS DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING
 * BUT NOT LIMITED TO WARRANTIES OF MERCHANTABILITY, NON-INFRINGEMENT, AND
 * FITNESS FOR A PARTICULAR PURPOSE.
 *
 * IN NO EVENT SHALL KARANA DYNAMICS BE LIABLE FOR ANY DAMAGES WHATSOEVER,
 * INCLUDING BUT NOT LIMITED TO LOSS OF PROFITS, DATA, OR USE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGES, WHETHER IN CONTRACT, TORT,
 * OR OTHERWISE ARISING OUT OF OR IN CONNECTION WITH THE LICENSED MATERIALS.
 *
 * U.S. Government End Users: The Licensed Materials are a "commercial item"
 * as defined at 48 C.F.R. 2.101, and are provided to the U.S. Government
 * only as a commercial end item under the terms of this license.
 *
 * Any use of the Licensed Materials in individual or commercial software must
 * include, in the user documentation and internal source code comments,
 * this Notice, Disclaimer, and U.S. Government Use Provision.
 */

/**
 * @file
 * @brief Contains the declarations for the FSM class and associated classes.
 */

#pragma once

#include "Karana/KCore/CallbackRegistry.h"
#include "Karana/KCore/LockingBase.h"
#include "Karana/KCore/UsageTrackingMap.h"

namespace Karana::KUtils {

    namespace kc = Karana::Core;

    // Forward declaration
    class FSM;
    class FSMTransition;

    /**
     * @class FSMState
     * @brief Represents a state in an FSM.
     *
     * This is a node in the graph that makes up an FSM.
     */
    class FSMState : public Karana::Core::Base {
        // For access to _fsm
        friend class FSMTransition;

        // For access to _transitions
        friend class FSM;

      public:
        /**
         * @brief FSMState constructor. The constructor is not meant to be called
         * directly. Please use the create(...) method instead to create an instance.
         *
         * @param name The name of the FSMState. The names for states in an FSM must be unique.
         * @param fsm The FSM that this FSMState will be a part of.
         */
        FSMState(std::string_view name, const kc::ks_ptr<FSM> &fsm);

        /**
         * @brief Create a new instance of FSMState.
         *
         * @param name The name of the FSMState. The names for states in an FSM must be unique.
         * @param fsm The FSM that this FSMState will be a part of.
         * @returns A pointer to the newly created instance of FSMState.
         */
        // Adding suppression, as CodeChecker complains this method has
        // the same name as a method in the base version. This is just a
        // CodeChecker false positive.
        // codechecker_suppress [cppcheck-duplInheritedMember]
        static Karana::Core::ks_ptr<FSMState> create(std::string_view name,
                                                     const kc::ks_ptr<FSM> &fsm);

        /// Functions that execute when entering the state
        Karana::Core::CallbackRegistry<void> on_enter_fns;

        /// Functions that execute when exiting the state
        Karana::Core::CallbackRegistry<void> on_exit_fns;

        /// Functions that execute during the state
        Karana::Core::CallbackRegistry<void> during_fns;

        /**
         * @brief Get all the transitions that connect this state to its children.
         *
         * @returns All the transitions that connect this state to its children.
         */
        std::vector<kc::ks_ptr<FSMTransition>> getChildTransitions();

        /**
         * @brief Get all the transitions that connect this state to its parents.
         *
         * @returns All the transitions that connect this state to its parents.
         */
        std::vector<kc::ks_ptr<FSMTransition>> getParentTransitions();

        std::string
        dumpString(std::string_view prefix = "",
                   const Karana::Core::Base::DumpOptions *options = nullptr) const override;

      protected:
        void _discard(Karana::Core::ks_ptr<Karana::Core::Base> &base) override;

      private:
        /// The FSM this FSMState is associated with
        kc::ks_ptr<FSM> _fsm;

        /// The transitions that connect this state to its children states
        std::vector<kc::ks_ptr<FSMTransition>> _transitions;

        /// The transitions that connect this state to its parent states
        std::vector<kc::ks_ptr<FSMTransition>> _parent_transitions;
    };

    /**
     * @class FSMTransition
     * @brief An edge in the graph that makes up an FSM.
     */
    class FSMTransition : public Karana::Core::Base {
        // For access to _transition_fn
        friend class FSM;

      public:
        /**
         * @brief FSMTransition constructor. The constructor is not meant to be called
         * directly. Please use the create(...) method instead to create an instance.
         *
         * @param parent_state The parent FSMState of the edge.
         * @param child_state The child FSMState of the edge.
         * @param should_transition_fn The function that indicates whether this state should
         * transition or not. This should return true when it is time to transition, and false when
         * it is not. The FSM.update method checks these functions to decide when to transition.
         */
        FSMTransition(const kc::ks_ptr<FSMState> &parent_state,
                      const kc::ks_ptr<FSMState> &child_state,
                      std::function<bool()> should_transition_fn);

        /**
         * @brief Create a new instance of FSMTransition.
         *
         * @param parent_state The parent FSMState of the edge.
         * @param child_state The child FSMState of the edge.
         * @param should_transition_fn The function that indicates whether this state should
         * transition or not. This should return true when it is time to transition, and false when
         * it is not. The FSM.update method checks these functions to decide when to transition.
         * @returns A pointer to the newly created instance of FSMTransition.
         */
        static kc::ks_ptr<FSMTransition> create(const kc::ks_ptr<FSMState> &parent_state,
                                                const kc::ks_ptr<FSMState> &child_state,
                                                std::function<bool()> should_transition_fn);
        /// Function that indicates if we should transition. true for yes and false for no.
        std::function<bool()> should_transition_fn;

        /// Functions that execute when transitioning on this edge
        Karana::Core::CallbackRegistry<void> on_transition_fns;

        /**
         * @brief Get the parent state for this edge.
         *
         * @returns The parent state for this edge.
         */
        const kc::ks_ptr<FSMState> &getParentState() const;

        /**
         * @brief Get the child state for this edge.
         *
         * @returns The child state for this edge.
         */
        const kc::ks_ptr<FSMState> &getChildState() const;

      protected:
        void _discard(Karana::Core::ks_ptr<Karana::Core::Base> &base) override;

      private:
        /// The FSM this FSMState is associated with
        kc::ks_ptr<FSM> _fsm;

        /// The parent state for this edge
        kc::ks_ptr<FSMState> _parent_state;

        /// The child state for this edge
        kc::ks_ptr<FSMState> _child_state;
    };

    /**
     * @class FSMVars
     * @brief Vars for the FSM class.
     */
    class FSMVars : public Karana::Core::LockingBaseVars {
      public:
        /**
         * @brief FSMVars constructor. The constructor is not meant to be
         *        called directly. Please use the create(...) method instead to create
         *        an instance.
         *
         * @param fsm The FSM associated with this FSMVars.
         */
        FSMVars(const kc::ks_ptr<const FSM> &fsm);

        /**
         * @brief Create an instance of the FSMVars.
         *
         * @param fsm The FSM associated with this FSMVars.
         * @returns A pointer to the newly created instance of FSMVars.
         */
        // Adding suppression, as CodeChecker complains this method has the same name as a method in
        // the LockingBase class. This is just a CodeChecker false positive. codechecker_suppress
        // [cppcheck-duplInheritedMember]
        static Karana::Core::ks_ptr<FSMVars> create(const kc::ks_ptr<const FSM> &fsm);

        /**
         * @brief Destructor.
         */
        ~FSMVars();

        /**
         * @brief Get all the Vars that this FSMVars has.
         *
         * @returns NestedVars with all the Vars that this FSMVars has.
         */
        Karana::Core::NestedVars getAllVars() const override;

        /// The current state of the FSM
        kc::ks_ptr<kc::Var_T<std::string>> current_state;
    };

    /**
     * @class FSM
     * @brief A class to implement finite state machines.
     *
     * The finite state machine is implemented as a graph that connects FSMStates with
     * FSMTransitions.
     */
    class FSM : public Karana::Core::LockingBase {
        // For access to _trackState and _untrackState
        friend class FSMState;

      public:
        using LockingBase::LockingBase;

        /**
         * @brief Create a new instance of FSM.
         *
         * @param name The name of the FSM.
         * @returns A pointer to the newly created instance of FSM.
         */
        // Adding suppression, as CodeChecker complains this method has the same name as a method in
        // the LockingBase class. This is just a CodeChecker false positive.
        // codechecker_suppress [cppcheck-duplInheritedMember]
        static Karana::Core::ks_ptr<FSM> create(std::string_view name);

        /**
         * @brief Get the current state.
         *
         * @returns The current FSMState.
         */
        const kc::ks_ptr<FSMState> &getCurrentState() const;

        /**
         * @brief Set the current state of this FSM to the state with the provided name.
         *
         * @param name The name of the state to set the FSM to.
         */
        void setCurrentState(std::string_view name);

        /**
         * @brief Set the current state of this FSM to the provided state.
         *
         * @param state The state to set the FSM to.
         */
        void setCurrentState(const Karana::Core::ks_ptr<FSMState> &state);

        /**
         * @brief Get the state with the provided name.
         *
         * @param name The name of the state to retrieve.
         * @returns The FSMState with the provided name.
         */
        const kc::ks_ptr<FSMState> &getState(std::string_view name) const;

        /**
         * @brief Get the transition that connects the two states with the provided names.
         *
         * @param parent_state The name of the parent of the edge.
         * @param child_state The name of the child of the edge.
         * @returns The transition that connects the parent and child states with the names
         * provided.
         */
        const kc::ks_ptr<FSMTransition> &getTransition(std::string_view parent_state,
                                                       std::string_view child_state);

        /**
         * @brief Get a list of all the FSMStates in this FSM.
         *
         * @returns A list of all the FSMStates in this FSM.
         */
        std::vector<kc::ks_ptr<FSMState>> getStates() const;

        /**
         * @brief Update the FSM.
         *
         * This updates the current state using the should_transition_fns of the FSMTransitions for
         * the current state.
         */
        void update();

        /// These functions will run whenever we transition to a new state
        Karana::Core::CallbackRegistry<void, const kc::ks_ptr<FSMTransition> &>
            on_state_transition_fns;

        /**
         * @brief Get whether this FSM is printing trace messages or not.
         *
         * @return true if trace messages are being printed, false otherwise.
         */
        bool getTraceFSM();

        /**
         * @brief Set whether this FSM prints trace messages or not.
         *
         * @param trace_fsm true to set the FSM to print trace messages,
         *                  false to stop printing trace messages.
         */
        void setTraceFSM(bool trace_fsm);

        std::string
        dumpString(std::string_view prefix = "",
                   const Karana::Core::Base::DumpOptions *options = nullptr) const override;

      protected:
        void _discard(Karana::Core::ks_ptr<Karana::Core::Base> &base) override;
        void _makeHealthy() override;
        Karana::Core::ks_ptr<Karana::Core::BaseVars> _getVars() const override;

      private:
        /**
         * @brief Track the provided state in a UsageTrackingMap.
         *
         * @param state The state to track.
         */
        void _trackState(const kc::ks_ptr<FSMState> &state);

        /**
         * @brief Untrack the provided state.
         *
         * This will erase the state from the UsageTrackingMap.
         *
         * @param state The state to untrack.
         */
        void _untrackState(kc::ks_ptr<FSMState> &state);

        /**
         * @brief Get the state with the provided name.
         *
         * @param name The name of the state to find.
         * @returns The state with the associated name.
         *          nullptr if a state with the provided name does not exist.
         */
        Karana::Core::ks_ptr<FSMState> _findStateWithName(std::string_view name);

        /// The current state
        FSMState *_current_state = nullptr;

        /**
         * @brief The state transitions.
         *
         * The first value in the pair is the upstream state and the second
         * value in the pair is the downstream state.
         */
        std::vector<std::pair<Karana::Core::id_t, Karana::Core::id_t>> _state_transitions;

        /// Usage tracking map for tracking the states
        Karana::Core::UsageTrackingMap<Karana::Core::id_t, FSMState> _states;

        /**
         * @brief Print a standard trace message.
         *
         * @param msg The message to print.
         */
        void _stdTraceMsg(std::string_view msg);

        /// If true, then print out trace messages.
        bool _trace_fsm = false;
    };

} // namespace Karana::KUtils