From 6cf3878e8838b1a28cf2fe61711893041796eee7 Mon Sep 17 00:00:00 2001 From: Antonio Maiorano Date: Sun, 8 Oct 2017 20:44:22 -0400 Subject: [PATCH 1/6] Delete outdated "sample_1_basic" --- samples/hsm_sample_1_basic/Player.cpp | 276 ------------------ samples/hsm_sample_1_basic/Player.h | 31 -- .../hsm_sample_1_basic/hsm_sample_1_basic.sln | 20 -- .../hsm_sample_1_basic.vcxproj | 89 ------ .../hsm_sample_1_basic.vcxproj.filters | 28 -- samples/hsm_sample_1_basic/main.cpp | 14 - 6 files changed, 458 deletions(-) delete mode 100644 samples/hsm_sample_1_basic/Player.cpp delete mode 100644 samples/hsm_sample_1_basic/Player.h delete mode 100644 samples/hsm_sample_1_basic/hsm_sample_1_basic.sln delete mode 100644 samples/hsm_sample_1_basic/hsm_sample_1_basic.vcxproj delete mode 100644 samples/hsm_sample_1_basic/hsm_sample_1_basic.vcxproj.filters delete mode 100644 samples/hsm_sample_1_basic/main.cpp diff --git a/samples/hsm_sample_1_basic/Player.cpp b/samples/hsm_sample_1_basic/Player.cpp deleted file mode 100644 index d27d80b..0000000 --- a/samples/hsm_sample_1_basic/Player.cpp +++ /dev/null @@ -1,276 +0,0 @@ -#include "Player.h" - -namespace -{ - struct CombatInfo - { - // Pretend there's super useful data here - }; - - // Stub - void SetPlayerControlsLocked(bool bLocked) {} -} - -// To reduce verbosity, it's recommended to bring in the hsm namespace - but only in the cpp and never in the header! -using namespace hsm; - -// Define the inner struct States - this is where we put all our states -struct Player::States -{ - // Declare a custom base State class that effectively derives from hsm::State and sets Player as - // the Owner type. This basically adds the member function Player& Owner() to your states. Internally, - // it's just casting the state machine's void* pointer to your owner to Player for you. Note that you - // can just use typedef or declare a StateBase struct that derives from StateWithOwner in which - // you could add utility functions for all states. - struct StateBase : StateWithOwner - { - // An example state message/event. @TODO: Add support for generic state messages. - virtual bool OnCombatStarted(const CombatInfo& combatInfo) { return false; } - }; - - // Declare a root state. This will be the initial state we pass to the state machine. Note that you don't - // actually need a single root state - if you want to have a simple flat state machine, just pass whatever - // initial state you want and perform only sibling transitions. - struct Root : StateBase - { - // This macro is necessary because we set the HSM to NOT use the standard C++ RTTI. - DEFINE_HSM_STATE(Root); - - virtual Transition GetTransition() - { - return InnerEntryTransition(); - } - }; - - // Alive and Dead are both inner states of Root, and sibling states of each other - struct Alive : StateBase - { - DEFINE_HSM_STATE(Alive); - - virtual Transition GetTransition() - { - if ( Owner().m_health <= 0.0f ) - { - return SiblingTransition(); - } - - // Start in Exploring inner state - return InnerEntryTransition(); - } - }; - - struct Dead : StateBase - { - DEFINE_HSM_STATE(Dead); - - virtual void OnEnter() - { - // Tell some manager that we died! - } - }; - - struct Exploring : StateBase - { - DEFINE_HSM_STATE(Exploring); - - // This state is a good example of how to handle events. We store a transition object default initialized - // to NoTransition, which we keep returning in GetTransition, and once we get the event we're waiting for, - // we set the transition object to whatever transition we want. - - virtual void OnEnter() - { - m_transition = NoTransition(); - } - - virtual Transition GetTransition() - { - return m_transition; - } - - virtual void Update(/*float deltaTime*/) - { - } - - virtual bool OnCombatStarted(const CombatInfo& combatInfo) - { - // Ok, we received a combat started message, so let's set up the transition we want - // to return on the next HSM update. This is also an example of how to pass arguments - // to a state via the StateArgs mechanism. - m_transition = SiblingTransition( Fighting::Args(combatInfo) ); - - return true; // We handled this message, no need to keep dispatching up the stack - } - - Transition m_transition; - }; - - - struct Fighting : StateBase - { - DEFINE_HSM_STATE(Fighting); - - // This is an example of how to declare arguments for a state. The way it works is that - // you must declare a type named Args that derives from StateArgs, and override the - // version of OnEnter that takes 'const Args&'. - struct Args : StateArgs - { - // Having a constructor like this is convenient but not necessary. You could simply - // declare and fill up a struct instance at the call site and pass it in to the transition - // function. - Args(const CombatInfo& combatInfo) : m_combatInfo(combatInfo) {} - Args() {}; - - CombatInfo m_combatInfo; - }; - - virtual void OnEnter(const Args& args) - { - // Sometimes it's useful to cache the args like this - m_args = args; - } - - virtual Transition GetTransition() - { - // We start in Fighting_Main, and wait until our inner siblings to Fighting_Done - if ( IsInState() ) - { - return SiblingTransition(); - } - - return InnerEntryTransition(); - } - - Args m_args; - }; - - struct Fighting_Main : StateBase - { - DEFINE_HSM_STATE(Fighting_Main); - - virtual Transition GetTransition() - { - if ( IsCombatFinished() ) - { - return SiblingTransition(); - } - - if ( DoCinematicAttack() ) - { - return SiblingTransition(); - } - - return NoTransition(); - } - - bool IsCombatFinished() // Stub - { - static int frame = 0; - ++frame; - return frame >= 10; - } - - bool DoCinematicAttack() // Stub - { - return true; - } - }; - - struct Fighting_CinematicAttack : StateBase - { - DEFINE_HSM_STATE(Fighting_CinematicAttack); - - virtual void OnEnter() - { - // An example of setting a StateValue. When this state gets popped off the stack, the value - // of m_bLockControls will go back to whatever it was before we set it in this state. Note that - // we can set it as often as we like in this state, it will go back to whatever it was before this - // state was pushed. - SetStateValue(Owner().m_bLockControls) = true; // Lock controls during cinematics - } - - virtual Transition GetTransition() - { - if ( IsCinematicAttackFinished() ) - { - return SiblingTransition(); - } - - return NoTransition(); - } - - bool IsCinematicAttackFinished() // Stub - { - return true; - } - }; - - // This state doesn't do anything - an outer state checks for its existence in GetTransition() - // and performs a transition when found. In other words, this is a transient state that acts like - // a bool, and should never remain on the stack once it has settled. - struct Fighting_Done : StateBase - { - DEFINE_HSM_STATE(Fighting_Done); - }; -}; - - -Player::Player() : - m_health(100.0f), - m_bLockControls(false) -{ -} - -void Player::Init() -{ - // Initialize the state machine, passing in the root state type, the owner (this), and debug info. - int debugLevel = 2; // 0 is no logging, 1 is basic logging, and 2 is verbose (for now, these may change) - m_stateMachine.Initialize(this); - - m_stateMachine.SetDebugInfo(HSM_TEXT("Player"), TraceLevel::Basic); -} - -void Player::Shutdown() -{ - m_stateMachine.Shutdown(); -} - -void Player::FrameUpdate(float deltaTime) -{ - // Update the state machine once per frame. This will first process all state transitions until - // the state stack has settled. - m_stateMachine.ProcessStateTransitions(); - - // Update all states from outermost to innermost. - // To pass in deltaTime here, modify the HSM_STATE_UPDATE_ARGS macros in config.h - m_stateMachine.UpdateStates(/*deltaTime*/); - - // This is how to invoke a custom function on our states - //InnerToOuterIterator iter = m_stateMachine.BeginInnerToOuter(); - //InnerToOuterIterator end = m_stateMachine.EndInnerToOuter(); - //for ( ; iter != end; ++iter) - //{ - // static_cast(*iter)->CustomFunc(); - //} - - // Here we read a StateValue that may have been modified by the state machine - SetPlayerControlsLocked(m_bLockControls); - - // Wait a few frames to send a CombatStarted message to the HSM - static int frame = 0; - ++frame; - if (frame == 5) - { - CombatInfo combatInfo; - - // This is one way to send a message to the HSM. We decide to invoke from innermost to outermost - // because this emulates virtual dispatch. - InnerToOuterIterator iter = m_stateMachine.BeginInnerToOuter(); - InnerToOuterIterator end = m_stateMachine.EndInnerToOuter(); - for ( ; iter != end; ++iter) - { - bool bHandled = static_cast(*iter)->OnCombatStarted(combatInfo); - if (bHandled) - break; - } - } -} diff --git a/samples/hsm_sample_1_basic/Player.h b/samples/hsm_sample_1_basic/Player.h deleted file mode 100644 index 9298220..0000000 --- a/samples/hsm_sample_1_basic/Player.h +++ /dev/null @@ -1,31 +0,0 @@ -#include "../../include/hsm.h" - -// Player is a class that _owns_ a state machine. The states defined in the cpp can access this -// owner and can access its privates. -class Player -{ -public: - Player(); - void Init(); - void Shutdown(); - void FrameUpdate(float deltaTime); - -private: - // The state machine instance that will maintain the state stack - hsm::StateMachine m_stateMachine; - - // Declare an inner-struct here which we will define in the cpp file. In this struct, we will put all our - // states. This serves two purposes: - // 1) Because of C++ parsing rules for inner types, the states we add within the States struct can refer to other - // states defined below it - no need to forward declare them. - // 2) By making this an inner type of Player, all states will have access to the privates of Player. This - // is because in C++, inner types are members and as such has the same access rights as any other member. - struct States; - - // Regular data member - float m_health; - - // StateValues - the values of these variables are bound to the state stack. What this means is that if a state - // modifies the value, when that state gets popped, the value will revert to its previous value. - hsm::StateValue m_bLockControls; -}; diff --git a/samples/hsm_sample_1_basic/hsm_sample_1_basic.sln b/samples/hsm_sample_1_basic/hsm_sample_1_basic.sln deleted file mode 100644 index f2da7ff..0000000 --- a/samples/hsm_sample_1_basic/hsm_sample_1_basic.sln +++ /dev/null @@ -1,20 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 11.00 -# Visual Studio 2010 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "hsm_sample_1_basic", "hsm_sample_1_basic.vcxproj", "{5F8DA46D-2A30-443A-8B8B-B0B60FF9A7CA}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Win32 = Debug|Win32 - Release|Win32 = Release|Win32 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {5F8DA46D-2A30-443A-8B8B-B0B60FF9A7CA}.Debug|Win32.ActiveCfg = Debug|Win32 - {5F8DA46D-2A30-443A-8B8B-B0B60FF9A7CA}.Debug|Win32.Build.0 = Debug|Win32 - {5F8DA46D-2A30-443A-8B8B-B0B60FF9A7CA}.Release|Win32.ActiveCfg = Release|Win32 - {5F8DA46D-2A30-443A-8B8B-B0B60FF9A7CA}.Release|Win32.Build.0 = Release|Win32 - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection -EndGlobal diff --git a/samples/hsm_sample_1_basic/hsm_sample_1_basic.vcxproj b/samples/hsm_sample_1_basic/hsm_sample_1_basic.vcxproj deleted file mode 100644 index c4648bc..0000000 --- a/samples/hsm_sample_1_basic/hsm_sample_1_basic.vcxproj +++ /dev/null @@ -1,89 +0,0 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - - {5F8DA46D-2A30-443A-8B8B-B0B60FF9A7CA} - Win32Proj - hsm_sample_1_basic - - - - Application - true - Unicode - v120 - - - Application - false - true - Unicode - v120 - - - - - - - - - - - - - true - - - false - - - - - - Level3 - Disabled - WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) - - - Console - true - - - - - Level3 - - - MaxSpeed - true - true - WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - - - Console - true - true - true - - - - - - - - - - - - - - \ No newline at end of file diff --git a/samples/hsm_sample_1_basic/hsm_sample_1_basic.vcxproj.filters b/samples/hsm_sample_1_basic/hsm_sample_1_basic.vcxproj.filters deleted file mode 100644 index 1dd69e1..0000000 --- a/samples/hsm_sample_1_basic/hsm_sample_1_basic.vcxproj.filters +++ /dev/null @@ -1,28 +0,0 @@ - - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx - - - {0c5df389-f540-494b-ae61-f995c65d7588} - - - - - Source Files - - - Source Files - - - - - Source Files - - - hsm - - - \ No newline at end of file diff --git a/samples/hsm_sample_1_basic/main.cpp b/samples/hsm_sample_1_basic/main.cpp deleted file mode 100644 index b5810bd..0000000 --- a/samples/hsm_sample_1_basic/main.cpp +++ /dev/null @@ -1,14 +0,0 @@ -#include "Player.h" - -int main() -{ - Player player; - player.Init(); - - for (int i = 0; i < 10; ++i) - { - player.FrameUpdate(0.1f); - } - - player.Shutdown(); -} From 53d8e6da2dab03837558a2eb05340e6b23718a25 Mon Sep 17 00:00:00 2001 From: Antonio Maiorano Date: Sun, 8 Oct 2017 21:05:53 -0400 Subject: [PATCH 2/6] Always store RTTI info on StateTypeId, whether using C++ RTTI or custom Also improve compiler definition macros. --- include/hsm.h | 142 ++++++++++++++++++++++++++++---------------------- 1 file changed, 80 insertions(+), 62 deletions(-) diff --git a/include/hsm.h b/include/hsm.h index 57958d5..eea7af7 100644 --- a/include/hsm.h +++ b/include/hsm.h @@ -18,7 +18,20 @@ #ifndef __HSM_H__ #define __HSM_H__ -#ifdef _MSC_VER +// Compiler defines +#if defined(__clang__) +#define HSM_COMPILER_CLANG +#elif defined(__GNUC__) || defined(__GNUG__) +#define HSM_COMPILER_GCC +#elif defined(_MSC_VER) +#define HSM_COMPILER_MSC +#endif + +#if defined(HSM_COMPILER_CLANG) || defined(HSM_COMPILER_GCC) +#define HSM_COMPILER_CLANG_OR_GCC +#endif + +#ifdef HSM_COMPILER_MSC #pragma region "Config" #endif /////////////////////////////////////////////////////////////////////////////////////////////////// @@ -41,12 +54,11 @@ #endif #endif -// If set, even if the compiler has RTTI enabled, this will force the usage of the custom HSM RTTI system -#define HSM_FORCE_CUSTOM_RTTI 0 - -// HSM_CPP_RTTI is set if the compiler has standard C++ RTTI support enabled -#if !HSM_FORCE_CUSTOM_RTTI && ( (defined(_MSC_VER) && defined(_CPPRTTI)) || (defined(__GNUC__) && defined(__GXX_RTTI)) ) -#define HSM_CPP_RTTI 1 +// If set and C++ RTTI is enabled, will use C++ RTTI instead of the custom HSM RTTI system to +// identify state types and return state names. Using C++ RTTI may yield better performance +// when comparing StateTypeIds, but the state names returned are usually less human readable. +#if !defined(HSM_USE_CPP_RTTI_IF_ENABLED) +#define HSM_USE_CPP_RTTI_IF_ENABLED 1 #endif #define HSM_STD_VECTOR std::vector @@ -71,13 +83,13 @@ typedef char hsm_char; #define HSM_PRINTF ::printf #define STRCMP ::strcmp -#ifdef _MSC_VER +#ifdef HSM_COMPILER_MSC #define SNPRINTF ::_snprintf_s #else #define SNPRINTF ::snprintf #endif -#ifdef _MSC_VER +#ifdef HSM_COMPILER_MSC #define STRNCPY ::strncpy_s #else #define STRNCPY ::strncpy @@ -101,18 +113,23 @@ typedef void Owner; } // namespace hsm -#ifdef _MSC_VER +#ifdef HSM_COMPILER_MSC #pragma endregion "Config" #endif -#ifdef _MSC_VER +#ifdef HSM_COMPILER_MSC #pragma region "RTTI" #endif /////////////////////////////////////////////////////////////////////////////////////////////////// // RTTI /////////////////////////////////////////////////////////////////////////////////////////////////// -#if HSM_CPP_RTTI +// HSM_USE_CPP_RTTI is set if the compiler has standard C++ RTTI support enabled +#if HSM_USE_CPP_RTTI_IF_ENABLED && ( (defined(HSM_COMPILER_MSC) && defined(_CPPRTTI)) || (defined(HSM_COMPILER_CLANG_OR_GCC) && defined(__GXX_RTTI)) ) +#define HSM_USE_CPP_RTTI +#endif + +#ifdef HSM_USE_CPP_RTTI #include @@ -121,35 +138,34 @@ namespace hsm { // We use standard C++ RTTI // We need a copyable wrapper around std::type_info since std::type_info is non-copyable -struct StateTypeIdStorage +struct StateTypeId { - StateTypeIdStorage() : m_typeInfo(0) { } - StateTypeIdStorage(const std::type_info& typeInfo) { m_typeInfo = &typeInfo; } - hsm_bool operator==(const StateTypeIdStorage& rhs) const + StateTypeId() : mTypeInfo(0) {} + StateTypeId(const std::type_info& typeInfo) : mTypeInfo(&typeInfo) {} + hsm_bool operator==(const StateTypeId& rhs) const { - HSM_ASSERT_MSG(m_typeInfo != 0, "m_typeInfo was not properly initialized"); - return *m_typeInfo == *rhs.m_typeInfo; + HSM_ASSERT_MSG(mTypeInfo != 0, "mTypeInfo was not properly initialized"); + return *mTypeInfo == *rhs.mTypeInfo; } - const std::type_info* m_typeInfo; + const std::type_info* mTypeInfo; }; -typedef const StateTypeIdStorage& StateTypeId; - template -StateTypeId GetStateType() +const StateTypeId& GetStateType() { - static StateTypeIdStorage stateTypeId(typeid(StateType)); + static StateTypeId stateTypeId(typeid(StateType)); return stateTypeId; } -} // namespace hsm - -// DEFINE_HSM_STATE is NOT necessary; however, we define it here to nothing to make it easier to -// test switching between compiler RTTI enabled or disabled. -#define DEFINE_HSM_STATE(__StateName__) +template +const char* GetStateName() +{ + return GetStateType().mTypeInfo->name(); +} +} // namespace hsm -#else // !HSM_CPP_RTTI +#else // !HSM_USE_CPP_RTTI namespace hsm { @@ -157,24 +173,30 @@ namespace hsm { // DEFINE_HSM_STATE macro, which makes use of the input name of the state as the unique identifier. String // compares are used to determine equality. -// Like std::type_info, we need to be able test equality and get a unique name -struct StateTypeIdStorage +// We need a comparable wrapper around the type name. We can't just compare const char* pointers +// because GetTypeName may return two different strings for the same T in different translation units. +struct StateTypeId { - StateTypeIdStorage(const hsm_char* aStateName = 0) : mStateName(aStateName) {} - hsm_bool operator==(const StateTypeIdStorage& rhs) const + StateTypeId(const hsm_char* aStateName = 0) : mStateName(aStateName) {} + hsm_bool operator==(const StateTypeId& rhs) const { HSM_ASSERT_MSG(mStateName != 0, "StateTypeId was not properly initialized"); return STRCMP(mStateName, rhs.mStateName) == 0; } const hsm_char* mStateName; }; - -typedef const StateTypeIdStorage& StateTypeId; + +template +const StateTypeId& GetStateType() +{ + static StateTypeId stateTypeId = internal::GetTypeName(); + return stateTypeId; +} template -StateTypeId GetStateType() +const char* GetStateName() { - return StateType::GetStaticStateType(); + return GetStateType().mStateName; } } // namespace hsm @@ -185,13 +207,13 @@ StateTypeId GetStateType() virtual hsm::StateTypeId DoGetStateType() const { return GetStaticStateType(); } \ virtual const hsm_char* DoGetStateDebugName() const { return HSM_TEXT(#__StateName__); } -#endif // !HSM_CPP_RTTI +#endif // !HSM_USE_CPP_RTTI -#ifdef _MSC_VER +#ifdef HSM_COMPILER_MSC #pragma endregion "RTTI" #endif -#ifdef _MSC_VER +#ifdef HSM_COMPILER_MSC #pragma region "Transition" #endif /////////////////////////////////////////////////////////////////////////////////////////////////// @@ -212,6 +234,7 @@ const StateFactory& GetStateFactory(); struct StateFactory { virtual StateTypeId GetStateType() const = 0; + virtual const char* GetStateName() const = 0; virtual State* AllocateState() const = 0; }; @@ -228,6 +251,11 @@ struct ConcreteStateFactory : StateFactory return hsm::GetStateType(); } + virtual const char* GetStateName() const + { + return hsm::GetStateName(); + } + virtual State* AllocateState() const { return HSM_NEW TargetState(); @@ -441,11 +469,11 @@ inline Transition NoTransition() } // namespace hsm -#ifdef _MSC_VER +#ifdef HSM_COMPILER_MSC #pragma endregion "Transition" #endif -#ifdef _MSC_VER +#ifdef HSM_COMPILER_MSC #pragma region "State" #endif /////////////////////////////////////////////////////////////////////////////////////////////////// @@ -517,7 +545,7 @@ struct ConcreteStateValueResetter : StateValueResetter namespace detail { - void InitState(State* state, StateMachine* ownerStateMachine, size_t stackDepth); + void InitState(State* state, StateMachine* ownerStateMachine, size_t stackDepth, const StateFactory& stateFactory); } struct State @@ -644,17 +672,7 @@ struct State StateOverride GetStateOverride(); private: - -#if HSM_CPP_RTTI - StateTypeIdStorage DoGetStateType() const { return typeid(*this); } - const hsm_char* DoGetStateDebugName() const { return typeid(*this).name(); } -#else - // These are implemented in each state via the DEFINE_HSM_STATE macro - virtual StateTypeId DoGetStateType() const = 0; - virtual const hsm_char* DoGetStateDebugName() const = 0; -#endif - - friend void detail::InitState(State* state, StateMachine* ownerStateMachine, size_t stackDepth); + friend void detail::InitState(State* state, StateMachine* ownerStateMachine, size_t stackDepth, const StateFactory& stateFactory); template StateValue* FindStateValueInResetterList(StateValue& stateValue) @@ -691,7 +709,7 @@ struct State StateValueResetterList mStateValueResetters; // Values cached to avoid virtual call, especially since the values are constant - StateTypeIdStorage mStateTypeId; + StateTypeId mStateTypeId; const hsm_char* mStateDebugName; }; @@ -729,11 +747,11 @@ struct StateWithOwner : StateBaseType } // namespace hsm -#ifdef _MSC_VER +#ifdef HSM_COMPILER_MSC #pragma endregion "State" #endif -#ifdef _MSC_VER +#ifdef HSM_COMPILER_MSC #pragma region "StateMachine" #endif /////////////////////////////////////////////////////////////////////////////////////////////////// @@ -1017,20 +1035,20 @@ inline const StateFactory& StateMachine::GetStateOverride() namespace detail { - inline void InitState(State* state, StateMachine* ownerStateMachine, size_t stackDepth) + inline void InitState(State* state, StateMachine* ownerStateMachine, size_t stackDepth, const StateFactory& stateFactory) { HSM_ASSERT(ownerStateMachine != 0); state->mOwnerStateMachine = ownerStateMachine; state->mOwner = ownerStateMachine->GetOwner(); state->mStackDepth = stackDepth; - state->mStateTypeId = state->DoGetStateType(); - state->mStateDebugName = state->DoGetStateDebugName(); + state->mStateTypeId = stateFactory.GetStateType(); + state->mStateDebugName = stateFactory.GetStateName(); } inline State* CreateState(const Transition& transition, StateMachine* ownerStateMachine, size_t stackDepth) { State* state = transition.GetStateFactory().AllocateState(); - InitState(state, ownerStateMachine, stackDepth); + InitState(state, ownerStateMachine, stackDepth, transition.GetStateFactory()); return state; } @@ -1347,7 +1365,7 @@ inline void StateMachine::LogTransition(size_t minLevel, size_t depth, const hsm } // namespace hsm -#ifdef _MSC_VER +#ifdef HSM_COMPILER_MSC #pragma endregion "StateMachine" #endif From 9e777cf40d7ae7c90eb2eb6d514a72e480c33e86 Mon Sep 17 00:00:00 2001 From: Antonio Maiorano Date: Mon, 9 Oct 2017 16:53:14 -0400 Subject: [PATCH 3/6] Assert that variadic args OnEnterArgs func is invoked on the expected type of state at runtime --- include/hsm.h | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/include/hsm.h b/include/hsm.h index eea7af7..cab2f3e 100644 --- a/include/hsm.h +++ b/include/hsm.h @@ -307,7 +307,13 @@ namespace detail // Purposely capture args by copy rather than by reference in case args are // created on the stack. Use std::ref() to wrap args that do not need to be copied. - return [args...](State* state) { (static_cast(state))->OnEnter(std::move(args)...); }; + return [args...](State* state) + { + HSM_ASSERT_MSG(state->GetStateType() == GetStateType(), + "Type of state to call OnEnter on doesn't match original target state returned by transition"); + + static_cast(state)->OnEnter(std::move(args)...); + }; } // Base case: do nothing @@ -358,7 +364,7 @@ struct Transition } // Transition with state args - Transition(Transition::Type transitionType, const StateFactory& stateFactory, OnEnterArgsFunc&& onEnterArgsFunc) + Transition(Transition::Type transitionType, const StateFactory& stateFactory, OnEnterArgsFunc onEnterArgsFunc) : mTransitionType(transitionType) , mStateFactory(&stateFactory) , mOnEnterArgsFunc(std::move(onEnterArgsFunc)) From 3430be3dd55e1a9205eca2cefb27a65ead8ac59a Mon Sep 17 00:00:00 2001 From: Antonio Maiorano Date: Mon, 9 Oct 2017 20:27:30 -0400 Subject: [PATCH 4/6] Move GenerateOnEnterArgsFunc and related code below State definition to fix undefined class usage when compiling with gcc and clang --- include/hsm.h | 108 ++++++++++++++++++++++++++------------------------ 1 file changed, 57 insertions(+), 51 deletions(-) diff --git a/include/hsm.h b/include/hsm.h index cab2f3e..9c874d0 100644 --- a/include/hsm.h +++ b/include/hsm.h @@ -287,60 +287,11 @@ struct StateOverride typedef std::function OnEnterArgsFunc; -// MSVC 14 (VS 2015) doesn't handle generating lambdas that capture C-style arrays ("const T(&)[n]") -// by value, emitting error "array initialization requires a brace-enclosed initializer list". -// The most common case that this affects are immediate strings, so we work around this issue by -// detecting this case and forwarding them as "const char*", which is safe since immediate strings -// have global lifetime. -#ifdef _MSC_VER -#define DECAY_IMMEDIATE_STRINGS_WHEN_FORWARDING -#endif - namespace detail { - // Generates a lambda that will invoke TargetState::OnEnter with matching args. - // We get a compiler-time error if a matching OnEnter is not found. - template - OnEnterArgsFunc DoGenerateOnEnterArgsFunc(Args&&... args) - { - static_assert(std::is_convertible::value, "TargetState must derive from hsm::State"); - - // Purposely capture args by copy rather than by reference in case args are - // created on the stack. Use std::ref() to wrap args that do not need to be copied. - return [args...](State* state) - { - HSM_ASSERT_MSG(state->GetStateType() == GetStateType(), - "Type of state to call OnEnter on doesn't match original target state returned by transition"); - - static_cast(state)->OnEnter(std::move(args)...); - }; - } - - // Base case: do nothing - template - T&& DecayIfImmediateString(T&& arg1) - { - return std::forward(arg1); - } - - // If immediate string, decay to const char* - template - const char* DecayIfImmediateString(const char(&arr)[_Nx]) - { - return static_cast(arr); - } - template - OnEnterArgsFunc GenerateOnEnterArgsFunc(Args&&... args) - { -#ifdef DECAY_IMMEDIATE_STRINGS_WHEN_FORWARDING - return DoGenerateOnEnterArgsFunc(DecayIfImmediateString(args)...); -#else - return DoGenerateOnEnterArgsFunc(std::forward(args)...); -#endif // DECAY_IMMEDIATE_STRINGS_WHEN_FORWARDING - } -} // namespace detail - + OnEnterArgsFunc GenerateOnEnterArgsFunc(Args&&... args); +} // Transition objects are created via the free-standing transition functions below, and typically returned by // GetTransition. They can also be stored as data members, passed around, and returned later. They are meant @@ -719,6 +670,61 @@ struct State const hsm_char* mStateDebugName; }; +// MSVC 14 (VS 2015) doesn't handle generating lambdas that capture C-style arrays ("const T(&)[n]") +// by value, emitting error "array initialization requires a brace-enclosed initializer list". +// The most common case that this affects are immediate strings, so we work around this issue by +// detecting this case and forwarding them as "const char*", which is safe since immediate strings +// have global lifetime. +#ifdef _MSC_VER +#define DECAY_IMMEDIATE_STRINGS_WHEN_FORWARDING +#endif + +namespace detail +{ + // Generates a lambda that will invoke TargetState::OnEnter with matching args. + // We get a compiler-time error if a matching OnEnter is not found. + template + OnEnterArgsFunc DoGenerateOnEnterArgsFunc(Args&&... args) + { + static_assert(std::is_convertible::value, "TargetState must derive from hsm::State"); + + // Purposely capture args by copy rather than by reference in case args are + // created on the stack. Use std::ref() to wrap args that do not need to be copied. + return[args...](State* state) + { + HSM_ASSERT_MSG(state->GetStateType() == GetStateType(), + "Type of state to call OnEnter on doesn't match original target state returned by transition"); + + static_cast(state)->OnEnter(std::move(args)...); + }; + } + + // Base case: do nothing + template + T&& DecayIfImmediateString(T&& arg1) + { + return std::forward(arg1); + } + + // If immediate string, decay to const char* + template + const char* DecayIfImmediateString(const char(&arr)[_Nx]) + { + return static_cast(arr); + } + + template + OnEnterArgsFunc GenerateOnEnterArgsFunc(Args&&... args) + { +#ifdef DECAY_IMMEDIATE_STRINGS_WHEN_FORWARDING + return DoGenerateOnEnterArgsFunc(DecayIfImmediateString(args)...); +#else + return DoGenerateOnEnterArgsFunc(std::forward(args)...); +#endif // DECAY_IMMEDIATE_STRINGS_WHEN_FORWARDING + } +} // namespace detail + + // StateWithOwner From ccb7566088c60246cd28f458cce4747f84568605 Mon Sep 17 00:00:00 2001 From: Antonio Maiorano Date: Mon, 9 Oct 2017 20:28:34 -0400 Subject: [PATCH 5/6] Add HSM_DEPRECATED macro and use them to deprecate three functions that were deprecated in comments --- include/hsm.h | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/include/hsm.h b/include/hsm.h index 9c874d0..8d145bb 100644 --- a/include/hsm.h +++ b/include/hsm.h @@ -31,6 +31,16 @@ #define HSM_COMPILER_CLANG_OR_GCC #endif +// HSM_DEPRECATED macro +#if defined(HSM_COMPILER_CLANG_OR_GCC) +#define HSM_DEPRECATED(MESSAGE) __attribute__((deprecated("DEPRECATED: " MESSAGE))) +#elif defined(HSM_COMPILER_MSC) +#define HSM_DEPRECATED(MESSAGE) __declspec(deprecated("DEPRECATED: " MESSAGE)) +#else +#pragma message("Implement HSM_DEPRECATED for this compiler") +#define HSM_DEPRECATED +#endif + #ifdef HSM_COMPILER_MSC #pragma region "Config" #endif @@ -882,9 +892,8 @@ class StateMachine template const StateFactory& GetStateOverride(); - - //@DEPRECATED: Initialize should no longer accept debug info. Use SetDebugInfo instead. template + HSM_DEPRECATED("Initialize should no longer accept debug info. Use SetDebugInfo instead.") void Initialize(Owner* owner, const hsm_char* debugName, size_t debugLevel) { HSM_ASSERT(mInitialTransition.IsNo()); @@ -893,13 +902,13 @@ class StateMachine SetDebugInfo(debugName, debugLevel); } - //@DEPRECATED: Use SetDebugInfo(const hsm_char*, TraceLevel::Type) - void SetDebugInfo(const hsm_char* name, size_t level) { SetDebugName(name); SetDebugLevel(level); } + HSM_DEPRECATED("Use SetDebugInfo(const hsm_char*, TraceLevel::Type)") + void SetDebugInfo(const hsm_char* name, size_t level) { SetDebugName(name); SetDebugTraceLevel(static_cast(level)); } - //@DEPRECATED: Use SetDebugTraceLevel + HSM_DEPRECATED("Use SetDebugTraceLevel") void SetDebugLevel(size_t level) { SetDebugTraceLevel(static_cast(level)); } - //@DEPRECATED: Use GetDebugTraceLevel + HSM_DEPRECATED("Use GetDebugLevel") size_t GetDebugLevel() { return static_cast(GetDebugTraceLevel()); } private: From d7899047b14a4b7be0cb42251d4a76e4ea518e47 Mon Sep 17 00:00:00 2001 From: Antonio Maiorano Date: Mon, 9 Oct 2017 20:43:28 -0400 Subject: [PATCH 6/6] Deprecate transition functions for state overrides that also accept state args as these cannot be supported --- include/hsm.h | 21 +++++++------------ .../source/ch4/state_overrides.cpp | 10 ++++----- 2 files changed, 12 insertions(+), 19 deletions(-) diff --git a/include/hsm.h b/include/hsm.h index 8d145bb..0d053b9 100644 --- a/include/hsm.h +++ b/include/hsm.h @@ -370,11 +370,11 @@ Transition SiblingTransition(Args&&... args) return Transition(Transition::Sibling, GetStateFactory(), detail::GenerateOnEnterArgsFunc(std::forward(args)...)); } +// Deprecated after v1.5 upon realizing that it's not possible to bind to the correct OnEnter for state +// overrides since the actual target state is not known at compile time, but rather at runtime. template -Transition SiblingTransition(const StateOverride& stateOverride, T1&& arg1, Args&&... args) -{ - return Transition(Transition::Sibling, stateOverride.mStateFactory, detail::GenerateOnEnterArgsFunc(std::forward(arg1), std::forward(args)...)); -} +HSM_DEPRECATED("Passing state args to state overrides is not supported") +Transition SiblingTransition(const StateOverride& stateOverride, T1&& arg1, Args&&... args); // InnerTransition @@ -396,10 +396,8 @@ Transition InnerTransition(Args&&... args) } template -Transition InnerTransition(const StateOverride& stateOverride, T1&& arg1, Args&&... args) -{ - return Transition(Transition::Inner, stateOverride.mStateFactory, detail::GenerateOnEnterArgsFunc(std::forward(arg1), std::forward(args)...)); -} +HSM_DEPRECATED("Passing state args to state overrides is not supported") // See details on SiblingTransition version +Transition InnerTransition(const StateOverride& stateOverride, T1&& arg1, Args&&... args); // InnerEntryTransition @@ -421,11 +419,8 @@ Transition InnerEntryTransition(Args&&... args) } template -Transition InnerEntryTransition(const StateOverride& stateOverride, T1&& arg1, Args&&... args) -{ - return Transition(Transition::InnerEntry, stateOverride.mStateFactory, detail::GenerateOnEnterArgsFunc(std::forward(arg1), std::forward(args)...)); -} - +HSM_DEPRECATED("Passing state args to state overrides is not supported") // See details on SiblingTransition version +Transition InnerEntryTransition(const StateOverride& stateOverride, T1&& arg1, Args&&... args); // NoTransition diff --git a/samples/hsm_book_samples/source/ch4/state_overrides.cpp b/samples/hsm_book_samples/source/ch4/state_overrides.cpp index 995fb49..e027ba7 100644 --- a/samples/hsm_book_samples/source/ch4/state_overrides.cpp +++ b/samples/hsm_book_samples/source/ch4/state_overrides.cpp @@ -52,7 +52,7 @@ struct CharacterStates if (Owner().mJump) { Owner().mJump = false; - return SiblingTransition(GetStateOverride(), 20); + return SiblingTransition(GetStateOverride()); } return NoTransition(); @@ -69,14 +69,13 @@ struct CharacterStates struct JumpBase { - virtual void OnEnter(int height) = 0; + virtual void OnEnter() = 0; }; struct Jump : BaseState, JumpBase { - void OnEnter(int height) override + void OnEnter() override { - (void)height; } Transition GetTransition() override @@ -130,9 +129,8 @@ struct HeroStates struct Jump : BaseState, CharacterStates::JumpBase { - void OnEnter(int height) override + void OnEnter() override { - (void)height; } Transition GetTransition() override