如何将字符串文字映射到 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;
}

如何获取要传递给函数模板的组件名称?我需要某种反射系统吗?

我能想到一些方法来解决你的问题。

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

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

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

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

  1. 您有 C++ 组件类型的动态 st。

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

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

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