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