首页
/ Bevy 0.20 迁移指南:从 Entity::PLACEHOLDER 到 Option<Entity> —— UiCameraMapper 与 RetainedViewEntity 的 API 变更详解

Bevy 0.20 迁移指南:从 Entity::PLACEHOLDER 到 Option<Entity> —— UiCameraMapper 与 RetainedViewEntity 的 API 变更详解

2026-09-05 15:09:38作者:殷蕙予

本文讲解 Bevy 0.20(对应 PR #25119)中一项静默但影响 API 的变更:若干原先以 Entity::PLACEHOLDER 作为空值哨兵的变量与函数,已改用 Option<Entity> 表达“无实体”语义。读完后你将明确知道哪些 API 受到影响(UiCameraMapper::current_camera()RetainedViewEntity::auxiliary_entity)、新旧版本代码如何对照改写,以及从当前仓库源码看这两处设计为何要这样演进。

一、背景:PLACEHOLDER 哨兵模式与 Option 的取舍

Entity::PLACEHOLDER 是 Bevy ECS 中长期存在的哨兵值,定义在 bevy_ecs 实体模块,其取值为实体索引空间中的保留位置(NonMaxU32::MAX 对应的 index)。它在官方文档中给出的典型用途有两类:

// 1. 初始化已知大小的实体集合,稍后再填入真实实体
let mut entities: [Entity; 10] = [Entity::PLACEHOLDER; 10];

// 2. 为含 Entity 字段的组件实现 Reflect 的 FromWorld 时作为占位值
impl FromWorld for MyStruct {
    fn from_world(_world: &mut World) -> Self {
        Self {
            entity: Entity::PLACEHOLDER,
        }
    }
}

以上用法在 实体模块文档示例 中可以直接看到,且在 0.20 中依然有效——本次变更并没有删除 Entity::PLACEHOLDER 本身。

问题出在“公共 API 用哨兵值表达空值”的用法上:调用方必须记住用 != Entity::PLACEHOLDER 做手动判断,既不自文档化,也容易与真实实体混淆。Rust 惯用法是用 Option<Entity> 表达“可能没有实体”,类型系统本身即承载了该语义。Bevy 0.20 正是按这一思路清理了下面两处对外 API。

二、变更一:UiCameraMapper::current_camera() 返回 Option<Entity>

2.1 新旧代码对照

这是本次迁移的核心代码示例(完整示例也收录于 官方迁移指南):

// Bevy 0.19
let camera: Entity = camera_mapper.current_camera();
if camera != Entity::PLACEHOLDER {
    ...
}

// Bevy 0.20
if let Some(camera) = camera_mapper.current_camera() {
    ...
}

迁移要点:将 let camera: Entity = ... 加哨兵比较的写法,替换为对 Option<Entity>if let Some(...) 解构。语义保持不变:Some(camera) 表示 mapper 最近一次成功映射出的相机实体,None 表示尚未发生过成功映射。

2.2 源码级印证:缓存式映射器的内部结构

UiCameraMapper 定义在 bevy_ui_render,用于把 UI 目标相机实体(ComputedUiTargetCamera 中的相机)映射为渲染世界中的 render entity:

/// Helper for mapping UI target camera entities to their corresponding render entities,
/// with caching to avoid repeated lookups for the same camera.
pub struct UiCameraMapper<'w, 's> {
    mapping: &'w Query<'w, 's, RenderEntity>,
    /// Cached camera entity from the last successful `map` call.
    camera_entity: Option<Entity>,
    /// Cached entity from the last successful `map` call.
    render_entity: Option<Entity>,
}

从源码结构看,Option<Entity> 的引入与内部缓存逻辑是同一件事的两面:map() 方法(同文件 L335-L344)在每次调用时先取出目标相机,若与缓存不同则查一次 RenderEntity 并更新缓存;current_camera() 则直接返回缓存的 camera_entity 字段。缓存初值自然就是 None(由 UiCameraMap::get_mapper()L314-L320 中构造),而 0.19 中这个初值只能用 Entity::PLACEHOLDER 表达——这正是迁移指南中两处字段类型变化的由来。

该 mapper 由系统参数 UiCameraMap(一个 #[derive(SystemParam)])的 get_mapper() 创建,在 bevy_ui_render 的各提取系统中被反复使用(例如 L719 的 uinode 提取、text.rsbox_shadow.rs 等模块)。对于自定义 UI 渲染管线、或需要按帧查询“当前映射到的 UI 相机”的用户代码,升级后必须把哨兵比较改写为 Option 解构,否则无法通过编译。

三、变更二:RetainedViewEntity::auxiliary_entity 变为 Option<MainEntity>

3.1 字段类型变化

RetainedViewEntity 是渲染世界中“跨帧稳定的视图标识符”,定义在 bevy_render 视图模块。0.20 中其 auxiliary_entity 字段由 MainEntity(内部以 PLACEHOLDER 表空)改为:

pub auxiliary_entity: Option<MainEntity>,

构造函数签名同步变化,L394-L404

pub fn new(
    main_entity: MainEntity,
    auxiliary_entity: Option<MainEntity>,
    subview_index: u32,
) -> Self

直接构造 RetainedViewEntity(例如自定义 render phase、按视图保留数据)的用户,需要在升级时把原来传 Entity::PLACEHOLDER 的位置改为传 None,把原来传 Some(x)/具体实体处按实际值传入。

3.2 为什么要 auxiliary_entity:subview 机制

字段注释解释了它的存在意义(同文件 L371-L385):

  • 不能用渲染世界实体做标识,因为渲染世界实体跨帧不稳定;
  • 也不能只用主世界 MainEntity,因为一个主世界视图可能提取为多个渲染世界视图(subview):方向光的每个阴影级联(cascade)是一个 subview,点光源阴影立方体贴图的每一面(0 到 5)是一个 subview;
  • 因此用 (main_entity, auxiliary_entity, subview_index) 三元组唯一标识一个视图。

其中 auxiliary_entity 当前服务于阴影级联:当存在多个相机时,每个相机需要各自的一套级联,光实体加 subview 索引不足以区分,需要额外记录“该级联关联的相机”——这个相机就是 auxiliary_entity。对于不需要辅助实体的视图(普通相机、无阴影方向光的级联等),0.19 中只能塞入 PLACEHOLDER,0.20 起则直接是 None,语义一目了然。

RetainedViewEntity 作为 ExtractedViewretained_view_entity 字段(L416-L418)贯穿渲染提取流程,并被 bevy_pbrbevy_sprite_renderbevy_core_pipeline 以及自定义 render phase 示例(如 custom_render_phase)引用,因此凡是在视图粒度上自建逻辑的进阶用户,都应确认相关代码已适配新签名。

四、迁移操作清单

升级 0.19 → 0.20 时,可按以下步骤排查:

  1. 定位受影响调用点:在代码库中全局搜索 Entity::PLACEHOLDER 的相等性比较(== Entity::PLACEHOLDER / != Entity::PLACEHOLDER),以及 current_camera().auxiliary_entityRetainedViewEntity::new( 三处符号。
  2. UiCameraMapper::current_camera():把 let camera: Entity = ...; if camera != Entity::PLACEHOLDER { ... } 改为 if let Some(camera) = camera_mapper.current_camera() { ... }(见第二节示例)。
  3. RetainedViewEntity:构造处将空值位置的 Entity::PLACEHOLDER 替换为 None,非空位置替换为 Some(entity);读取 auxiliary_entity 处的代码同样改为 Option 解构或 is_some_and(...)
  4. 确认影响范围:这两个 API 分别位于 bevy_ui_renderbevy_render,只影响触及 UI 渲染映射或自定义视图级渲染管线的代码;纯游戏逻辑、标准 PBR/2D 场景代码通常不受影响。
  5. 版本前提:本文以当前仓库源码为准,适用前提是从 Bevy 0.19 升级到 0.20;UiCameraMapper::map() 原本就返回 Option<Entity>,未在本次变更之列。

五、哪些地方仍然使用 Entity::PLACEHOLDER

需要澄清的是,Entity::PLACEHOLDER 并未被废弃,它在合理场景下依然是标准工具:

  • 集合初始化[Entity::PLACEHOLDER; N] 这类“先占位后填充”的模式仍是文档推荐的写法(实体模块示例);
  • 反射与序列化占位Reflect 派生组件的 FromWorld 实现中作为字段占位值;
  • 内部实现:例如实体远程分配器把“未初始化”实体槽位表示为 PLACEHOLDER(见 remote_allocator 注释),bevy_ui_render 内部系统也仍在局部变量中沿用哨兵模式(lib.rs L1966 附近)。

本次迁移的边界因此很清晰:内部实现与占位初始化继续保留哨兵模式,而面向外部用户的公共 API 在表达“没有实体”时统一改用 Option<Entity>。理解这一边界,是判断自己代码是否需要迁移、以及如何迁移的依据。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
528
588
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
906
1.83 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
891
5.78 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.53 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.34 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
987
506
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384