【问题标题】:How to map string literals to types in C++如何将字符串文字映射到 C++ 中的类型
【发布时间】:2021-06-19 05:23:38
【问题描述】:

我正在编写一个小型 2D 游戏,我目前正在向它添加脚本功能(使用 Lua 或 Python),我偶然发现了这个问题(我认为这将导致我为我的游戏):

我正在使用实体组件系统模式,并且实体的定义由脚本(Lua 表或 Python 字典)提供,因此每当我想构建实体时,我都会运行脚本:

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 映射,以实体名称(例如“玩家”)为键,当我需要构造此类命名实体时调用它们。

现在,每个工厂函数都会读取实体的表(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++ 中,有一整套编码理念围绕着不带反射的操作而建立。
  • "如何从与其名称对应的字符串中获取对象的类型?" -- 写一个工厂方法。
  • @Luca 这一切都取决于 cpp 的版本。在 cpp20 中,这很容易做到。在早期版本中,将字符串映射到类型有点 hack

标签: c++ templates types reflection scripting


【解决方案1】:

我可以想出一些方法来解决您的问题。

  1. 不同类型的组件并不是不同的 C++ 类型。

在这种情况下,您的组件只是属性包。代码会查看您拥有哪些捆绑包并表现出不同的行为。

  1. 您在 C++ 中有一组固定的组件类型。

在这里,脚本命名各种组件。这些是 C++ 类型。组件名称和类型之间的映射存储在 C++ 中。关联可能就像一两个硬编码的 switch 语句一样简单。

  1. 您在 C++ 中有一个动态的组件类型。

为了添加更多的组件类型,您可以加载另一个动态库,该库注册新的组件类型以及组件名称和类型之间的关联。

  1. 更疯狂的事情。

就像,您发布的 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) 有很多优点,比如扩展能力,以及对MetaComponents 本身进行单元测试的能力。

(b) 的优势在于,一旦设置完成,您只需编写代码来完成您需要完成的事情。它确实需要 才能获得良好的变体和 lambda 语法。

【讨论】:

  • 好的,我想我需要一些时间来阅读并理解它,因为有些东西我有点过头了。值得深思,谢谢。
  • @Luca 基本上,C++ 没有反射,但您可以通过一些手动工作来实现它。这里有3种方法。你知道的第一个。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-09-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-11-26
  • 1970-01-01
相关资源
最近更新 更多