error_handling/
error_handling.rs

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