Skip to content
Permalink
Browse files

Replace custom RTTI with compiler-specific trick using __FUNCTION__/_…

…_PRETTY_FUNCTION__
  • Loading branch information...
amaiorano committed Dec 22, 2015
1 parent 14a50b1 commit f5c3b9a5c23fd07a2092336b1bc6d6f82526a022
Showing with 151 additions and 80 deletions.
  1. +151 −80 include/hsm.h
@@ -16,7 +16,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
///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -39,12 +52,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
@@ -69,13 +81,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
@@ -99,18 +111,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 <typeinfo>

@@ -119,77 +136,135 @@ 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 <typename StateType>
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 <typename StateType>
const char* GetStateName()
{
return GetStateType<StateType>().mTypeInfo->name();
}

} // namespace hsm

#else // !HSM_CPP_RTTI
#else // !HSM_USE_CPP_RTTI

namespace hsm {

// Standard C++ RTTI is not available, so we roll our own custom RTTI. All states are required to use the
// 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.
// When not using C++ RTTI, we use a custom RTTI system based on using non-standard compiler
// macros like __FUNCTION__ that evaluate to the name of the current function. When combined
// with template functions, we can extract the name of any type.
// Credit for this neat trick goes to Stefan Reinalter who posted about it on his blog:
// http://blog.molecular-matters.com/2015/12/11/getting-the-type-of-a-template-argument-as-string-without-rtti/

// Like std::type_info, we need to be able test equality and get a unique name
struct StateTypeIdStorage
{
StateTypeIdStorage(const hsm_char* aStateName = 0) : mStateName(aStateName) {}
hsm_bool operator==(const StateTypeIdStorage& rhs) const
namespace internal
{
HSM_ASSERT_MSG(mStateName != 0, "StateTypeId was not properly initialized");
return STRCMP(mStateName, rhs.mStateName) == 0;
template <typename T>
struct GetTypeNameHelper
{
static const char* Apply()
{
#if defined(HSM_COMPILER_MSC)
// e.g.: hsm::internal::GetTypeNameHelper<struct HeroStates::Alive>::Apply
#define FUNCTION_MACRO __FUNCTION__
#define PREFIX "hsm::internal::GetTypeNameHelper<"
#define SUFFIX_1 ">::Apply"
#define SUFFIX_2 ""
#define NUM_TYPE_REPEATS 1
#elif defined(HSM_COMPILER_CLANG)
// e.g.: static const char *hsm::internal::GetTypeNameHelper<HeroStates::Alive>::Apply() [T = HeroStates::Alive]
#define FUNCTION_MACRO __PRETTY_FUNCTION__
#define PREFIX "static const char *hsm::internal::GetTypeNameHelper<"
#define SUFFIX_1 ">::Apply() [T = "
#define SUFFIX_2 "]"
#define NUM_TYPE_REPEATS 2
#elif defined(HSM_COMPILER_GCC)
// e.g.: static const char* hsm::internal::GetTypeNameHelper<T>::Apply() [with T = HeroStates::Alive]
#define FUNCTION_MACRO __PRETTY_FUNCTION__
#define PREFIX "static const char* hsm::internal::GetTypeNameHelper<T>::Apply() [with T = "
#define SUFFIX_1 "]"
#define SUFFIX_2 ""
#define NUM_TYPE_REPEATS 1
#else
#error "Implement for current compiler"
#endif
const size_t funcNameLength = sizeof(FUNCTION_MACRO) - 1u;
const size_t prefixLength = sizeof(PREFIX) - 1u;
const size_t suffixLength = sizeof(SUFFIX_1) - 1u + sizeof(SUFFIX_2) - 1u;
//static_assert((funcNameLength - (prefixLength + suffixLength)) % NUM_TYPE_REPEATS == 0, "NUM_TYPE_REPEATS is probably incorrect");
const size_t typeLength = (funcNameLength - (prefixLength + suffixLength)) / NUM_TYPE_REPEATS;

static char typeName[typeLength + 1u];
memcpy(&typeName[0], FUNCTION_MACRO + prefixLength, typeLength);
return typeName;

#undef FUNCTION_MACRO
#undef PREFIX
#undef SUFFIX_1
#undef SUFFIX_2
#undef NUM_TYPE_REPEATS
}
};

template <typename T>
const char* GetTypeName()
{
static const char* typeName = GetTypeNameHelper<T>::Apply();
return typeName;
}
}
const hsm_char* mStateName;
};

typedef const StateTypeIdStorage& StateTypeId;
// We need a comparable wrapper around the type name. We can't just compare const char* pointers
// because GetTypeName<T> may return two different strings for the same T in different translation units.
struct StateTypeId
{
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;
};

template <typename StateType>
const StateTypeId& GetStateType()
{
static StateTypeId stateTypeId = internal::GetTypeName<StateType>();
return stateTypeId;
}

template <typename StateType>
StateTypeId GetStateType()
{
return StateType::GetStaticStateType();
}
template <typename StateType>
const char* GetStateName()
{
return internal::GetTypeName<StateType>();
}

} // namespace hsm

// Must use this macro in every State to add RTTI support.
#define DEFINE_HSM_STATE(__StateName__) \
static hsm::StateTypeId GetStaticStateType() { static hsm::StateTypeIdStorage sStateTypeId(HSM_TEXT(#__StateName__)); return sStateTypeId; } \
virtual hsm::StateTypeId DoGetStateType() const { return GetStaticStateType(); } \
virtual const hsm_char* DoGetStateDebugName() const { return HSM_TEXT(#__StateName__); }
#endif // !HSM_USE_CPP_RTTI

#endif // !HSM_CPP_RTTI

#ifdef _MSC_VER
#ifdef HSM_COMPILER_MSC
#pragma endregion "RTTI"
#endif

#ifdef _MSC_VER
#ifdef HSM_COMPILER_MSC
#pragma region "Utils"
#endif
///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -335,11 +410,11 @@ class IntrusivePtrClient
} // namespace util
} // namespace hsm

#ifdef _MSC_VER
#ifdef HSM_COMPILER_MSC
#pragma endregion "Utils"
#endif

#ifdef _MSC_VER
#ifdef HSM_COMPILER_MSC
#pragma region "Transition"
#endif
///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -367,6 +442,7 @@ const StateFactory& GetStateFactory();
struct StateFactory
{
virtual StateTypeId GetStateType() const = 0;
virtual const char* GetStateName() const = 0;
virtual State* AllocateState() const = 0;
virtual void InvokeStateOnEnter(State* state, const StateArgs* stateArgs) const = 0;
};
@@ -417,6 +493,11 @@ struct ConcreteStateFactory : StateFactory
return hsm::GetStateType<TargetState>();
}

virtual const char* GetStateName() const
{
return hsm::GetStateName<TargetState>();
}

virtual State* AllocateState() const
{
return HSM_NEW TargetState();
@@ -601,11 +682,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
///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -677,7 +758,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
@@ -800,17 +881,7 @@ struct State
const hsm::StateFactory& 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 <typename T>
StateValue<T>* FindStateValueInResetterList(StateValue<T>& stateValue)
@@ -846,7 +917,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;
};

@@ -888,11 +959,11 @@ void ConcreteStateFactory<TargetState>::InvokeStateOnEnter(State* state, const S

} // namespace hsm

#ifdef _MSC_VER
#ifdef HSM_COMPILER_MSC
#pragma endregion "State"
#endif

#ifdef _MSC_VER
#ifdef HSM_COMPILER_MSC
#pragma region "StateMachine"
#endif
///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -1176,19 +1247,19 @@ 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->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;
}

@@ -1498,7 +1569,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

0 comments on commit f5c3b9a

Please sign in to comment.
You can’t perform that action at this time.