Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/examples/ecs/error_handling.rs
6592 views
1
//! Showcases how fallible systems and observers can make use of Rust's powerful result handling
2
//! syntax.
3
4
use bevy::ecs::{error::warn, world::DeferredWorld};
5
use bevy::math::sampling::UniformMeshSampler;
6
use bevy::prelude::*;
7
8
use rand::distr::Distribution;
9
use rand::SeedableRng;
10
use rand_chacha::ChaCha8Rng;
11
12
fn 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>
59
fn 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`.
125
fn fallible_observer(
126
event: On<Pointer<Move>>,
127
mut world: DeferredWorld,
128
mut step: Local<f32>,
129
) -> Result {
130
let mut transform = world
131
.get_mut::<Transform>(event.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)]
148
struct UninitializedResource;
149
150
fn 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
161
fn 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
}
185
186