如何将字符串文字映射到 C++ 中的类型
How to map string literals to types in C++
我正在编写一个小型 2D 游戏,目前正在为其添加脚本功能(使用 Lua 或 Python),我偶然发现了这个问题(我认为这会导致我要为我的游戏实现某种反射系统):
我正在使用实体组件系统模式,实体的定义由脚本(Lua table 或 Python 字典)提供,因此无论何时我想构建一个实体 I 运行 脚本:
player = {
transformComponent = {
position = {1.0, 2.0, 0.0},
scale = {1.0, 2.0, 1.0}
},
spriteComponent = {
fileName = 'imageFile.png',
numRows = 4,
numCols = 6
}
}
等等。
在 EntityFactory 中,我有一个 EntityFactoryFunctions 映射,以实体名称为键(例如 'Player'),当我需要构造这样的命名实体时,我会调用它们。
现在,每个工厂函数将读取实体的 table (dict) 并获取它需要添加到实体的所有组件的名称。
Entity *CreateEntity(const std::string entityType) // table / dictionary name in script
{
Entity *newEntity = Scene::GetInstance().AddEntity();
return mEntityFactories[entityType](newEntity);
}
typedef Entity *(*EntityFactoryFunction)(Entity*);
std::map<std::string, EntityFactoryFunction> mEntityFactories;
问题是,我的 ECS 使用类型为 enity.AddComponent():
的函数
Entity *PlayerFactory(Entity *entity)
{
// read components from Lua table / Python dictionary
// get strings of components' names and store them into vector
Vector<std::string> componentNames;
// create components and add to entity
for (const auto &componentName : componentNames)
{
Component *component = mComponentFactories[componentName](/* pass a reference to component table / dictionary */);
entity->AddComponent<......>(component); // I must know the component type
}
return entity;
}
如何获取要传递给函数模板的组件名称?我需要某种反射系统吗?
我能想到一些方法来解决你的问题。
- 不同类型的组件不是不同的C++类型。
在这种情况下,您的组件只是属性包。代码会查看您拥有哪些包并表现不同。
- 您有一组固定的 C++ 组件类型。
在这里,脚本命名了各种组件。这些是 C++ 类型。组件名称和类型之间的映射存储在 C++ 中。该关联可能与一两个硬编码的 switch 语句一样简单。
- 您有 C++ 组件类型的动态 st。
为了添加更多的组件类型,您可以加载另一个动态库,它会注册新的组件类型以及组件名称和类型之间的关联。
- 更疯狂的东西。
比如,您发布了 C++ 编译器,它可以动态构建组件类型并动态加载它们。或者,您编写自己的语言,您的 C++ 代码实际上只是它的解释器。
我会排除#4。
现在,在情况 #1 中,您无事可做。
对于第 2/3 种情况,您仍然需要将该字符串映射到一个类型。
最简单的基于#2 的方法是一堆硬编码的 switch 语句,这些语句采用您的类型字符串并编写处理具体类型的自定义代码。这很快,但不能很好地扩展。这是方案(a)。
另一个步骤是抽象 switch 语句并让它在多个地方使用。将此解决方案称为 (b)。
另一种选择是将整个 type 视为对象本身;您编写了一个描述您的 类 的元类,并构建了一个从您的字符串到元类的映射。元类本身对于所有 类 都是相同的类型。将此解决方案称为 (c).
我认为 (a) 很简单,但很无聊。你真的做了一个
if (componentName=="bob") {
/* code assuming the type is Bob */
} else if (componentName=="blue") {
...
(b)的例子:
template<class T>struct tag_t{using type=T;};
template<class Tag>using type_t = typename T::type;
template<class T>constexpr tag_t<T> tag={};
template<class...Ts>
using tags_t = std::variant<tag_t<Ts>...>;
namespace Components{
using ComponentTag = tags_t<Transform, Sprite, Physics>;
ComponentTag GetTagFromName(std::string_view str) {
if(str=="transformComponent") return tag<Transform>;
if(str=="spriteComponent") return tag<Sprite>;
// ...
}
}
现在我们得到:
// create components and add to entity
for (const auto &componentName : componentNames)
{
Component *component = mComponentFactories[componentName](/* pass a reference to component table / dictionary */);
auto tag = Components::GetTagFromName(componentName);
std::visit([&](auto tag) {
using Type = type_t<decltype(tag)>;
entity->AddComponent<Type>(component); // I must know the component type
}, tag);
}
在最终版本(c)中,我们可以做:
for (const auto &componentName : componentNames)
{
IMetaComponent* meta = mComponentMetaFactory[componentName];
Component *component = meta->Create(/* pass a reference to component table / dictionary */);
meta->Add(entity, component);
}
此处,IMetaComponent
为需要在需要知道类型的组件上完成的每个操作获取虚拟方法。
MetaComponent 实现本身可以使用模板编写 90% 以上的代码,但它有一个基础 IMetaComponent
不是模板。
(c) 有很多优点,比如扩展能力,以及对 MetaComponent
本身进行单元测试的能力。
(b) 的优点是一旦设置好,您只需编写代码来完成您需要完成的事情。它确实需要 c++17 or c++14 and boost 才能获得良好的变体和 lambda 语法。
我正在编写一个小型 2D 游戏,目前正在为其添加脚本功能(使用 Lua 或 Python),我偶然发现了这个问题(我认为这会导致我要为我的游戏实现某种反射系统):
我正在使用实体组件系统模式,实体的定义由脚本(Lua table 或 Python 字典)提供,因此无论何时我想构建一个实体 I 运行 脚本:
player = {
transformComponent = {
position = {1.0, 2.0, 0.0},
scale = {1.0, 2.0, 1.0}
},
spriteComponent = {
fileName = 'imageFile.png',
numRows = 4,
numCols = 6
}
}
等等。 在 EntityFactory 中,我有一个 EntityFactoryFunctions 映射,以实体名称为键(例如 'Player'),当我需要构造这样的命名实体时,我会调用它们。
现在,每个工厂函数将读取实体的 table (dict) 并获取它需要添加到实体的所有组件的名称。
Entity *CreateEntity(const std::string entityType) // table / dictionary name in script
{
Entity *newEntity = Scene::GetInstance().AddEntity();
return mEntityFactories[entityType](newEntity);
}
typedef Entity *(*EntityFactoryFunction)(Entity*);
std::map<std::string, EntityFactoryFunction> mEntityFactories;
问题是,我的 ECS 使用类型为 enity.AddComponent
Entity *PlayerFactory(Entity *entity)
{
// read components from Lua table / Python dictionary
// get strings of components' names and store them into vector
Vector<std::string> componentNames;
// create components and add to entity
for (const auto &componentName : componentNames)
{
Component *component = mComponentFactories[componentName](/* pass a reference to component table / dictionary */);
entity->AddComponent<......>(component); // I must know the component type
}
return entity;
}
如何获取要传递给函数模板的组件名称?我需要某种反射系统吗?
我能想到一些方法来解决你的问题。
- 不同类型的组件不是不同的C++类型。
在这种情况下,您的组件只是属性包。代码会查看您拥有哪些包并表现不同。
- 您有一组固定的 C++ 组件类型。
在这里,脚本命名了各种组件。这些是 C++ 类型。组件名称和类型之间的映射存储在 C++ 中。该关联可能与一两个硬编码的 switch 语句一样简单。
- 您有 C++ 组件类型的动态 st。
为了添加更多的组件类型,您可以加载另一个动态库,它会注册新的组件类型以及组件名称和类型之间的关联。
- 更疯狂的东西。
比如,您发布了 C++ 编译器,它可以动态构建组件类型并动态加载它们。或者,您编写自己的语言,您的 C++ 代码实际上只是它的解释器。
我会排除#4。
现在,在情况 #1 中,您无事可做。
对于第 2/3 种情况,您仍然需要将该字符串映射到一个类型。
最简单的基于#2 的方法是一堆硬编码的 switch 语句,这些语句采用您的类型字符串并编写处理具体类型的自定义代码。这很快,但不能很好地扩展。这是方案(a)。
另一个步骤是抽象 switch 语句并让它在多个地方使用。将此解决方案称为 (b)。
另一种选择是将整个 type 视为对象本身;您编写了一个描述您的 类 的元类,并构建了一个从您的字符串到元类的映射。元类本身对于所有 类 都是相同的类型。将此解决方案称为 (c).
我认为 (a) 很简单,但很无聊。你真的做了一个
if (componentName=="bob") {
/* code assuming the type is Bob */
} else if (componentName=="blue") {
...
(b)的例子:
template<class T>struct tag_t{using type=T;};
template<class Tag>using type_t = typename T::type;
template<class T>constexpr tag_t<T> tag={};
template<class...Ts>
using tags_t = std::variant<tag_t<Ts>...>;
namespace Components{
using ComponentTag = tags_t<Transform, Sprite, Physics>;
ComponentTag GetTagFromName(std::string_view str) {
if(str=="transformComponent") return tag<Transform>;
if(str=="spriteComponent") return tag<Sprite>;
// ...
}
}
现在我们得到:
// create components and add to entity
for (const auto &componentName : componentNames)
{
Component *component = mComponentFactories[componentName](/* pass a reference to component table / dictionary */);
auto tag = Components::GetTagFromName(componentName);
std::visit([&](auto tag) {
using Type = type_t<decltype(tag)>;
entity->AddComponent<Type>(component); // I must know the component type
}, tag);
}
在最终版本(c)中,我们可以做:
for (const auto &componentName : componentNames)
{
IMetaComponent* meta = mComponentMetaFactory[componentName];
Component *component = meta->Create(/* pass a reference to component table / dictionary */);
meta->Add(entity, component);
}
此处,IMetaComponent
为需要在需要知道类型的组件上完成的每个操作获取虚拟方法。
MetaComponent 实现本身可以使用模板编写 90% 以上的代码,但它有一个基础 IMetaComponent
不是模板。
(c) 有很多优点,比如扩展能力,以及对 MetaComponent
本身进行单元测试的能力。
(b) 的优点是一旦设置好,您只需编写代码来完成您需要完成的事情。它确实需要 c++17 or c++14 and boost 才能获得良好的变体和 lambda 语法。