SuperAPI AI 工具集

bevy-performance

编程与开发

查看项目

更新于 10-16 06:08

技能介绍

用途

当某个系统运行缓慢,或资源(资产)一直留在内存中时,可加载本技能。它聚焦 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 路由)。