Skip to content

Commit 9d4d110

Browse files
chescockmockersf
authored andcommitted
Include SystemParamValidationError in RunSystemError and RegisteredSystemError (#18666)
# Objective Provide more useful errors when `World::run_system` and related methods fail parameter validation. Let callers determine whether the validation failure would have skipped or failed the system. Follow-up to #18541. ## Solution Add a `SystemParamValidationError` value to the `RunSystemError::InvalidParams` and `RegisteredSystemError::InvalidParams` variants. That includes the complete context of the parameter validation error, including the `skipped` flag.
1 parent 4ad5f46 commit 9d4d110

File tree

2 files changed

+48
-38
lines changed

2 files changed

+48
-38
lines changed

crates/bevy_ecs/src/system/system.rs

+20-20
Original file line numberDiff line numberDiff line change
@@ -368,37 +368,35 @@ impl RunSystemOnce for &mut World {
368368
{
369369
let mut system: T::System = IntoSystem::into_system(system);
370370
system.initialize(self);
371-
match system.validate_param(self) {
372-
Ok(()) => Ok(system.run(input, self)),
373-
// TODO: should we expse the fact that the system was skipped to the user?
374-
// Should we somehow unify this better with system error handling?
375-
Err(_) => Err(RunSystemError::InvalidParams(system.name())),
376-
}
371+
system
372+
.validate_param(self)
373+
.map_err(|err| RunSystemError::InvalidParams {
374+
system: system.name(),
375+
err,
376+
})?;
377+
Ok(system.run(input, self))
377378
}
378379
}
379380

380381
/// Running system failed.
381-
#[derive(Error)]
382+
#[derive(Error, Debug)]
382383
pub enum RunSystemError {
383384
/// System could not be run due to parameters that failed validation.
384-
///
385-
/// This can occur because the data required by the system was not present in the world.
386-
#[error("The data required by the system {0:?} was not found in the world and the system did not run due to failed parameter validation.")]
387-
InvalidParams(Cow<'static, str>),
388-
}
389-
390-
impl Debug for RunSystemError {
391-
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
392-
match self {
393-
Self::InvalidParams(arg0) => f.debug_tuple("InvalidParams").field(arg0).finish(),
394-
}
395-
}
385+
/// This should not be considered an error if [`field@SystemParamValidationError::skipped`] is `true`.
386+
#[error("System {system} did not run due to failed parameter validation: {err}")]
387+
InvalidParams {
388+
/// The identifier of the system that was run.
389+
system: Cow<'static, str>,
390+
/// The returned parameter validation error.
391+
err: SystemParamValidationError,
392+
},
396393
}
397394

398395
#[cfg(test)]
399396
mod tests {
400397
use super::*;
401398
use crate::prelude::*;
399+
use alloc::string::ToString;
402400

403401
#[test]
404402
fn run_system_once() {
@@ -470,6 +468,8 @@ mod tests {
470468
// This fails because `T` has not been added to the world yet.
471469
let result = world.run_system_once(system);
472470

473-
assert!(matches!(result, Err(RunSystemError::InvalidParams(_))));
471+
assert!(matches!(result, Err(RunSystemError::InvalidParams { .. })));
472+
let expected = "System bevy_ecs::system::system::tests::run_system_once_invalid_params::system did not run due to failed parameter validation: Parameter `Res<T>` failed validation: Resource does not exist";
473+
assert_eq!(expected, result.unwrap_err().to_string());
474474
}
475475
}

crates/bevy_ecs/src/system/system_registry.rs

+28-18
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use crate::reflect::ReflectComponent;
33
use crate::{
44
change_detection::Mut,
55
entity::Entity,
6-
system::{input::SystemInput, BoxedSystem, IntoSystem},
6+
system::{input::SystemInput, BoxedSystem, IntoSystem, SystemParamValidationError},
77
world::World,
88
};
99
use alloc::boxed::Box;
@@ -351,17 +351,16 @@ impl World {
351351
initialized = true;
352352
}
353353

354-
let result = if system.validate_param(self).is_ok() {
355-
// Wait to run the commands until the system is available again.
356-
// This is needed so the systems can recursively run themselves.
357-
let ret = system.run_without_applying_deferred(input, self);
358-
system.queue_deferred(self.into());
359-
Ok(ret)
360-
} else {
361-
// TODO: do we want to differentiate between failed validation and skipped systems?
362-
// Do we want to better unify this with system error handling?
363-
Err(RegisteredSystemError::InvalidParams(id))
364-
};
354+
let result = system
355+
.validate_param(self)
356+
.map_err(|err| RegisteredSystemError::InvalidParams { system: id, err })
357+
.map(|()| {
358+
// Wait to run the commands until the system is available again.
359+
// This is needed so the systems can recursively run themselves.
360+
let ret = system.run_without_applying_deferred(input, self);
361+
system.queue_deferred(self.into());
362+
ret
363+
});
365364

366365
// Return ownership of system trait object (if entity still exists)
367366
if let Ok(mut entity) = self.get_entity_mut(id.entity) {
@@ -494,10 +493,14 @@ pub enum RegisteredSystemError<I: SystemInput = (), O = ()> {
494493
#[error("System {0:?} tried to remove itself")]
495494
SelfRemove(SystemId<I, O>),
496495
/// System could not be run due to parameters that failed validation.
497-
///
498-
/// This can occur because the data required by the system was not present in the world.
499-
#[error("The data required by the system {0:?} was not found in the world and the system did not run due to failed parameter validation.")]
500-
InvalidParams(SystemId<I, O>),
496+
/// This should not be considered an error if [`field@SystemParamValidationError::skipped`] is `true`.
497+
#[error("System {system:?} did not run due to failed parameter validation: {err}")]
498+
InvalidParams {
499+
/// The identifier of the system that was run.
500+
system: SystemId<I, O>,
501+
/// The returned parameter validation error.
502+
err: SystemParamValidationError,
503+
},
501504
}
502505

503506
impl<I: SystemInput, O> core::fmt::Debug for RegisteredSystemError<I, O> {
@@ -509,7 +512,11 @@ impl<I: SystemInput, O> core::fmt::Debug for RegisteredSystemError<I, O> {
509512
Self::SystemNotCached => write!(f, "SystemNotCached"),
510513
Self::Recursive(arg0) => f.debug_tuple("Recursive").field(arg0).finish(),
511514
Self::SelfRemove(arg0) => f.debug_tuple("SelfRemove").field(arg0).finish(),
512-
Self::InvalidParams(arg0) => f.debug_tuple("InvalidParams").field(arg0).finish(),
515+
Self::InvalidParams { system, err } => f
516+
.debug_struct("InvalidParams")
517+
.field("system", system)
518+
.field("err", err)
519+
.finish(),
513520
}
514521
}
515522
}
@@ -858,6 +865,7 @@ mod tests {
858865
#[test]
859866
fn run_system_invalid_params() {
860867
use crate::system::RegisteredSystemError;
868+
use alloc::{format, string::ToString};
861869

862870
struct T;
863871
impl Resource for T {}
@@ -870,8 +878,10 @@ mod tests {
870878

871879
assert!(matches!(
872880
result,
873-
Err(RegisteredSystemError::InvalidParams(_))
881+
Err(RegisteredSystemError::InvalidParams { .. })
874882
));
883+
let expected = format!("System {id:?} did not run due to failed parameter validation: Parameter `Res<T>` failed validation: Resource does not exist");
884+
assert_eq!(expected, result.unwrap_err().to_string());
875885
}
876886

877887
#[test]

0 commit comments

Comments
 (0)