没有多态性
我已经实现了一个实体组件系统,它使用模板来获取组件。为每种类型生成一个id。对于给定类型T,函数size_t GetComponentTypeId<T>()
将始终返回相同的id。
为了更好地理解,这里添加组件的功能
template <typename TComponent, typename... TArguments>
inline TComponent & Entity::AddComponent(TArguments&&... arguments)
{
// Check whether the component doesn't already exist
assert(componentBitSet[detail::GetComponentTypeID<TComponent>()] == false && "The component already exists");
assert(componentArray[detail::GetComponentTypeID<TComponent>()] == nullptr && "The component already exists");
TComponent * c = new TComponent(*this, std::forward<TArguments>(arguments)...);
Component::UPtr uPtr{ c };
componentList.emplace_back(std::move(uPtr));
// set the component * in the array
componentArray[detail::GetComponentTypeID<TComponent>()] = c;
// set the according component flag to true
componentBitSet[detail::GetComponentTypeID<TComponent>()] = true;
return *c;
}
这里是获取组件的功能
template<typename TComponent>
inline TComponent & Entity::GetComponent() const
{
Component * component = componentArray[getComponentTypeID<TComponent>()];
if (component == nullptr)
throw std::runtime_error("Entity: This entity does not have the requested component");
return *static_cast<TComponent*>(component);
}
这里没什么特别的
如果是GetComponentTypeID()方法,那么我当前的实现:
namespace detail
{
typedef std::size_t ComponentTypeID;
/// @brief Returns a unique number (for each function call) of type std::size_t
inline ComponentTypeID GetComponentID() noexcept
{
// This will only be initialised once
static ComponentTypeID lastId = 0;
// After the first initialisation a new number will be returned for every function call
return lastId++;
}
/// @brief Returns a unique number (of type std::size_t) for each type T
/// @details Each component type will have its own unique id.
/// The id will be the same for every instance of that type
/// @tparam T The type for which the id is generated
template <typename T>
inline ComponentTypeID GetComponentTypeID() noexcept
{
// There will be only one static variable for each template type
static ComponentTypeID typeId = GetComponentID();
return typeId;
}
} // namespace detail
添加多态性
现在我想为我的类添加多态行为。例如。可能有一个继承自RenderComponent的SpriteRenderComponent(当然它继承了Component)。 RenderComponent将具有在SpriteRenderComponent中实现的虚拟绘制方法。我希望能够只添加sprite组件,并且仍然能够通过调用添加了sprite组件的实体上的entity.GetComponent<RenderComponent>()
来获取对renderComponent的引用。在返回的渲染组件引用上调用draw方法应该调用SpriteRenderComponent.draw()。此外,我不能添加从render组件继承的任何其他组件。
我的一些想法
我认为,基本的解决方案是为两个id添加一个SpriteRenderComponent实例的指针; RenderComponent和SpriteRenderComponent。这也会阻止用户添加从RenderComponent继承的多个组件。组件本身只会添加一次到componentList,因此每帧只更新一次(如所需)
问题:使其类型安全
我的问题是,我正在努力使它变得类型安全。另外,我想要包含一些检查,以确保SpriteRenderComponent实际上继承自RenderComponent。我最喜欢的解决方案是自动添加的解决方案获取超类的ID并为它们添加组件指针。我对这种元编程很新(也许是错误的单词),所以非常感谢帮助。
更新
我发现一个可能的解决方案是向实体类添加AddPolymorphism<class TDerivedComponent, class TBaseComponent>()
方法。以下是实施:
template<class TDerivedComponent, class TBaseComponent>
inline void Entity::AddPolymorphism()
{
// Needed since std::is_base_of<T, T> == true
static_assert(std::is_base_of<Component, TBaseComponent>::value, "Entity: TBaseComponent must inherit from Component");
static_assert(std::is_same<Component, TBaseComponent>::value == false, "Entity: TBaseComponent must inherit from Component");
static_assert(std::is_base_of<TBaseComponent, TDerivedComponent>::value, "Entity: TDerivedComponent must inherit from TBaseComponent");
static_assert(std::is_same<Component, TBaseComponent>::value == false, "Entity: TBaseComponent must inherit from Component");
assert(this->HasComponent<TDerivedComponent>() && "Entity: The entity must have the derived component");
auto derivedComponentPtr = componentDictionary.find(detail::GetComponentTypeID<TDerivedComponent>())->second.lock();
componentDictionary.insert(std::make_pair(detail::GetComponentTypeID<TBaseComponent>(), derivedComponentPtr));
}
我猜它有点类型安全,但对我来说它有一个主要问题。它要求我每次都调用这个函数 我添加了一个具有多态行为的组件。虽然这是一种解决方案(有点),但我更倾向于采用静态方式来指定这种行为。
答案 0 :(得分:1)
关于确保它继承自:
的部分template<typename T>
struct Foo {
static_assert(is_base_of<Base, T>::value, "T must inherit from Base");
};
可能会帮助你;至于其他问题;我需要更多的时间,因为我必须尽快离开...当我有机会更新这个答案时,我将在稍后回来。
我有时间做某事;我不确定这是不是你要找的东西;但这是我之前使用的storage-manager
类型系统。它确实支持类的多态行为。所以也许这个结构会帮助你。
<强>的main.cpp 强>
#include <iostream>
#include <string>
#include <memory>
#include "FooManager.h"
#include "DerivedFoos.h"
int main() {
try {
std::unique_ptr<FooManager> pFooManager;
pFooManager.reset( new FooManager() );
for ( unsigned i = 0; i < 10; i++ ) {
DerivedA* pA = new DerivedA();
DerivedB* pB = new DerivedB();
pFooManager->add( pA, FOO_A );
pFooManager->add( pB, FOO_B );
}
pFooManager.reset();
} catch ( std::exception& e ) {
std::cout << e.what() << std::endl;
std::cout << "\nPress any key to quit.\n";
std::cin.get();
return -1;
} catch ( std::string str ) {
std::cout << str << std::endl;
std::cout << "\nPress any key to quit.\n";
std::cin.get();
return -1;
} catch ( ... ) {
std::cout << __FUNCTION__ << " caught unknown exception." << std::endl;
std::cout << "\nPress any key to quit.\n";
std::cin.get();
return -1;
}
std::cout << "\nPress any key to quit.\n";
std::cin.get();
return 0;
}
<强> FooBase.h 强>
#ifndef FOO_BASE_H
#define FOO_BASE_H
enum FooTypes {
FOO_A,
FOO_B,
FOO_UNKNOWN // MUST BE LAST!!!
};
class FooBase {
protected:
std::string _nameAndId;
private:
std::string _id;
static int _baseCounter;
public:
std::string idOfBase();
virtual std::string idOf() const = 0;
protected:
FooBase();
};
#endif // !FOO_BASE_H
<强> FooBase.cpp 强>
#include "FooBase.h"
#include <iostream>
#include <string>
int FooBase::_baseCounter = 0;
FooBase::FooBase() {
_id = std::string( __FUNCTION__ ) + std::to_string( ++_baseCounter );
std::cout << _id << " was created." << std::endl;
}
std::string FooBase::idOfBase() {
return _id;
}
std::string FooBase::idOf() const {
return "";
} // empty
<强> DerivedFoos.h 强>
#ifndef DERIVED_FOOS_H
#define DERIVED_FOOS_H
#include "FooBase.h"
class DerivedA : public FooBase {
private:
static int _derivedCounter;
public:
DerivedA();
std::string idOf() const override;
};
class DerivedB : public FooBase {
private:
static int _derivedCounter;
public:
DerivedB();
std::string idOf() const override;
};
#endif // !DERIVED_FOOS_H
<强> DerivedFoos.cpp 强>
#include "DerivedFoos.h"
#include <iostream>
#include <string>
int DerivedA::_derivedCounter = 0;
int DerivedB::_derivedCounter = 0;
DerivedA::DerivedA() : FooBase() {
_nameAndId = std::string( __FUNCTION__ ) + std::to_string( ++DerivedA::_derivedCounter );
std::cout << _nameAndId << " was created." << std::endl;
}
std::string DerivedA::idOf() const {
return _nameAndId;
}
DerivedB::DerivedB() : FooBase() {
_nameAndId = std::string( __FUNCTION__ ) + std::to_string( ++DerivedB::_derivedCounter );
std::cout << _nameAndId << " was created." << std::endl;
}
std::string DerivedB::idOf() const {
return _nameAndId;
}
FooManager.h - 我不会更改此类的代码来替换其名称。看了一会儿之后;显而易见的是FooStore
或Storage
等类似于此类的更合适的名称。除了从其成员容器中添加和删除对象之外,它并没有真正管理任何其他内容。如果您决定添加的功能不仅仅是添加和删除对象,那么您可以按原样保留其名称。
#ifndef FOO_MANAGER_H
#define FOO_MANAGER_H
class FooBase;
class DerivedA;
class DerivedB;
enum FooTypes;
class FooManager final {
private:
static bool _alreadyExists;
typedef std::unordered_map<std::string, std::shared_ptr<FooBase>> MapFoos;
MapFoos _idsA;
MapFoos _idsB;
std::vector<std::string> _foosForRemoval;
public:
FooManager();
~FooManager();
// Foo Objects
FooBase* getFoo( const std::string& id, FooTypes type ) const;
void add( FooBase* foo, FooTypes type );
bool removeFoo( const std::string& id );
template<typename T>
bool removeFoo( T* pFoo );
void markFooForRemoval( const std::string& id );
private:
FooBase* getFoo( const std::string& id, const MapFoos& fooMap ) const;
void add( FooBase* pFoo, MapFoos& fooMap );
bool removeFoo( const std::string& strId, MapFoos& fooMap );
};
template<typename T>
inline bool FooManager::removeFoo( T* pFoo ) {
return false;
}
#endif // !FOO_MANAGER_H
<强> FooManager.cpp 强>
#include "FooManager.h"
#include "DerivedFoos.h"
#include <iostream>
#include <sstream>
#include <string>
#include <unordered_map>
#include <memory>
bool FooManager::_alreadyExists = false;
FooManager::FooManager() {
// First check if no other instance is created.
if ( _alreadyExists ) {
std::ostringstream strStream;
strStream << "Failed to create " << __FUNCTION__ << " as it was already created." << std::endl;
throw strStream.str();
}
// Make sure this is last
_alreadyExists = true;
std::cout << __FUNCTION__ + std::string( " was created successfully." ) << std::endl;
}
FooManager::~FooManager() {
// If we are destroying make sure to reset flag
// So it can be constructed again.
_idsA.clear();
_idsB.clear();
_alreadyExists = false;
std::cout << __FUNCTION__ + std::string( " was destroyed successfully." ) << std::endl;
}
FooBase* FooManager::getFoo( const std::string& id, FooTypes type ) const {
switch ( type ) {
case FOO_A: {
return getFoo( id, _idsA );
}
case FOO_B: {
return getFoo( id, _idsB );
}
default: {
std::ostringstream strStream;
strStream << __FUNCTION__ << " Unrecognized FooType = " << type;
throw strStream.str();
}
}
return nullptr;
}
FooBase* FooManager::getFoo( const std::string& id, const MapFoos& fooMap ) const {
MapFoos::const_iterator itFoo = fooMap.find( id );
if ( itFoo == fooMap.cend() ) {
return nullptr;
}
return itFoo->second.get();
}
void FooManager::add( FooBase* pFoo, FooTypes type ) {
// first check to see foo is valid
if ( nullptr == pFoo ) {
std::ostringstream strStream;
strStream << __FUNCTION__ + std::string( " pFoo == nullptr passed in" );
}
// Make Sure Name Is Unique Across All Foo Types
for ( int i = 0; i < FOO_UNKNOWN; ++i ) {
if ( getFoo( pFoo->idOf(), (FooTypes)i ) != nullptr ) {
std::ostringstream strStream;
strStream << __FUNCTION__ << " attempting to store " << pFoo->idOf() << " multiple times" << std::endl;
throw strStream.str();
}
}
switch ( type ) {
case FOO_A: {
add( pFoo, _idsA );
break;
}
case FOO_B: {
add( pFoo, _idsB );
break;
}
default: {
std::ostringstream strStream;
strStream << __FUNCTION__ << " uncrecognized FooType = " << type;
}
}
}
void FooManager::add( FooBase* pFoo, MapFoos& fooMap ) {
fooMap.insert( MapFoos::value_type( pFoo->idOf(), std::shared_ptr<FooBase>( pFoo ) ) );
}
template<>
bool FooManager::removeFoo( DerivedA* pFoo ) {
return removeFoo( pFoo->idOf(), _idsA );
}
template<>
bool FooManager::removeFoo( DerivedB* pFoo ) {
return removeFoo( pFoo->idOf(), _idsB );
}
bool FooManager::removeFoo( const std::string& id ) {
// Find which type this Foo is in
for ( int i = 0; i < FOO_UNKNOWN; ++i ) {
FooBase* pFoo = getFoo( id, (FooTypes)i );
if ( pFoo != nullptr ) {
// Found It
switch ( static_cast<FooTypes>(i) ) {
case FOO_A: {
return removeFoo( pFoo->idOf(), _idsA );
}
case FOO_B: {
return removeFoo( pFoo->idOf(), _idsB );
}
default: {
std::ostringstream strStream;
strStream << __FUNCTION__ << " uncrecognized FooType = " << i;
throw strStream.str();
}
}
}
}
std::ostringstream strStream;
strStream << __FUNCTION__ << " failed. " << id << " was not found in FooManager";
// don't throw just display message (typically write to log file).
std::cout << strStream.str() << std::endl;
return false;
}
bool FooManager::removeFoo( const std::string& id, MapFoos& fooMap ) {
MapFoos::iterator itFoo = fooMap.find( id );
if ( itFoo == fooMap.end() ) {
std::ostringstream strStream;
strStream << __FUNCTION__ << " failed. " << id << " was not found in AssetStorage";
// don't throw just display message (typically write to log file).
std::cout << strStream.str() << std::endl;
return false;
} else {
// do what ever from Foo's functions to clean up its internals
// itFoo->second.get()->cleanUp(); // etc.
fooMap.erase( itFoo );
// When the above foo was deleted, there might have been some children
// that were also marked for removal. We can remove them here.
for ( unsigned i = 0; i < _foosForRemoval.size(); ++i ) {
itFoo = _idsB.find( _foosForRemoval[i] );
if ( itFoo != _idsB.end() ) {
// Remove this Foo
// do what ever from Foo's functions to clean up its internals.
// itFoo->second.get()->cleanUp(); // etc.
_idsB.erase( itFoo );
} else {
std::ostringstream strStream;
strStream << __FUNCTION__ << " failed to find " << _foosForRemoval[i] << " for removal from the _idsB";
// don't throw just display message (typically write to log file).
std::cout << strStream.str() << std::endl;
}
}
_foosForRemoval.clear();
return true;
}
}
void FooManager::markFooForRemoval( const std::string& id ) {
_foosForRemoval.push_back( id );
}
这是一种动态存储项目的好方法,是的,您可以看到我在主要指针上使用new
,但您从未看到我使用delete
。这是因为一旦我们将指针添加到管理器类,它就会接管并处理我们的所有内存,因为它会将它们转换为shared_ptr<T>.
这个管理器类也支持多态行为。这只是一个基本的shell或结构。
然后从这里开始。您可以编写另一个包含指向此存储或管理器类的指针的类,然后添加和删除这些容器中的项。另一个类负责查找此存储中的对象,然后调用内部存储对象的方法;或者您可以直接将所有功能添加到此类中。我有点想尝试将事物的存储与事物的实现分开。我希望这个结构可以帮助你,或者给你一些想法。您可以看到我确实在此类中使用了函数模板来访问特定派生foos的特定映射。
您应该能够将is_derived_from
的概念集成到上述类中,并检查特定项目是否已存在以及是否不添加。最后注意:您还可以将存储分为两种类型,其中一个容器可以添加多个组件,每个帧可以多次渲染,而另一个容器可以是限制性的。不知道你可以获得什么样的好处,可能是在粒子发生器或发动机系统中,但如果你需要,可以灵活地做到这一点。
答案 1 :(得分:1)
你只需要让detail::GetComponentTypeID<T>()
更聪明。
实际上,您有一个组件类型列表。
template<class...>
struct type_list_t {};
using ComponentList = type_list_t<RenderComponent, PhysicsComponent, CharmComponent>;
此列表确定指针和位标志数组的长度。将此列表明确放在每个人都知道的着名位置。
是的,这意味着如果它发生变化你必须重建。坚韧。
现在你必须改进detail::GetComponentTypeID<T>()
。让constexpr
或模板元编程为ComponentList
搜索通过std::is_base_of< ListElement, T >
的第一种类型。
现在你的代码就像写的一样。