为实体组件系统实现多态组件
Implementing polymorphic components for entity-component system
没有多态性
我实现了一个使用模板获取组件的实体组件系统。为每种类型生成一个 id。对于给定类型 T,函数 size_t GetComponentTypeId<T>()
将始终 return 相同的 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
添加多态性
现在我想向我的 classes 添加多态行为。例如。可能有一个 SpriteRenderComponent 继承自 RenderComponent(它当然继承了 Component)。 RenderComponent 将有一个在 SpriteRenderComponent 中实现的虚拟绘制方法。我希望能够仅添加 sprite 组件,并且仍然能够通过在已添加 sprite 组件的实体上调用 entity.GetComponent<RenderComponent>()
来获取对 renderComponent 的引用。在 returned 渲染组件引用上调用 draw 方法应该调用 SpriteRenderComponent.draw()。此外,我不应该能够添加任何其他继承自渲染组件的组件。
我的一些想法
我认为,基本的解决方案是为两个 id 添加一个 SpriteRenderComponent 的一个实例的指针; RenderComponent 和 SpriteRenderComponent。这也会阻止用户添加多个继承自 RenderComponent 的组件。组件本身只会被添加到 componentList 一次,因此每帧只会更新一次(根据需要)
问题:使其类型安全
我的问题是我正在努力使其类型安全。我还想包括某种检查,以确保 SpriteRenderComponent 实际上继承自 RenderComponent。我最喜欢的解决方案是“自动添加获取超级 class 的 ID 并为它们添加组件指针的解决方案。我对这种元编程很陌生(可能是用错了词)所以非常感谢帮助。
更新
我找到的一个可能的解决方案是向实体 class 添加一个 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));
}
我想它有点类型安全,但对我来说它有一个主要问题。它要求我每次都调用此函数 我添加了一个具有多态行为的组件。尽管这是一个解决方案(有点),但我更喜欢静态方式来指定此行为。
关于确保它继承自的部分:
template<typename T>
struct Foo {
static_assert(is_base_of<Base, T>::value, "T must inherit from Base");
};
可能会帮助你;至于其他问题;我将需要更多时间,因为我很快就要离开了……我稍后会在有机会更新此答案时再回来讨论这个问题。
编辑 - 添加了一些额外的 classes 并展示了它们的用途。
我有时间做某事;我不确定这是否是您要找的东西;但这是我以前用过的 storage-manager
类型系统。它确实支持 classes 的多态行为。所以也许这个结构可以帮助你。
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 - 我不会更改此 class 的代码来替换它的名称。看了一会儿之后;很明显,FooStore
或 Storage
之类的名称更适合此 class。除了从其成员容器中添加和删除对象外,它实际上不管理任何其他内容。如果您决定添加更多功能,而不仅仅是添加和删除对象,则可以保留其名称。
#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 );
}
这是一种动态存储项目的好方法,是的,您可以看到我在 main 中的指针上使用 new
,但您永远不会看到我使用 delete
。这是因为一旦我们将该指针添加到管理器 class,它就会接管并为我们处理所有内存,因为它将把它们变成 shared_ptr<T>.
这个管理器 class 也支持多态行为.这只是一个基本的 shell 或结构。
然后从这里开始。您可以编写另一个 class 来保存指向此存储或管理器 class 的指针,然后它将从这些容器中添加和删除项目。另一个class负责查找这个存储中的对象,然后调用内部存储对象的方法;或者您可以将所有这些功能直接添加到这个 class 中。我有点喜欢尝试将事物的存储与事物的实现分开。我希望这个结构能帮助你,或者给你一些想法来解决问题。您可以看到我确实在 class 中使用了函数模板来访问特定派生 foo 的特定映射。
您应该能够将 classes 的概念整合到 is_derived_from
以及检查特定项目是否已经存在,如果不存在则不添加它。最后注意:您还可以将存储分为两种类型,其中一个容器将能够添加多个组件,这些组件可以每帧渲染多次,而另一个容器可能是限制性的。不确定你能从中获得什么样的好处,也许在粒子发生器或引擎系统中,但如果你需要的话,可以灵活地做到这一点。
你只需要让 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 >
的类型。
您的代码现在可以正常工作了。
没有多态性
我实现了一个使用模板获取组件的实体组件系统。为每种类型生成一个 id。对于给定类型 T,函数 size_t GetComponentTypeId<T>()
将始终 return 相同的 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
添加多态性
现在我想向我的 classes 添加多态行为。例如。可能有一个 SpriteRenderComponent 继承自 RenderComponent(它当然继承了 Component)。 RenderComponent 将有一个在 SpriteRenderComponent 中实现的虚拟绘制方法。我希望能够仅添加 sprite 组件,并且仍然能够通过在已添加 sprite 组件的实体上调用 entity.GetComponent<RenderComponent>()
来获取对 renderComponent 的引用。在 returned 渲染组件引用上调用 draw 方法应该调用 SpriteRenderComponent.draw()。此外,我不应该能够添加任何其他继承自渲染组件的组件。
我的一些想法
我认为,基本的解决方案是为两个 id 添加一个 SpriteRenderComponent 的一个实例的指针; RenderComponent 和 SpriteRenderComponent。这也会阻止用户添加多个继承自 RenderComponent 的组件。组件本身只会被添加到 componentList 一次,因此每帧只会更新一次(根据需要)
问题:使其类型安全
我的问题是我正在努力使其类型安全。我还想包括某种检查,以确保 SpriteRenderComponent 实际上继承自 RenderComponent。我最喜欢的解决方案是“自动添加获取超级 class 的 ID 并为它们添加组件指针的解决方案。我对这种元编程很陌生(可能是用错了词)所以非常感谢帮助。
更新
我找到的一个可能的解决方案是向实体 class 添加一个 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));
}
我想它有点类型安全,但对我来说它有一个主要问题。它要求我每次都调用此函数 我添加了一个具有多态行为的组件。尽管这是一个解决方案(有点),但我更喜欢静态方式来指定此行为。
关于确保它继承自的部分:
template<typename T>
struct Foo {
static_assert(is_base_of<Base, T>::value, "T must inherit from Base");
};
可能会帮助你;至于其他问题;我将需要更多时间,因为我很快就要离开了……我稍后会在有机会更新此答案时再回来讨论这个问题。
编辑 - 添加了一些额外的 classes 并展示了它们的用途。
我有时间做某事;我不确定这是否是您要找的东西;但这是我以前用过的 storage-manager
类型系统。它确实支持 classes 的多态行为。所以也许这个结构可以帮助你。
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 - 我不会更改此 class 的代码来替换它的名称。看了一会儿之后;很明显,FooStore
或 Storage
之类的名称更适合此 class。除了从其成员容器中添加和删除对象外,它实际上不管理任何其他内容。如果您决定添加更多功能,而不仅仅是添加和删除对象,则可以保留其名称。
#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 );
}
这是一种动态存储项目的好方法,是的,您可以看到我在 main 中的指针上使用 new
,但您永远不会看到我使用 delete
。这是因为一旦我们将该指针添加到管理器 class,它就会接管并为我们处理所有内存,因为它将把它们变成 shared_ptr<T>.
这个管理器 class 也支持多态行为.这只是一个基本的 shell 或结构。
然后从这里开始。您可以编写另一个 class 来保存指向此存储或管理器 class 的指针,然后它将从这些容器中添加和删除项目。另一个class负责查找这个存储中的对象,然后调用内部存储对象的方法;或者您可以将所有这些功能直接添加到这个 class 中。我有点喜欢尝试将事物的存储与事物的实现分开。我希望这个结构能帮助你,或者给你一些想法来解决问题。您可以看到我确实在 class 中使用了函数模板来访问特定派生 foo 的特定映射。
您应该能够将 classes 的概念整合到 is_derived_from
以及检查特定项目是否已经存在,如果不存在则不添加它。最后注意:您还可以将存储分为两种类型,其中一个容器将能够添加多个组件,这些组件可以每帧渲染多次,而另一个容器可能是限制性的。不确定你能从中获得什么样的好处,也许在粒子发生器或引擎系统中,但如果你需要的话,可以灵活地做到这一点。
你只需要让 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 >
的类型。
您的代码现在可以正常工作了。