Skip to content

Commit 7d8504f

Browse files
authored
feat(ecs): implement fallible observer systems (#17731)
This commit builds on top of the work done in #16589 and #17051, by adding support for fallible observer systems. As with the previous work, the actual results of the observer system are suppressed for now, but the intention is to provide a way to handle errors in a global way. Until then, you can use a `PipeSystem` to manually handle results. --------- Signed-off-by: Jean Mertz <[email protected]>
1 parent 5b0d898 commit 7d8504f

File tree

4 files changed

+259
-19
lines changed

4 files changed

+259
-19
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2165,6 +2165,7 @@ name = "Fallible Systems"
21652165
description = "Systems that return results to handle errors"
21662166
category = "ECS (Entity Component System)"
21672167
wasm = false
2168+
required-features = ["bevy_mesh_picking_backend"]
21682169

21692170
[[example]]
21702171
name = "startup_system"

crates/bevy_ecs/src/observer/runner.rs

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use crate::{
66
observer::{ObserverDescriptor, ObserverTrigger},
77
prelude::*,
88
query::DebugCheckedUnwrap,
9+
result::{DefaultSystemErrorHandler, SystemErrorContext},
910
system::{IntoObserverSystem, ObserverSystem},
1011
world::DeferredWorld,
1112
};
@@ -272,6 +273,7 @@ pub struct Observer {
272273
system: Box<dyn Any + Send + Sync + 'static>,
273274
descriptor: ObserverDescriptor,
274275
hook_on_add: ComponentHook,
276+
error_handler: Option<fn(Error, SystemErrorContext)>,
275277
}
276278

277279
impl Observer {
@@ -282,6 +284,7 @@ impl Observer {
282284
system: Box::new(IntoObserverSystem::into_system(system)),
283285
descriptor: Default::default(),
284286
hook_on_add: hook_on_add::<E, B, I::System>,
287+
error_handler: None,
285288
}
286289
}
287290

@@ -316,6 +319,14 @@ impl Observer {
316319
self
317320
}
318321

322+
/// Set the error handler to use for this observer.
323+
///
324+
/// See the [`result` module-level documentation](crate::result) for more information.
325+
pub fn with_error_handler(mut self, error_handler: fn(Error, SystemErrorContext)) -> Self {
326+
self.error_handler = Some(error_handler);
327+
self
328+
}
329+
319330
/// Returns the [`ObserverDescriptor`] for this [`Observer`].
320331
pub fn descriptor(&self) -> &ObserverDescriptor {
321332
&self.descriptor
@@ -363,6 +374,15 @@ fn observer_system_runner<E: Event, B: Bundle, S: ObserverSystem<E, B>>(
363374
}
364375
state.last_trigger_id = last_trigger;
365376

377+
// SAFETY: Observer was triggered so must have an `Observer` component.
378+
let error_handler = unsafe {
379+
observer_cell
380+
.get::<Observer>()
381+
.debug_checked_unwrap()
382+
.error_handler
383+
.debug_checked_unwrap()
384+
};
385+
366386
let trigger: Trigger<E, B> = Trigger::new(
367387
// SAFETY: Caller ensures `ptr` is castable to `&mut T`
368388
unsafe { ptr.deref_mut() },
@@ -386,7 +406,15 @@ fn observer_system_runner<E: Event, B: Bundle, S: ObserverSystem<E, B>>(
386406
unsafe {
387407
(*system).update_archetype_component_access(world);
388408
if (*system).validate_param_unsafe(world) {
389-
(*system).run_unsafe(trigger, world);
409+
if let Err(err) = (*system).run_unsafe(trigger, world) {
410+
error_handler(
411+
err,
412+
SystemErrorContext {
413+
name: (*system).name(),
414+
last_run: (*system).get_last_run(),
415+
},
416+
);
417+
};
390418
(*system).queue_deferred(world.into_deferred());
391419
}
392420
}
@@ -416,10 +444,15 @@ fn hook_on_add<E: Event, B: Bundle, S: ObserverSystem<E, B>>(
416444
..Default::default()
417445
};
418446

447+
let error_handler = world.get_resource_or_init::<DefaultSystemErrorHandler>().0;
448+
419449
// Initialize System
420450
let system: *mut dyn ObserverSystem<E, B> =
421451
if let Some(mut observe) = world.get_mut::<Observer>(entity) {
422452
descriptor.merge(&observe.descriptor);
453+
if observe.error_handler.is_none() {
454+
observe.error_handler = Some(error_handler);
455+
}
423456
let system = observe.system.downcast_mut::<S>().unwrap();
424457
&mut *system
425458
} else {
@@ -442,3 +475,44 @@ fn hook_on_add<E: Event, B: Bundle, S: ObserverSystem<E, B>>(
442475
}
443476
});
444477
}
478+
479+
#[cfg(test)]
480+
mod tests {
481+
use super::*;
482+
use crate::{event::Event, observer::Trigger};
483+
484+
#[derive(Event)]
485+
struct TriggerEvent;
486+
487+
#[test]
488+
#[should_panic(expected = "I failed!")]
489+
fn test_fallible_observer() {
490+
fn system(_: Trigger<TriggerEvent>) -> Result {
491+
Err("I failed!".into())
492+
}
493+
494+
let mut world = World::default();
495+
world.add_observer(system);
496+
Schedule::default().run(&mut world);
497+
world.trigger(TriggerEvent);
498+
}
499+
500+
#[test]
501+
fn test_fallible_observer_ignored_errors() {
502+
#[derive(Resource, Default)]
503+
struct Ran(bool);
504+
505+
fn system(_: Trigger<TriggerEvent>, mut ran: ResMut<Ran>) -> Result {
506+
ran.0 = true;
507+
Err("I failed!".into())
508+
}
509+
510+
let mut world = World::default();
511+
world.init_resource::<Ran>();
512+
let observer = Observer::new(system).with_error_handler(crate::result::ignore);
513+
world.spawn(observer);
514+
Schedule::default().run(&mut world);
515+
world.trigger(TriggerEvent);
516+
assert!(world.resource::<Ran>().0);
517+
}
518+
}

crates/bevy_ecs/src/system/observer_system.rs

Lines changed: 149 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,27 @@
1+
use alloc::{borrow::Cow, vec::Vec};
2+
use core::marker::PhantomData;
3+
14
use crate::{
5+
archetype::ArchetypeComponentId,
6+
component::{ComponentId, Tick},
27
prelude::{Bundle, Trigger},
3-
system::System,
8+
query::Access,
9+
result::Result,
10+
schedule::{Fallible, Infallible},
11+
system::{input::SystemIn, System},
12+
world::{unsafe_world_cell::UnsafeWorldCell, DeferredWorld, World},
413
};
514

615
use super::IntoSystem;
716

817
/// Implemented for [`System`]s that have a [`Trigger`] as the first argument.
9-
pub trait ObserverSystem<E: 'static, B: Bundle, Out = ()>:
18+
pub trait ObserverSystem<E: 'static, B: Bundle, Out = Result>:
1019
System<In = Trigger<'static, E, B>, Out = Out> + Send + 'static
1120
{
1221
}
1322

14-
impl<
15-
E: 'static,
16-
B: Bundle,
17-
Out,
18-
T: System<In = Trigger<'static, E, B>, Out = Out> + Send + 'static,
19-
> ObserverSystem<E, B, Out> for T
23+
impl<E: 'static, B: Bundle, Out, T> ObserverSystem<E, B, Out> for T where
24+
T: System<In = Trigger<'static, E, B>, Out = Out> + Send + 'static
2025
{
2126
}
2227

@@ -32,31 +37,158 @@ impl<
3237
label = "the trait `IntoObserverSystem` is not implemented",
3338
note = "for function `ObserverSystem`s, ensure the first argument is a `Trigger<T>` and any subsequent ones are `SystemParam`"
3439
)]
35-
pub trait IntoObserverSystem<E: 'static, B: Bundle, M, Out = ()>: Send + 'static {
40+
pub trait IntoObserverSystem<E: 'static, B: Bundle, M, Out = Result>: Send + 'static {
3641
/// The type of [`System`] that this instance converts into.
3742
type System: ObserverSystem<E, B, Out>;
3843

3944
/// Turns this value into its corresponding [`System`].
4045
fn into_system(this: Self) -> Self::System;
4146
}
4247

43-
impl<
44-
S: IntoSystem<Trigger<'static, E, B>, Out, M> + Send + 'static,
45-
M,
46-
Out,
47-
E: 'static,
48-
B: Bundle,
49-
> IntoObserverSystem<E, B, M, Out> for S
48+
impl<E, B, M, Out, S> IntoObserverSystem<E, B, (Fallible, M), Out> for S
5049
where
50+
S: IntoSystem<Trigger<'static, E, B>, Out, M> + Send + 'static,
5151
S::System: ObserverSystem<E, B, Out>,
52+
E: 'static,
53+
B: Bundle,
5254
{
53-
type System = <S as IntoSystem<Trigger<'static, E, B>, Out, M>>::System;
55+
type System = S::System;
5456

5557
fn into_system(this: Self) -> Self::System {
5658
IntoSystem::into_system(this)
5759
}
5860
}
5961

62+
impl<E, B, M, S> IntoObserverSystem<E, B, (Infallible, M), Result> for S
63+
where
64+
S: IntoSystem<Trigger<'static, E, B>, (), M> + Send + 'static,
65+
S::System: ObserverSystem<E, B, ()>,
66+
E: Send + Sync + 'static,
67+
B: Bundle,
68+
{
69+
type System = InfallibleObserverWrapper<E, B, S::System>;
70+
71+
fn into_system(this: Self) -> Self::System {
72+
InfallibleObserverWrapper::new(IntoSystem::into_system(this))
73+
}
74+
}
75+
76+
/// A wrapper that converts an observer system that returns `()` into one that returns `Ok(())`.
77+
pub struct InfallibleObserverWrapper<E, B, S> {
78+
observer: S,
79+
_marker: PhantomData<(E, B)>,
80+
}
81+
82+
impl<E, B, S> InfallibleObserverWrapper<E, B, S> {
83+
/// Create a new `InfallibleObserverWrapper`.
84+
pub fn new(observer: S) -> Self {
85+
Self {
86+
observer,
87+
_marker: PhantomData,
88+
}
89+
}
90+
}
91+
92+
impl<E, B, S> System for InfallibleObserverWrapper<E, B, S>
93+
where
94+
S: ObserverSystem<E, B, ()>,
95+
E: Send + Sync + 'static,
96+
B: Bundle,
97+
{
98+
type In = Trigger<'static, E, B>;
99+
type Out = Result;
100+
101+
#[inline]
102+
fn name(&self) -> Cow<'static, str> {
103+
self.observer.name()
104+
}
105+
106+
#[inline]
107+
fn component_access(&self) -> &Access<ComponentId> {
108+
self.observer.component_access()
109+
}
110+
111+
#[inline]
112+
fn archetype_component_access(&self) -> &Access<ArchetypeComponentId> {
113+
self.observer.archetype_component_access()
114+
}
115+
116+
#[inline]
117+
fn is_send(&self) -> bool {
118+
self.observer.is_send()
119+
}
120+
121+
#[inline]
122+
fn is_exclusive(&self) -> bool {
123+
self.observer.is_exclusive()
124+
}
125+
126+
#[inline]
127+
fn has_deferred(&self) -> bool {
128+
self.observer.has_deferred()
129+
}
130+
131+
#[inline]
132+
unsafe fn run_unsafe(
133+
&mut self,
134+
input: SystemIn<'_, Self>,
135+
world: UnsafeWorldCell,
136+
) -> Self::Out {
137+
self.observer.run_unsafe(input, world);
138+
Ok(())
139+
}
140+
141+
#[inline]
142+
fn run(&mut self, input: SystemIn<'_, Self>, world: &mut World) -> Self::Out {
143+
self.observer.run(input, world);
144+
Ok(())
145+
}
146+
147+
#[inline]
148+
fn apply_deferred(&mut self, world: &mut World) {
149+
self.observer.apply_deferred(world);
150+
}
151+
152+
#[inline]
153+
fn queue_deferred(&mut self, world: DeferredWorld) {
154+
self.observer.queue_deferred(world);
155+
}
156+
157+
#[inline]
158+
unsafe fn validate_param_unsafe(&mut self, world: UnsafeWorldCell) -> bool {
159+
self.observer.validate_param_unsafe(world)
160+
}
161+
162+
#[inline]
163+
fn initialize(&mut self, world: &mut World) {
164+
self.observer.initialize(world);
165+
}
166+
167+
#[inline]
168+
fn update_archetype_component_access(&mut self, world: UnsafeWorldCell) {
169+
self.observer.update_archetype_component_access(world);
170+
}
171+
172+
#[inline]
173+
fn check_change_tick(&mut self, change_tick: Tick) {
174+
self.observer.check_change_tick(change_tick);
175+
}
176+
177+
#[inline]
178+
fn get_last_run(&self) -> Tick {
179+
self.observer.get_last_run()
180+
}
181+
182+
#[inline]
183+
fn set_last_run(&mut self, last_run: Tick) {
184+
self.observer.set_last_run(last_run);
185+
}
186+
187+
fn default_system_sets(&self) -> Vec<crate::schedule::InternedSystemSet> {
188+
self.observer.default_system_sets()
189+
}
190+
}
191+
60192
#[cfg(test)]
61193
mod tests {
62194
use crate::{

0 commit comments

Comments
 (0)