Architecture 什么是';规范的';还是将组件与ECS中的特定实体关联的最佳方式?

Architecture 什么是';规范的';还是将组件与ECS中的特定实体关联的最佳方式?,architecture,game-engine,entity-component-system,Architecture,Game Engine,Entity Component System,关于如何在实体组件系统中存储信息,我已经看到了大量不同的、相互矛盾的建议——有些建议鼓吹“纯度”或缓存优化,但没有太多解释。作为一般性总结: 实体列表,将组件存储在其结构中(例如,C++中的某种无序映射)。系统跟踪其自己的列表,其中当前活动实体拥有其操作的组件 与1相同,只是系统在所有实体上全局迭代并检查它们包含哪些组件 每种类型的组件都有一个单独的列表,并且没有实际存储的“实体”列表——系统迭代其相关组件,并且必须通过连接它们的某个唯一ID以某种方式找到属于同一实体的其他关联组件。将组件保存在

关于如何在实体组件系统中存储信息,我已经看到了大量不同的、相互矛盾的建议——有些建议鼓吹“纯度”或缓存优化,但没有太多解释。作为一般性总结:

  • 实体列表,将组件存储在其结构中(例如,C++中的某种
    无序映射
    )。系统跟踪其自己的列表,其中当前活动实体拥有其操作的组件

  • 与1相同,只是系统在所有实体上全局迭代并检查它们包含哪些组件

  • 每种类型的组件都有一个单独的列表,并且没有实际存储的“实体”列表——系统迭代其相关组件,并且必须通过连接它们的某个唯一ID以某种方式找到属于同一实体的其他关联组件。将组件保存在这样一个列表中是受支持的,因为这样可以提高缓存位置(尽管我不知道怎么做,因为您仍将搜索几个大列表以查找特定实体上的相关组件),而没有实际的“实体”类型则被认为是“纯”ECS的标志

  • 每个组件类型都有自己的全局容器/列表,但仍然有一个实体结构列表,用于跟踪哪些组件属于某个特定实体。系统的行为如1或2所示

  • 我还发现一些人支持“每个系统一个组件类型”——这将简化system 3的一些挑战,但总体上没有什么意义


    因此,我的问题是——消除各种不同实现的噪音,其中有哪一种是制造ECS的“理想”或“规范”方法?很难分析每种设计的优点和缺点以及它们的许多不同变体。我通常按照上面列表中的“1”实现它们,这被证明是方便的,但不一定是最优的。

    不幸的是,我认为我不能提供比“视情况而定”更好的服务,因为任何接近理想主义的东西都与特定引擎的设计要求及其程序员的敏感性有关。不过,我或许可以就您列出的各个部分提供一些想法

  • 实体列表,将组件存储在其结构中(例如,C++中的某种无序_映射)。系统跟踪其自己的列表,其中当前活动实体拥有其操作的组件
  • 如果您支持ECS架构设计是为了提高效率,而不仅仅是灵活性(尽管支持ECS只是为了灵活性没有什么错),那么将组件列表直接存储在单个实体内部,而不是并行存储在外部(并且只有一个列表存储同一类型的所有组件)由于超出每个实体组件查找范围的原因,通常会与该目标背道而驰

    ECS的过程(有时是功能)范式经常与面向对象编程形成对比。这包括表现。OOP在最关键的执行路径中的一个基本性能缺点是,面向对象的设计倾向于交错/捆绑数据,而不是基于最佳内存访问模式,而是基于人为因素和SE因素,以便封装和维护不变量。ECS与此相反,它立即打破封装,将逻辑(系统)与数据(组件)分离。这样的设计立即为更优化的数据表示、内存布局和优化(如热/冷拆分)打开了空间,但如果您最终直接在实体内存储/捆绑/交错所有此类数据,则不会这样做

    因此,如果我冒险尝试任何类似canonical的东西,并充分利用ECS可能提供的全部好处,它将不会将组件数据直接存储在实体内部。它将把它们存储在外部,这样,例如,很少访问的数据就可以从通常访问的数据中分离出来,而不是交错在一起,浪费地加载到关键路径的缓存线中

  • 与1相同,只是系统在所有实体上全局迭代并检查它们包含哪些组件
  • 根据我的理解,这是1的要求。出于上述类似原因,这将是次优的

    每种类型的组件都有一个单独的列表,没有 实际存储的“实体”列表-系统迭代其相关 组件,并且必须以某种方式找到 通过连接它们的某个唯一ID属于同一实体。 将组件保存在这样一个列表中是支持的 改进缓存位置(尽管我不知道如何改进,因为您仍然需要 正在搜索多个大型列表,以查找某个计算机上的相关组件 特定实体),并且不具有实际的“实体”类型 应该是“纯粹”ECS的标志

    这通常需要有接近最佳参考位置的可能性。最简单的例子之一是热/冷场分裂。考虑这样一个例子:

    structfoo
    {
    //每一帧都被访问。
    int32_t x,y;
    //几乎从未访问过,而且只能通过UI访问。
    uint64_t id;
    };
    
    在这种情况下,通过加载此
    id
    字段,我们将在关键执行路径中浪费每个CPU缓存线的一半内存,该字段甚至不会与
    x
    y
    一起在这些路径中访问,从而依次遍历
    Foo
    数组。使用ECS,我们可以通过创建两个或多个单独的组件类型来存储它们和两个或多个组件列表,从而将通常访问的字段
    x
    y
    与很少访问的字段
    id
    分开