尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
Flecs 层级系统(Hierarchies)完全指南:ChildOf 与 Parent 两种存储、递归遍历与查询优化
游戏开发【免费下载链接】flecsA fast entity component system (ECS) for C C项目地址https://gitcode.com/gh_mirrors/fl/flecs点击查看免费下载层级结构是 Flecs 这个 C/C ECSEntity Component System引擎中最常用的组织手段之一。本指南以仓库中的 HierarchiesManual.md 为主体结合 Flecs 源码与官方示例系统讲解如何通过内建的(ChildOf, parent)关系对实体进行父子编排深入剖析OrderedChildren有序存储、以及ChildOf与Parent两种层级存储的实现原理与性能取舍并给出深度优先 / 广度优先遍历、命名空间解析与查询优化的完整实战方案。读完本文你将掌握在 Flecs 中构建场景树、Prefab 资源树与 UI 树并针对查询性能进行存储选型的完整能力。引言Flecs 层级结构的定位在 Flecs 中实体Entity可以通过内建的层级实现被组织成父/子层级结构。这种结构非常实用典型用途包括将实体组织成场景Scene将组件Component、系统System、观察器Observer、Prefab 组织成模块Module表达资源层级Asset hierarchy参见 Prefabs 手册表达 UI 控件层级表达层级化组织的数据例如文件系统。从实现上看层级结构是建立在 Flecs 关系Relationship之上的内建的EcsChildOf即flecs::ChildOf关系对是层级的基石。这一点在官方示例中有直接印证例如 examples/c/entities/hierarchy/src/main.c 中注释明确写到“Hierarchies use ECS relationships and the builtin flecs::ChildOf relationship to create entities as children of other entities.”创建简单的层级结构最简单的层级创建方式是给子实体挂上一个(ChildOf, parent)关系对// C ecs_entity_t spaceship ecs_new(world); ecs_entity_t cockpit ecs_new_w_pair(world, EcsChildOf, spaceship);// C flecs::entity spaceship world.entity(); flecs::entity cockpit world.entity().child_of(spaceship);在 C 中还可以通过实体描述符ecs_entity_desc_t的.parent字段一步到位地创建带名字的层级子实体例如官方示例 examples/c/entities/hierarchy/src/main.c 中创建了一棵“太阳系”层级树Sun → Mercury/Venus/Earth → Moon。C 侧对应world.entity(parent, name)的写法见 examples/cpp/entities/hierarchy/src/main.cpp。获取父实体与遍历子实体Flecs 为“找父亲”和“数孩子”提供了对称的 API// C获取实体的父实体 ecs_entity_t parent ecs_get_parent(world, entity); // C遍历实体的所有子实体 ecs_iter_t it ecs_children(world, parent); while (ecs_children_next(it)) { for (int i 0; i it.count; i ) { ecs_entity_t child it.entities[i]; // ... } }// C获取实体的父实体 flecs::entity parent entity.parent(); // C遍历实体的所有子实体 parent.children([](flecs::entity child) { // ... });对应 C API 的原型定义可以在 include/flecs.hecs_get_parent、include/flecs.hecs_children/ecs_children_next中查到。其中ecs_children默认使用EcsChildOf关系也可以通过ecs_children_w_rel(world, rel, parent)指定自定义关系来遍历如ecs_children_w_rel(world, EcsChildOf, parent)见 include/flecs.h 的说明。递归清理删除父实体时的级联行为当父实体被删除时它的所有子实体会以“深度优先、后删除父”的顺序被递归删除子实体先于父实体被删除// C ecs_entity_t spaceship ecs_new(world); ecs_entity_t cockpit ecs_new_w_pair(world, EcsChildOf, spaceship); ecs_entity_t pilot ecs_new_w_pair(world, EcsChildOf, cockpit); ecs_delete(world, spaceship); ecs_is_alive(world, spaceship); // false ecs_is_alive(world, cockpit); // false ecs_is_alive(world, pilot); // false// C flecs::entity spaceship world.entity(); flecs::entity cockpit world.entity().child_of(spaceship); flecs::entity pilot world.entity().child_of(cockpit); spaceship.destruct(); spaceship.is_alive(); // false cockpit.is_alive(); // false pilot.is_alive(); // false这条语义保证了场景树、UI 树等层级结构在整体销毁时不会留下“孤儿”实体或悬挂引用。值得注意的补充点Parent层级存储见下文同样继承“子随父删”的递归清理行为并且当父实体被删除时子实体的删除是批量执行的无需逐个扫描子实体向量因此删除父实体的开销远低于逐个删除子实体。遍历方式深度优先与广度优先深度优先遍历Depth-First层级结构可以按深度优先方式遍历递归地迭代每个父实体的子实体。完整的可运行示例参见Cexamples/c/entities/hierarchy/src/main.c其中的iterate_tree函数递归遍历整棵树并逐层累加坐标Cexamples/cpp/entities/hierarchy/src/main.cppC 示例中iterate_tree的核心是ecs_childrenecs_children_next的递归调用examples/c/entities/hierarchy/src/main.cC 侧则是e.children(...)回调内的递归examples/cpp/entities/hierarchy/src/main.cpp。广度优先遍历Breadth-First广度优先遍历通过查询Query的cascade特性实现。cascade会保证查询按“父在前、子在后”的顺序迭代实体。相关说明见 Queries 文档 的 relationship traversal 一节可运行示例参见Cexamples/c/queries/hierarchies/src/main.cCexamples/cpp/queries/hierarchy/src/main.cpp以 C 示例为例一条带cascade的层级查询如下examples/cpp/queries/hierarchy/src/main.cppflecs::queryconst LocalTransform, const WorldTransform*, WorldTransform q ecs.query_builderconst LocalTransform, const WorldTransform*, WorldTransform() // 从父实体上取第二个查询参数const WorldTransform // cascade() 保证先遍历父、再遍历子 .term_at(1).parent().cascade() .build();该示例计算了太阳系各天体的世界坐标Sun: {1,1}、Mercury: {2,2}、Venus: {3,3}、Earth: {4,4}、Moon: {4.1,4.1}是最经典的“从局部坐标合成世界坐标”的层级系统用例。C 版本使用.src.id EcsCascade表达同样的语义并将父项设为EcsOptional以让根实体也能被匹配到见 examples/c/queries/hierarchies/src/main.c。命名空间Namespacing实体是有名字的可以在世界中按名字查找。名字根据实体层级进行命名空间限定例如一个名为Cockpit的实体其父实体名为SpaceShip那么在 C 中需要以SpaceShip.Cockpit查找C 中以SpaceShip::Cockpit查找。对应示例examples/c/entities/hierarchy/src/main.c 与 examples/cpp/entities/hierarchy/src/main.cpp中ecs_lookup(ecs, Sun.Earth.Moon)/ecs.lookup(Sun::Earth::Moon)都能正确定位到月球实体。更完整的命名规则说明参见 Entities 与 Components 手册 的 names 一节。从源码看名字索引是按父实体分别维护的flecs_on_reparent_update_name会在实体被重新挂到新父实体时将实体从旧父的名字索引中移除并加入新父的名字索引src/storage/non_fragmenting_childof.c这也解释了为什么层级中的名字解析总是相对父实体进行的。OrderedChildren保持稳定的子实体顺序默认情况下ECS 的实体是存放在 Archetype表中的而实体的表归属会随其组件集合的变化而改变。因此如果子实体在创建后被添加/移除组件ecs_children/children()返回的子实体顺序可能发生变化。OrderedChildrentrait 用于解决这一问题把它加到父实体上后ecs_children/entity::children返回的实体 id 将保持创建顺序或应用自定义的顺序且子实体会在单个结果一次迭代中全部返回。该 trait 不影响查询Query返回实体的顺序。// C ecs_entity_t parent ecs_new(world); ecs_add_id(world, parent, EcsOrderedChildren); ecs_entity_t child_1 ecs_new_w_pair(world, EcsChildOf, parent); ecs_entity_t child_2 ecs_new_w_pair(world, EcsChildOf, parent); ecs_entity_t child_3 ecs_new_w_pair(world, EcsChildOf, parent); // 添加/移除组件通常会改变子实体的迭代顺序 // 但有了 OrderedChildren trait 顺序会被保留。 ecs_set(world, child_2, Position, {10, 20}); ecs_iter_t it ecs_children(world, parent); while (ecs_children_next(it)) { // it.table 在迭代有序子实体时会被置为 NULL for (int i 0; i it.count; i ) { ecs_entity_t e it.entities[i]; // i 0: child_1 // i 1: child_2 // i 2: child_3 } }// C flecs::entity parent world.entity().add(flecs::OrderedChildren); flecs::entity child_1 world.entity().child_of(parent); flecs::entity child_2 world.entity().child_of(parent); flecs::entity child_3 world.entity().child_of(parent); child_2.set(Position{10, 20}); parent.children([](flecs::entity child) { // 1st result: child_1 // 2nd result: child_2 // 3rd result: child_3 });应用还可以通过ecs_set_child_order/entity::set_child_order操作主动修改存储的子实体顺序。该操作对应的 C API 声明在 include/flecs.h。源码实现视角有序子实体的存储实现在 src/storage/ordered_children.c每个(ChildOf, parent)关系的组件记录ecs_component_record_t中维护一个vectorecs_entity_t字段pair-ordered_children子实体按追加顺序存放flecs_ordered_entities_append见 src/storage/ordered_children.c当实体添加了OrderedChildrentrait 时首次访问会调用flecs_ordered_children_populate把该父实体现有的子实体按当前顺序灌入向量src/storage/ordered_children.c实体从表中移动例如因组件变更导致换表时会触发flecs_ordered_children_reparent把该实体从旧父的有序向量中移除并追加到新父的向量中src/storage/ordered_children.cecs_set_child_order则直接对ordered_children向量做整段内存拷贝覆盖src/storage/ordered_children.c并且会校验传入的 children 数组必须与现有子实体完全一致个数与成员都要匹配否则抛出ECS_INVALID_PARAMETER。一个值得注意的限制从 src/storage/ordered_children.c 的断言可以看出不能从“已有子实体使用 Parent 组件”的父实体上移除OrderedChildrentrait会触发ECS_UNSUPPORTED。这是因为Parent层级存储本身是建立在OrderedChildren之上的详见下文二者是绑定关系。两种层级存储ChildOf 与 ParentFlecs 实现了两种针对不同场景优化的层级存储ChildOf层级和Parent层级。它们的行为大体一致但内存布局与性能特征截然不同。总体对比与选型ChildOf层级为大型、非结构化的层级优化。典型例子是场景Scene一个场景根节点可能拥有成百上千个动态创建、动态删除的子实体运行期任何时刻存在哪些实体往往是未知的因此属于“非结构化”层级。Parent层级为小型、结构化的层级优化。典型例子是 Prefab一个 Prefab 通常只有几十个子实体在 Prefab 被实例化时创建、在实例被删除时销毁任何时刻存在哪些实体通常是已知的。参见 Prefabs 手册。选择ChildOf层级的条件一个父实体拥有很多子实体子实体的数量或种类事先未知。选择Parent层级的条件一个父实体只有少量几十个子实体子实体的数量和种类事先已知层级嵌套很深。创建方式上ChildOf层级通过在实体上添加(ChildOf, parent)关系对实现Parent层级则通过给实体设置一个Parent组件值为父实体实现// C ecs_entity_t spaceship ecs_new(world); // 使用 ChildOf 存储创建子实体 ecs_entity_t cockpit ecs_new_w_pair(world, EcsChildOf, spaceship); // 使用 Parent 存储创建子实体 ecs_entity_t engine ecs_new_w_parent(world, spaceship, NULL /* 可选名字 */);// C flecs::entity spaceship world.entity(); // 使用 ChildOf 存储创建子实体 flecs::entity cockpit world.entity(spaceship, nullptr /* 可选名字 */); // 使用 Parent 存储创建子实体 flecs::entity engine world.entity(flecs::Parent{spaceship}, nullptr /* 可选名字 */);其中ecs_new_w_parent的 C API 声明在 include/flecs.h文档明确说明如果指定了名字且该子实体已存在则直接返回已有实体。混用规则一个父实体可以同时拥有两种存储的子实体ChildOf子实体与Parent子实体可以挂在同一个父实体之下单个实体不能同时拥有ChildOf关系对和Parent组件// C ecs_entity_t spaceship ecs_new(world); // OK同一个父实体下可以混用两种存储 ecs_entity_t cockpit ecs_new_w_pair(world, EcsChildOf, spaceship); ecs_entity_t engine ecs_new_w_parent(world, spaceship, NULL); // 不 OK单个实体不能同时拥有两种存储 ecs_entity_t engineering ecs_new_w_parent(world, spaceship, NULL); ecs_add_pair(world, engineering, EcsChildOf, spaceship);// C flecs::entity spaceship world.entity(); // OK同一个父实体下可以混用两种存储 flecs::entity cockpit world.entity(spaceship, nullptr); flecs::entity engine world.entity(flecs::Parent{spaceship}, nullptr); // 不 OK给已有 Parent 组件的实体再加 ChildOf 会移除 Parent 组件 flecs::entity engineering world.entity(flecs::Parent{spaceship}, nullptr); engineering.child_of(spaceship); // 会移除 Parent 组件C 的行为child_of会覆盖并移除Parent组件在源码中也有对应实现依据flecs_on_replace_parent是EcsParent组件的on_replace钩子src/storage/non_fragmenting_childof.c当实体的Parent值被替换例如从 Parent 存储切换到 ChildOf 存储时它会执行从旧父的有序子实体向量中移除、向新父记录中追加、更新名字索引、更新ParentDepth等一系列内部操作。两种存储的共同行为无论使用哪种存储以下行为是一致的父实体被删除时子实体被递归删除带ChildOf项的查询对两种存储都有效关系遍历Position(up)或Position(cascade)对两种存储都有效名字查找parent.lookup(child_name)对两种存储都有效ecs_get_parent/e.parent()可以获取两种存储下的父实体ecs_children/e.children()可以迭代两种存储下的子实体Prefab 实例化prefab hierarchies 一节对两种存储都有效JSON 序列化json serialization 一节对两种存储都有效。两种存储的差异使用Parent层级时父实体不会出现在子实体的类型中ecs_get_type()/e.type()看不到(ChildOf, parent)对Parent层级构建在OrderedChildren特性之上因此子实体以明确定义的顺序存储实例化带Parent层级的 Prefab 时实例子实体会继承Prefab 子实体的组件而实例化带ChildOf层级的 Prefab 时Prefab 子实体的组件会被复制到实例子实体上实例化带Parent层级的 Prefab 时Prefab 子实体的名字不会被复制到实例子实体上Or和Not查询运算符operator overview 一节暂不支持Parent层级。ChildOf 存储的实现细节ChildOf层级本质上是普通“会碎片化”的 Flecs 关系每个唯一的(ChildOf, parent)对在概念上类似于一个组件。因此ChildOf层级的性能是可预期的——几乎所有的组件操作都具有 O(1) 时间复杂度。同时查询某个父实体子实体的组件也非常快因为组件可以像普通数组一样连续访问ecs_query_t *q ecs_query(world, { .expr (ChildOf, parent), Position }); ecs_iter_t it ecs_query_iter(world, q); while (ecs_query_next(it)) { Position *p ecs_field(it, Position, 0); for (int i 0; i it.count; i ) { p[i].x ; // Position 可以作为数组高效访问 } }这些优势在“一个父实体有很多子实体”或“应用只需要访问特定父实体的子实体”时最为明显。例如应用可能在内存中同时加载多个场景但只渲染和更新其中一个场景。使用ChildOf层级时不同父实体的子实体被存储在相互独立的表中因此过滤掉其他场景的实体几乎是零成本的。然而当应用同时拥有大量父实体并且需要同时访问不同父实体的子实体时ChildOf层级就会变得昂贵代价来自以下几个因素不同父实体的子实体存放在不同的表Archetype中碎片化了内存访问。迭代 1 张表 vs. 1000 张表的性能差距可以超过一个数量级每张表都会消耗内存内存量随表中组件数量线性增长带缓存的查询会缓存匹配的表表越多查询的内存占用也越大动态层级会引起表的创建与销毁其开销相对于实体的创建与销毁而言是昂贵的此外表可能需要在查询中被匹配/取消匹配进一步抬高了动态层级的成本。这些缺点在“父实体很多、每个父实体只有少量子实体”时最明显。极端情况下如果父实体的每个子实体组件集合都不同每个表中可能只有一个子实体。决定ChildOf层级是否合适时可以考虑以下问题应用是否只访问特定父实体的子实体父实体的数量是否合理不超过数千内存开销是否可接受可以使用统计 API 或 explorer 仪表盘查看详细的内存统计如果以上有一个或多个答案为“否”建议考虑使用Parent层级。Parent 存储的实现细节Parent层级通过设置值为父实体的Parent组件启用。与ChildOf层级不同使用Parent层级时多个父实体的子实体可以存放在同一张表中。Parent层级中的子实体存储在为(ChildOf, parent)组件索引记录准备的vectorecs_entity_t中——这正是OrderedChildren存储所使用的同一个向量对应源码中的pair-ordered_children见 src/storage/ordered_children.c。当Parent组件被设置时一个(ParentDepth, depth)关系对会被自动添加到子实体上。查看子实体的类型时会看到类似Parent, Position, Velocity, (ParentDepth, 3)这表示该子实体存储在层级中 3 层深的位置。层级深度被查询用于高效实现Parent层级下的cascade/group_by特性。表示这是一个值对value pair而3指定的是实体 id。用户自定义查询同样可以利用ParentDepth例如下面的查询按广度优先顺序迭代实体// C ecs_query_t *q ecs_query(world, { .terms { { ecs_id(Position) } }, .group_by EcsParentDepth });// C auto q world.query_builderPosition() .group_by(flecs::ParentDepth) .build();EcsParentDepth是 Flecs 内建的实体声明于 include/flecs.h。从源码看ParentDepth的维护在flecs_on_replace_parent中完成当子实体的父实体发生变化、且新旧父实体的深度不同时会更新(ParentDepth, depth)值对并递归更新其自身子实体的缓存深度src/storage/non_fragmenting_childof.c。同时父实体的删除/重挂也会触发深度缓存更新flecs_component_update_childof_w_depth。相比 ChildOf 的优势Parent层级最大的好处是它不会显著改变 ECS 存储中组件的内存布局。一条不涉及层级的查询无论实体是否被组织进层级执行性能都相同。这一点尤其利好包含大量小型Prefab层级的应用——切换到Parent层级可以将性能提升、内存占用降低一个数量级以上。除了不碎片化存储之外Parent层级在与 Prefab 组合使用时还有额外的性能收益Prefab 层级实例化不会导致表创建使用Parent层级时因为没有新表创建也就不存在表与查询的匹配开销应用的查询越多性能提升越明显使用Parent层级的 Prefab 会构建一个 “TreeSpawner”它缓存 Prefab 层级的结构与表这对ChildOf层级是不可能的因为各实例间的表并不相同实例子实体可以从 Prefab 子实体继承组件从而降低内存占用和复制开销。TreeSpawner 的存在同样有源码佐证flecs_tree_spawner_assert_not_instantiated在Parent层级被修改时会被调用src/storage/non_fragmenting_childof.c用于阻止已实例化的 Prefab 层级被动态修改。相比 ChildOf 的劣势大多数情况下Parent层级会优于ChildOf层级但存在几个例外Parent层级中的子实体存储在一个向量中因此删除一个子实体是 O(n) 操作。如果父实体拥有大量成千上万个子实体这可能非常昂贵此时应考虑改用ChildOf层级多词项查询中如果包含ChildOf词项性能可能更差。这主要是因为多个父实体的子实体存放在同一张表中查询需要做更多工作来过滤匹配的实体。注意删除子实体的 O(n) 代价不适用于删除父实体的场景删除父实体时子实体是批量删除的无需扫描子实体向量对应源码中的批量清理路径。查询性能对比不同查询形态的取舍新层级存储在特定查询下的表现高度依赖查询的形态。以下逐类说明各类查询相对ChildOf层级的性能表现。Position, Velocity不涉及层级的查询这类查询几乎总是用Parent层级表现更好。ChildOf层级会产生更多表从而降低查询性能使用Parent层级时组件更可能被紧密地打包在内存中。(ChildOf, my_parent)按特定父实体过滤这类查询对Parent层级通常表现相同或更好。原因在于查询直接返回父实体的子实体向量因此永远不需要迭代多张表——而多表迭代正是ChildOf层级下可能发生的情况。(ChildOf, my_parent), Position这类查询在Parent层级下表现更差。因为它无法在表级别求值查询必须逐个检查my_parent的每个子实体是否拥有Position。(ChildOf, *), Position这类查询在Parent层级下表现更差。查询首先找到所有带有ChildOf关系对或Parent组件的表然后检查结果表是否拥有Position。对于带Parent组件的表查询必须逐个返回每个实体因为它需要返回通配符实际匹配到的关系对。这是为了支持使用ecs_field_id、it.id()或it.pair()的代码auto q world.query_builderPosition() .with(flecs::ChildOf, flecs::Wildcard) .each([](flecs::iter it, size_t, Position) { flecs::entity parent it.pair().second() // value is set by query });两种存储下将词项顺序反转都会带来更好的性能第一个词项匹配的表通常远多于第二个词项这会增加非缓存查询的成本以及填充查询缓存的成本。Position, (ChildOf, my_parent)这类查询在Parent层级下表现更差。查询先找到所有带Position的表然后对表中每个实体逐一检查它是否为my_parent的子实体。如果表包含大量实体这可能是一个非常昂贵的操作。即使表中恰好只有my_parent的一个子实体查询会走一条无需逐实体扫描的快速路径但整体仍比ChildOf层级更差。Position, (ChildOf, *)这类查询在Parent层级下表现更差。查询先找到所有带Position的表再检查表是否有Parent组件。如果有查询就知道表中的每个实体都匹配但由于必须把匹配到的父实体传达给应用它仍必须逐实体返回。Position, (ChildOf, _)这类查询在Parent层级下表现相同。查询先找到所有带Position的表再检查表是否有Parent组件。由于任意_通配符不需要向应用传达父实体整张表可以被一次性返回。如果应用还需要找出实体的父实体可以在查询中追加flecs::Parent组件auto q world.query_builderPosition, const flecs::Parent() .with(flecs::ChildOf, flecs::Wildcard) .each([](flecs::iter it, size_t, Position, const flecs::Parent p) { flecs::entity parent it.world().entity(p.value); });Position, Position(up)向上关系遍历这类查询在Parent层级下表现更差。查询先找到所有带Position的表如果发现表带Parent组件而非ChildOf关系对查询必须迭代每个实体并向上遍历层级。该结果无法缓存进一步损害性能。之所以为Parent层级支持 up 遍历主要是为了提供从ChildOf层级迁移的更平滑路径。性能关键的查询通常不应在Parent层级上使用 up 遍历应参见下文改用替代查询。Position(up), Position这类查询在Parent层级下表现更差。虽然不如上一类严重但同样因为使用Parent层级的查询结果无法缓存而劣于ChildOf层级。优化关系遍历拆分查询如上一节所述使用关系遍历的查询在Parent层级下可能更慢。解决方案是把一条使用关系遍历的查询拆分成两条——一条针对ChildOf层级一条针对Parent层级。以查询Position, Position(up)为例可以拆分为// 针对 ChildOf 层级使用关系遍历 Position, Position(up), !Parent // 针对 Parent 层级不使用关系遍历 Position, Parent然后在迭代逻辑中手动从父实体获取Position组件// C ecs_query_t *q ecs_query(world, { .terms { { ecs_id(Position) }, { ecs_id(EcsParent) } }, .cache_kind EcsQueryCacheAuto }); ecs_iter_t it ecs_query_iter(world, q); while (ecs_query_next(it)) { Position *p ecs_field(it, Position, 0); EcsParent *parents ecs_field(it, EcsParent, 1); for (int i 0; i it.count; i ) { const Position *p_parent ecs_get(world, parents[i].value, Position); // 逻辑照常 } }// C auto q world.queryPosition, const flecs::Parent(); q.each([](Position p, const flecs::Parent parent) { const Position p_parent parent.getPosition(); // 逻辑照常 });对于使用cascade的查询应用可以使用group_by(flecs::ParentDepth)替代按层级深度顺序迭代实体。下面的代码展示了如何对每种存储各写一条查询、实现广度优先遍历。注意实现中按深度逐层迭代这可以防止较深的ChildOf层级在较浅的Parent实体之前被处理// C ecs_query_t *q_childof ecs_query(world, { .terms { { ecs_id(Position) }, { ecs_id(Position), .src.id EcsCascade }, { ecs_id(EcsParent), .oper EcsNot } }, .cache_kind EcsQueryCacheAuto }); ecs_query_t *q_parent ecs_query(world, { .terms { { ecs_id(Position) }, { ecs_id(EcsParent) } }, .group_by EcsParentDepth, .cache_kind EcsQueryCacheAuto }); // 将 16 替换为应用中的最大层级深度 for (int depth 0; depth 16; depth ) { // 迭代当前深度的 ChildOf 层级 { ecs_iter_t it ecs_query_iter(world, q_childof); ecs_iter_set_group(it, depth); while (ecs_query_next(it)) { Position *p ecs_field(it, Position, 0); Position *p_parent ecs_field(it, Position, 1); for (int i 0; i it.count; i ) { p[i].x p_parent-x; // ... } } } // 迭代当前深度的 Parent 层级 { ecs_iter_t it ecs_query_iter(world, q_parent); ecs_iter_set_group(it, depth); while (ecs_query_next(it)) { Position *p ecs_field(it, Position, 0); EcsParent *parents ecs_field(it, EcsParent, 1); for (int i 0; i it.count; i ) { const Position *p_parent ecs_get( world, parents[i].value, Position); p[i].x p_parent-x; // ... } } } }// C auto q_childof world.query_builderPosition, const Position() .term_at(1).cascade() .withoutflecs::Parent() .build(); auto q_parent world.query_builderPosition, const flecs::Parent() .group_by(flecs::ParentDepth) .build(); // 将 16 替换为应用中的最大层级深度 for (int depth 0; depth 16; depth ) { q_childof.set_group(depth) .each([](Position p, const Position p_parent) { p.x p_parent.x; }); q_parent.set_group(depth) .each([](Position p, const flecs::Parent parent) { const Position p_parent parent.getPosition(); p.x p_parent.x; }); }注意上述查询均显式指定了.cache_kind EcsQueryCacheAutoC 侧由查询构建器默认处理这确保了拆分后两条查询各自都能享受查询缓存从而把Parent层级“不可缓存 up 遍历”的短板消解在拆分结构之中。总结与进一步阅读Flecs 的层级系统是一个建立在关系之上的通用、高性能机制(ChildOf, parent)关系对负责常见的场景树与 UI 树Parent组件 OrderedChildren向量存储则服务于需要固定顺序、深层嵌套、Prefab 密集复用的结构化层级。选型的关键在于评估“父实体数量与子实体规模”以及“查询形态是否与层级深度交互”父实体少、子实体多、只需按父访问 → 优先ChildOfO(1) 组件操作 表级过滤父实体多、子实体少、Prefab 实例化密集、层级深 → 优先Parent消除表碎片化 TreeSpawner 缓存 组件继承需要稳定子实体顺序 → 加OrderedChildrentrait必要时用ecs_set_child_order自定义顺序需要广度优先迭代 →cascadeChildOf或group_by(flecs::ParentDepth)Parent或按上述方式拆分查询。可以继续深入阅读的仓库资源文档Prefabs 手册Prefab 层级、Queries 文档关系遍历与运算符、Entities 与 Components 手册命名规则、Flecs Remote APIJSON 序列化示例examples/c/entities/hierarchy/src/main.c、examples/cpp/entities/hierarchy/src/main.cpp深度优先遍历、examples/c/queries/hierarchies/src/main.c、examples/cpp/queries/hierarchy/src/main.cppcascade 广度优先遍历源码src/storage/ordered_children.c有序子实体向量与重排序、src/storage/non_fragmenting_childof.cParent 组件、ParentDepth 深度维护与 prefab 子实体索引公共 APIinclude/flecs.hecs_get_parent、ecs_children、ecs_set_child_order、ecs_new_w_parent、EcsParentDepth等声明。赞分享游戏开发【免费下载链接】flecsA fast entity component system (ECS) for C C项目地址https://gitcode.com/gh_mirrors/fl/flecs点击查看免费下载相关推荐二叉树层序遍历终极指南队列与递归两种实现的深度解析 二叉树层序遍历终极指南队列与递归两种实现的深度解析 二叉树的层序遍历Level Order Traversal是算法学习中的核心知识点也是Leet示例工程教程Cocos Creator 引用查找指南四步查清场景与资源的依赖去向Cocos Creator 引用查找指南四步查清场景与资源的依赖去向 你要改材质、删预制体、合并两个场景时最怕的就是不知道谁还在用它。本文带你跑一遍 Coc游戏开发图形学3D渲染chezmoi 的 --recursive 标志掌握目录递归遍历与两种默认语义chezmoi 的 recursive 标志掌握目录递归遍历与两种默认语义 导读 recursive 短标志 r 是 chezmoi 众多命令共用的核心标开发工具CLI配置管理上一篇QuickRecorder不到 10MB 的 macOS 录屏工具下一篇LayoutXLM KIE Algorithm in PaddleOCR: Multimodal SER RE Training, Evaluation and Python Inference创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED

相关推荐

VoltAgent Working Memory 实战指南:跨会话持久化 Agent 上下文的三格式配置与源码解析

VoltAgent Working Memory 实战指南:跨会话持久化 Agent 上下文的三格式配置与源码解析

人工智能AI AgentAgent 框架后端多智能体RAG工具调用Agent 记忆 【免费下载链接】voltagent AI Agent Engineering Platform built on an Open Source TypeScript AI Agent Framework 项目地址: https://gitcode.com/gh_mirrors/vo/voltagent 点击查看 免费下载 Wo…

📅 2026/9/25 16:56:41
Atlas 300V 24G加速卡与YOLOv8部署实战

Atlas 300V 24G加速卡与YOLOv8部署实战

最近总有人私信问我关于“atlas”的问题,点开一看大多是两个:“atlas部署yolo”到底难不难,以及“atlas 300v 24g 是运算加速卡吗”。这两个问题放在一块问,其实说明很多人对这把利器有误解。我上个月刚用一张Atlas 300V 24G推理卡…

📅 2026/9/25 16:56:41
PaddleNLP `chat_template` 多轮对话精调实战:从配置构造、数据流到训练全流程解析

PaddleNLP `chat_template` 多轮对话精调实战:从配置构造、数据流到训练全流程解析

人工智能大模型预训练微调LoRARLHF强化学习分布式训练 【免费下载链接】PaddleNLP Easy-to-use and powerful LLM and SLM library with awesome model zoo. 项目地址: https://gitcode.com/gh_mirrors/pa/PaddleNLP 点击查看 免费下载 多轮对话精调(Mu…

📅 2026/9/25 16:56:41
MORE NEWS

更多资讯

📰

深入解析 Salt 中 SSH 资源的 test 执行模块覆盖:`salt.resources.ssh.modules.test` 与真实的远端连通性检测

运维配置管理后端 【免费下载链接】salt Software to automate the management and configuration of infrastructure and applications at scale. 项目地址: https://gitcode.com/gh_mirrors/sa/salt 点击查看 免费下载 test.ping 是 Salt 生态中使用频率最高的连…

📰

lego v4 到 v5 库迁移完全指南:Context 化、slog 日志与 API 重构要点

网络安全密码学 【免费下载链接】lego Lets Encrypt/ACME client and library written in Go 项目地址: https://gitcode.com/gh_mirrors/le/lego 点击查看 免费下载 本文基于 go-acme/lego 官方迁移文档(docs/content/migration/library.md&#xff09…

📰

如何为开源项目 npmx.dev 贡献代码:环境搭建、开发工作流与测试体系指南

如何为开源项目 npmx.dev 贡献代码:环境搭建、开发工作流与测试体系指南 【免费下载链接】npmx.dev a fast, modern browser for the npm registry 项目地址: https://gitcode.com/gh_mirrors/np/npmx.dev npmx.dev 是一个快速、现代的 npm 注册表浏览器&…

📰

opencodex 配额用量 UI 重构:PR139 中 QuotaBars 行模型、窗口排序与重置文案的实现解析

【免费下载链接】opencodex Universal provider proxy for OpenAI Codex & Claude Code — use any LLM (Claude, Gemini, Grok, DeepSeek, Ollama…) with Codex CLI, App, SDK, and Claude Code 项目地址: https://gitcode.com/gh_mirrors/ope/opencodex 点击…

📰

WorkBuddy Enterprise 企业级 AI 平台与 Agent 生态落地实践指南

1. 从零理解 WorkBuddy Enterprise 的定位与核心价值1.1 它到底是什么,解决谁的什么问题WorkBuddy Enterprise 是腾讯云推出的一套企业级 AI 平台与 Agent 生态产品。说人话就是:它把大模型能力、Agent 编排、企业知识库、工具调用、权限管控这些东西打包…

📰

ONNX Runtime端侧部署三要素:打包、量化与线程治理

1. 项目概述:为什么端侧推理不能只靠“跑通就行”ONNX Runtime 打包、量化与推理线程治理——这九个字不是技术堆砌,而是端侧模型落地的三道生死关。我带团队做过17个终端AI项目,从智能摄像头固件到车载语音助手,再到工业手持终端…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬