用途
当某个系统运行缓慢,或资源(资产)一直留在内存中时,可加载本技能。它聚焦 Bevy ECS 的运行时开销,配合 bevy-debugging-profiling 查看计数器。目标版本为 Bevy 0.20+。
主要能力
ECS 结构与并行
系统在条件允许时会并行运行。.chain() 会强制顺序,并移除被链式连接的那组系统之间的并行性。只对必须看到彼此写入的系统使用 .chain()。
固定步长
移动以及其他不应跟随渲染帧的游戏逻辑,应放在 Fixed / FixedMain(bevy::time)上,而不是每个 Update 都执行。
度量
- FrameTimeDiagnosticsPlugin:记录帧时间、fps 和帧数。
- EntityCountDiagnosticsPlugin:记录实体数量。
- SystemInformationDiagnosticsPlugin:记录进程 cpu_usage 和 mem_usage。
- LogDiagnosticsPlugin:打印上述数据。
这些页面未给出目标毫秒数。
内存与 Handle
Handle 是引用计数的。长期存活的 Resource 中保留的强句柄会让资产保持加载;最后一个强句柄被丢弃时,资产会从 Assets<T> 中移除。Assets::add 不会对程序化生成的资产去重。只要还有一个句柄存在,AssetServer::load 就会复用该句柄。
调度
诊断插件在 DefaultPlugins 之后添加到 App 上。不要臆造预算(budget)资源。
使用方式
- 只对共享可变查询的系统使用 chain。
- 从 DiagnosticsStore 读取帧诊断数据。
- 仅在资产必须保持加载时,才在资源中保留强 Handle。
- Changed<T> 会跳过本 tick 内组件未发生变化的实体。
示例:Cargo 依赖
[package]
name = "performance"
version = "0.1.0"
edition = "2021"
[dependencies]
bevy = "0.20"
示例:伤害写入生命值,死亡系统链式跟随
伤害写入 health。死亡系统被链式排在它之后,以便观察到同一 tick。移动不共享该查询,因此留在链外。
#[derive(Component)]
struct Health(f32);
#[derive(Component)]
struct Velocity(Vec3);
fn apply_damage(mut query: Query<&mut Health>) {
for mut health in &mut query {
health.0 = (health.0 - 1.0).max(0.0);
}
}
fn reap(mut commands: Commands, query: Query<(Entity, &Health)>) {
for (entity, health) in &query {
if health.0 <= 0.0 {
commands.entity(entity).despawn();
}
}
}
fn integrate(time: Res<Time<Fixed>>, mut query: Query<(&Velocity, &mut Transform)>) {
let dt = time.delta_secs();
for (velocity, mut transform) in &mut query {
transform.translation += velocity.0 * dt;
}
}
pub struct SimPlugin;
impl Plugin for SimPlugin {
fn build(&self, app: &mut App) {
app.add_systems(FixedUpdate, (apply_damage, reap).chain())
.add_systems(FixedUpdate, integrate);
}
}
示例:帧诊断与强句柄保持网格加载
帧诊断记录 fps。资源中的强网格句柄会让该网格保持加载,直到资源被丢弃。
use bevy::diagnostic::{DiagnosticsStore, FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin};
#[derive(Resource)]
struct KeptMesh(Handle<Mesh>);
fn spawn_kept_mesh(mut commands: Commands, mut meshes: ResMut<Assets<Mesh>>) {
let handle = meshes.add(Mesh::from(Cuboid::new(1.0, 1.0, 1.0)));
commands.insert_resource(KeptMesh(handle));
}
fn read_fps(diagnostics: Res<DiagnosticsStore>) {
let Some(fps) = diagnostics.get(&FrameTimeDiagnosticsPlugin::FPS) else {
return;
};
let _value = fps.smoothed();
}
pub struct MeasurePlugin;
impl Plugin for MeasurePlugin {
fn build(&self, app: &mut App) {
app.add_plugins((
FrameTimeDiagnosticsPlugin::default(),
LogDiagnosticsPlugin::default(),
))
.add_systems(Startup, spawn_kept_mesh)
.add_systems(Update, read_fps);
}
}
示例:Changed<Transform> 只读取本 tick 移动过的锚点
本 tick 移动过的锚点是跟随者唯一读取的 transform。Changed<Transform> 让查询的其余部分保持空闲。
#[derive(Component)]
struct Marker;
#[derive(Component)]
struct Anchor;
fn follow_changed(
movers: Query<&Transform, (With<Anchor>, Changed<Transform>)>,
mut markers: Query<&mut Transform, (With<Marker>, Without<Anchor>)>,
) {
let Some(source) = movers.iter().next() else {
return;
};
let translation = source.translation;
for mut marker in &mut markers {
marker.translation = translation;
}
}
适用场景
- 系统运行缓慢,需要定位并行与顺序问题。
- 资产长期驻留内存,需要理解 Handle 引用计数与资源持有。
- 需要为移动等游戏逻辑设置固定步长。
- 需要读取帧时间、fps、实体数、进程 CPU 与内存等诊断数据。
- 需要只处理本 tick 发生变化的实体。
限制与约束
- 不要在文档 URL 中固定 /0.20.0/;链接保持指向最新版本,0.20 目标只在正文中说明一次。
- 不要从两个无序系统可变查询同一组件;调度会并行运行系统,除非用 .chain() 排序。
- 不要在此臆造物理、导航、网络或 XR crate;本技能只命名有文档的 Bevy crate 和模块。
- 不使用 Godot 的 servers、visibility notifiers 或 profiler monitors;Bevy 代码保持惯用 ECS,只使用有文档的 Bevy API。
- 不使用本文件未打开的剔除或批处理 API。
- 不使用第三方 profiler crate。
状态所有权
| 部分 | 所有者 | 谁可变 | 调度 |
|---|---|---|---|
| 单个实体上的数据 | Component | 一个系统通过 Query | Update 或 FixedMain |
| 整个应用的一个值 | Resource | 一个系统 | Startup 插入,之后由读取它的调度 |
| 生成 | Commands | 生成它的系统 | 初始世界用 Startup;运行时生成用之后的调度 |
| 不能竞态的顺序 | .chain() | 仅被链式的系统 | 同一调度 |
| 插件边界 | Plugin | build 注册系统与资源 | 应用运行之前 |
借用规则:两个都可变查询同一组件的系统必须排序(.chain()),或拆分使只有一个写入。不相关的系统保持并行。
决策表
| 如果你需要 | 这样做 | 不要 |
|---|---|---|
| 逐实体数据 | Component + Query | Godot 节点脚本 |
| 应用级配置或时钟 | Resource | 把值复制到每个实体 |
| 稳定的模拟步长 | FixedMain 和 Fixed 时钟 | 把模拟绑定到原始帧 Update |
| 启动时生成 | Startup 中的 Commands | 从渲染系统生成 |
| 非共享状态的跨系统通知 | bevy-events-messaging 中的 Message 或 observer 槽位 | 在本文件中臆造新事件类型 |
| 文档集合之外的 API | 先查官方文档 | 编造的 crate 或 API |
加载 references/ecs-ownership.md 可查看所有权表。
参考
- bevy-master 上的库固定为 0.20.0。官方页面保持在无版本号的 latest 分支。
- 官方文档:ECS(并行系统与 .chain())、Time 示例(Fixed 与 FixedMain)、Examples(latest 分支上的示例树)。
相关技能
前置:bevy-master(谁拥有哪部分状态)、bevy-ecs-composition(系统与 .chain())、bevy-debugging-profiling(这些诊断如何接入)。
互补:bevy-asset-scenes(句柄何时加载与丢弃)、bevy-character-movement(属于固定时间步长的工作)、godot-performance-optimization(Godot 引擎上的性能,同等深度)。
下游/消费者:暂无。
主技能:bevy-master(Bevy 路由)。