error_handling/
error_handling.rs

1//! Showcases how fallible systems and observers can make use of Rust's powerful result handling
2//! syntax.
3//!
4//! Important note: to set the global error handler, the `configurable_error_handler` feature must be
5//! enabled. This feature is disabled by default, as it may introduce runtime overhead, especially for commands.
6
7use bevy::ecs::{
8    error::{warn, GLOBAL_ERROR_HANDLER},
9    world::DeferredWorld,
10};
11use bevy::math::sampling::UniformMeshSampler;
12use bevy::prelude::*;
13
14use rand::distributions::Distribution;
15use rand::SeedableRng;
16use rand_chacha::ChaCha8Rng;
17
18fn main() {
19    // By default, fallible systems that return an error will panic.
20    //
21    // We can change this by setting a custom error handler, which applies globally.
22    // Here we set the global error handler using one of the built-in
23    // error handlers. Bevy provides built-in handlers for `panic`, `error`, `warn`, `info`,
24    // `debug`, `trace` and `ignore`.
25    GLOBAL_ERROR_HANDLER
26        .set(warn)
27        .expect("The error handler can only be set once, globally.");
28
29    let mut app = App::new();
30
31    app.add_plugins(DefaultPlugins);
32
33    #[cfg(feature = "bevy_mesh_picking_backend")]
34    app.add_plugins(MeshPickingPlugin);
35
36    // Fallible systems can be used the same way as regular systems. The only difference is they
37    // return a `Result<(), BevyError>` instead of a `()` (unit) type. Bevy will handle both
38    // types of systems the same way, except for the error handling.
39    app.add_systems(Startup, setup);
40
41    // Commands can also return `Result`s, which are automatically handled by the global error handler
42    // if not explicitly handled by the user.
43    app.add_systems(Startup, failing_commands);
44
45    // Individual systems can also be handled by piping the output result:
46    app.add_systems(
47        PostStartup,
48        failing_system.pipe(|result: In<Result>| {
49            let _ = result.0.inspect_err(|err| info!("captured error: {err}"));
50        }),
51    );
52
53    // Fallible observers are also supported.
54    app.add_observer(fallible_observer);
55
56    // If we run the app, we'll see the following output at startup:
57    //
58    //  WARN Encountered an error in system `fallible_systems::failing_system`: Resource not initialized
59    // ERROR fallible_systems::failing_system failed: Resource not initialized
60    //  INFO captured error: Resource not initialized
61    app.run();
62}
63
64/// An example of a system that calls several fallible functions with the question mark operator.
65///
66/// See: <https://doc.rust-lang.org/reference/expressions/operator-expr.html#the-question-mark-operator>
67fn setup(
68    mut commands: Commands,
69    mut meshes: ResMut<Assets<Mesh>>,
70    mut materials: ResMut<Assets<StandardMaterial>>,
71) -> Result {
72    let mut seeded_rng = ChaCha8Rng::seed_from_u64(19878367467712);
73
74    // Make a plane for establishing space.
75    commands.spawn((
76        Mesh3d(meshes.add(Plane3d::default().mesh().size(12.0, 12.0))),
77        MeshMaterial3d(materials.add(Color::srgb(0.3, 0.5, 0.3))),
78        Transform::from_xyz(0.0, -2.5, 0.0),
79    ));
80
81    // Spawn a light:
82    commands.spawn((
83        PointLight {
84            shadows_enabled: true,
85            ..default()
86        },
87        Transform::from_xyz(4.0, 8.0, 4.0),
88    ));
89
90    // Spawn a camera:
91    commands.spawn((
92        Camera3d::default(),
93        Transform::from_xyz(-2.0, 3.0, 5.0).looking_at(Vec3::ZERO, Vec3::Y),
94    ));
95
96    // Create a new sphere mesh:
97    let mut sphere_mesh = Sphere::new(1.0).mesh().ico(7)?;
98    sphere_mesh.generate_tangents()?;
99
100    // Spawn the mesh into the scene:
101    let mut sphere = commands.spawn((
102        Mesh3d(meshes.add(sphere_mesh.clone())),
103        MeshMaterial3d(materials.add(StandardMaterial::default())),
104        Transform::from_xyz(-1.0, 1.0, 0.0),
105    ));
106
107    // Generate random sample points:
108    let triangles = sphere_mesh.triangles()?;
109    let distribution = UniformMeshSampler::try_new(triangles)?;
110
111    // Setup sample points:
112    let point_mesh = meshes.add(Sphere::new(0.01).mesh().ico(3)?);
113    let point_material = materials.add(StandardMaterial {
114        base_color: Srgba::RED.into(),
115        emissive: LinearRgba::rgb(1.0, 0.0, 0.0),
116        ..default()
117    });
118
119    // Add sample points as children of the sphere:
120    for point in distribution.sample_iter(&mut seeded_rng).take(10000) {
121        sphere.with_child((
122            Mesh3d(point_mesh.clone()),
123            MeshMaterial3d(point_material.clone()),
124            Transform::from_translation(point),
125        ));
126    }
127
128    // Indicate the system completed successfully:
129    Ok(())
130}
131
132// Observer systems can also return a `Result`.
133fn fallible_observer(
134    trigger: Trigger<Pointer<Move>>,
135    mut world: DeferredWorld,
136    mut step: Local<f32>,
137) -> Result {
138    let mut transform = world
139        .get_mut::<Transform>(trigger.target)
140        .ok_or("No transform found.")?;
141
142    *step = if transform.translation.x > 3. {
143        -0.1
144    } else if transform.translation.x < -3. || *step == 0. {
145        0.1
146    } else {
147        *step
148    };
149
150    transform.translation.x += *step;
151
152    Ok(())
153}
154
155#[derive(Resource)]
156struct UninitializedResource;
157
158fn failing_system(world: &mut World) -> Result {
159    world
160        // `get_resource` returns an `Option<T>`, so we use `ok_or` to convert it to a `Result` on
161        // which we can call `?` to propagate the error.
162        .get_resource::<UninitializedResource>()
163        // We can provide a `str` here because `BevyError` implements `From<&str>`.
164        .ok_or("Resource not initialized")?;
165
166    Ok(())
167}
168
169fn failing_commands(mut commands: Commands) {
170    commands
171        // This entity doesn't exist!
172        .entity(Entity::from_raw(12345678))
173        // Normally, this failed command would panic,
174        // but since we've set the global error handler to `warn`
175        // it will log a warning instead.
176        .insert(Transform::default());
177
178    // The error handlers for commands can be set individually as well,
179    // by using the queue_handled method.
180    commands.queue_handled(
181        |world: &mut World| -> Result {
182            world
183                .get_resource::<UninitializedResource>()
184                .ok_or("Resource not initialized when accessed in a command")?;
185
186            Ok(())
187        },
188        |error, context| {
189            error!("{error}, {context}");
190        },
191    );
192}