Set panic as default fallible system param behavior (#16638)
Fixes: #16578 This is a patch fix, proper fix requires a breaking change. Added `Panic` enum variant and using is as the system meta default. Warn once behavior can be enabled same way disabling panic (originally disabling wans) is. To fix an issue with the current architecture, where **all** combinator system params get checked together, combinator systems only check params of the first system. This will result in old, panicking behavior on subsequent systems and will be fixed in 0.16. Ran unit tests and `fallible_params` example. --------- Co-authored-by: François Mockers <mockersf@gmail.com> Co-authored-by: François Mockers <francois.mockers@vleue.com>
This commit is contained in:
parent
e9a07c5deb
commit
a7d1a73fa8
@ -544,7 +544,7 @@ impl<'w> From<TicksMut<'w>> for Ticks<'w> {
|
|||||||
/// If you need a unique mutable borrow, use [`ResMut`] instead.
|
/// If you need a unique mutable borrow, use [`ResMut`] instead.
|
||||||
///
|
///
|
||||||
/// This [`SystemParam`](crate::system::SystemParam) fails validation if resource doesn't exist.
|
/// This [`SystemParam`](crate::system::SystemParam) fails validation if resource doesn't exist.
|
||||||
/// This will cause systems that use this parameter to be skipped.
|
/// This will cause a panic, but can be configured to do nothing or warn once.
|
||||||
///
|
///
|
||||||
/// Use [`Option<Res<T>>`] instead if the resource might not always exist.
|
/// Use [`Option<Res<T>>`] instead if the resource might not always exist.
|
||||||
pub struct Res<'w, T: ?Sized + Resource> {
|
pub struct Res<'w, T: ?Sized + Resource> {
|
||||||
@ -622,7 +622,7 @@ impl_debug!(Res<'w, T>, Resource);
|
|||||||
/// If you need a shared borrow, use [`Res`] instead.
|
/// If you need a shared borrow, use [`Res`] instead.
|
||||||
///
|
///
|
||||||
/// This [`SystemParam`](crate::system::SystemParam) fails validation if resource doesn't exist.
|
/// This [`SystemParam`](crate::system::SystemParam) fails validation if resource doesn't exist.
|
||||||
/// This will cause systems that use this parameter to be skipped.
|
/// /// This will cause a panic, but can be configured to do nothing or warn once.
|
||||||
///
|
///
|
||||||
/// Use [`Option<ResMut<T>>`] instead if the resource might not always exist.
|
/// Use [`Option<ResMut<T>>`] instead if the resource might not always exist.
|
||||||
pub struct ResMut<'w, T: ?Sized + Resource> {
|
pub struct ResMut<'w, T: ?Sized + Resource> {
|
||||||
@ -683,7 +683,7 @@ impl<'w, T: Resource> From<ResMut<'w, T>> for Mut<'w, T> {
|
|||||||
/// over to another thread.
|
/// over to another thread.
|
||||||
///
|
///
|
||||||
/// This [`SystemParam`](crate::system::SystemParam) fails validation if non-send resource doesn't exist.
|
/// This [`SystemParam`](crate::system::SystemParam) fails validation if non-send resource doesn't exist.
|
||||||
/// This will cause systems that use this parameter to be skipped.
|
/// /// This will cause a panic, but can be configured to do nothing or warn once.
|
||||||
///
|
///
|
||||||
/// Use [`Option<NonSendMut<T>>`] instead if the resource might not always exist.
|
/// Use [`Option<NonSendMut<T>>`] instead if the resource might not always exist.
|
||||||
pub struct NonSendMut<'w, T: ?Sized + 'static> {
|
pub struct NonSendMut<'w, T: ?Sized + 'static> {
|
||||||
|
@ -80,7 +80,7 @@ pub trait Condition<Marker, In: SystemInput = ()>: sealed::Condition<Marker, In>
|
|||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```should_panic
|
||||||
/// use bevy_ecs::prelude::*;
|
/// use bevy_ecs::prelude::*;
|
||||||
///
|
///
|
||||||
/// #[derive(Resource, PartialEq)]
|
/// #[derive(Resource, PartialEq)]
|
||||||
@ -90,7 +90,7 @@ pub trait Condition<Marker, In: SystemInput = ()>: sealed::Condition<Marker, In>
|
|||||||
/// # let mut world = World::new();
|
/// # let mut world = World::new();
|
||||||
/// # fn my_system() {}
|
/// # fn my_system() {}
|
||||||
/// app.add_systems(
|
/// app.add_systems(
|
||||||
/// // The `resource_equals` run condition will fail since we don't initialize `R`,
|
/// // The `resource_equals` run condition will panic since we don't initialize `R`,
|
||||||
/// // just like if we used `Res<R>` in a system.
|
/// // just like if we used `Res<R>` in a system.
|
||||||
/// my_system.run_if(resource_equals(R(0))),
|
/// my_system.run_if(resource_equals(R(0))),
|
||||||
/// );
|
/// );
|
||||||
|
@ -186,7 +186,7 @@ mod tests {
|
|||||||
self as bevy_ecs,
|
self as bevy_ecs,
|
||||||
prelude::{IntoSystemConfigs, IntoSystemSetConfigs, Resource, Schedule, SystemSet},
|
prelude::{IntoSystemConfigs, IntoSystemSetConfigs, Resource, Schedule, SystemSet},
|
||||||
schedule::ExecutorKind,
|
schedule::ExecutorKind,
|
||||||
system::{Commands, In, IntoSystem, Res},
|
system::{Commands, Res, WithParamWarnPolicy},
|
||||||
world::World,
|
world::World,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -215,15 +215,11 @@ mod tests {
|
|||||||
schedule.set_executor_kind(executor);
|
schedule.set_executor_kind(executor);
|
||||||
schedule.add_systems(
|
schedule.add_systems(
|
||||||
(
|
(
|
||||||
// Combined systems get skipped together.
|
|
||||||
(|mut commands: Commands| {
|
|
||||||
commands.insert_resource(R1);
|
|
||||||
})
|
|
||||||
.pipe(|_: In<()>, _: Res<R1>| {}),
|
|
||||||
// This system depends on a system that is always skipped.
|
// This system depends on a system that is always skipped.
|
||||||
|mut commands: Commands| {
|
(|mut commands: Commands| {
|
||||||
commands.insert_resource(R2);
|
commands.insert_resource(R2);
|
||||||
},
|
})
|
||||||
|
.param_warn_once(),
|
||||||
)
|
)
|
||||||
.chain(),
|
.chain(),
|
||||||
);
|
);
|
||||||
@ -246,18 +242,20 @@ mod tests {
|
|||||||
let mut world = World::new();
|
let mut world = World::new();
|
||||||
let mut schedule = Schedule::default();
|
let mut schedule = Schedule::default();
|
||||||
schedule.set_executor_kind(executor);
|
schedule.set_executor_kind(executor);
|
||||||
schedule.configure_sets(S1.run_if(|_: Res<R1>| true));
|
schedule.configure_sets(S1.run_if((|_: Res<R1>| true).param_warn_once()));
|
||||||
schedule.add_systems((
|
schedule.add_systems((
|
||||||
// System gets skipped if system set run conditions fail validation.
|
// System gets skipped if system set run conditions fail validation.
|
||||||
(|mut commands: Commands| {
|
(|mut commands: Commands| {
|
||||||
commands.insert_resource(R1);
|
commands.insert_resource(R1);
|
||||||
})
|
})
|
||||||
|
.param_warn_once()
|
||||||
.in_set(S1),
|
.in_set(S1),
|
||||||
// System gets skipped if run conditions fail validation.
|
// System gets skipped if run conditions fail validation.
|
||||||
(|mut commands: Commands| {
|
(|mut commands: Commands| {
|
||||||
commands.insert_resource(R2);
|
commands.insert_resource(R2);
|
||||||
})
|
})
|
||||||
.run_if(|_: Res<R2>| true),
|
.param_warn_once()
|
||||||
|
.run_if((|_: Res<R2>| true).param_warn_once()),
|
||||||
));
|
));
|
||||||
schedule.run(&mut world);
|
schedule.run(&mut world);
|
||||||
assert!(world.get_resource::<R1>().is_none());
|
assert!(world.get_resource::<R1>().is_none());
|
||||||
|
@ -214,7 +214,7 @@ where
|
|||||||
#[inline]
|
#[inline]
|
||||||
unsafe fn validate_param_unsafe(&mut self, world: UnsafeWorldCell) -> bool {
|
unsafe fn validate_param_unsafe(&mut self, world: UnsafeWorldCell) -> bool {
|
||||||
// SAFETY: Delegate to other `System` implementations.
|
// SAFETY: Delegate to other `System` implementations.
|
||||||
unsafe { self.a.validate_param_unsafe(world) && self.b.validate_param_unsafe(world) }
|
unsafe { self.a.validate_param_unsafe(world) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn initialize(&mut self, world: &mut World) {
|
fn initialize(&mut self, world: &mut World) {
|
||||||
@ -433,7 +433,7 @@ where
|
|||||||
|
|
||||||
unsafe fn validate_param_unsafe(&mut self, world: UnsafeWorldCell) -> bool {
|
unsafe fn validate_param_unsafe(&mut self, world: UnsafeWorldCell) -> bool {
|
||||||
// SAFETY: Delegate to other `System` implementations.
|
// SAFETY: Delegate to other `System` implementations.
|
||||||
unsafe { self.a.validate_param_unsafe(world) && self.b.validate_param_unsafe(world) }
|
unsafe { self.a.validate_param_unsafe(world) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_param(&mut self, world: &World) -> bool {
|
fn validate_param(&mut self, world: &World) -> bool {
|
||||||
|
@ -60,7 +60,7 @@ impl SystemMeta {
|
|||||||
is_send: true,
|
is_send: true,
|
||||||
has_deferred: false,
|
has_deferred: false,
|
||||||
last_run: Tick::new(0),
|
last_run: Tick::new(0),
|
||||||
param_warn_policy: ParamWarnPolicy::Once,
|
param_warn_policy: ParamWarnPolicy::Panic,
|
||||||
#[cfg(feature = "trace")]
|
#[cfg(feature = "trace")]
|
||||||
system_span: info_span!("system", name = name),
|
system_span: info_span!("system", name = name),
|
||||||
#[cfg(feature = "trace")]
|
#[cfg(feature = "trace")]
|
||||||
@ -190,6 +190,8 @@ impl SystemMeta {
|
|||||||
/// State machine for emitting warnings when [system params are invalid](System::validate_param).
|
/// State machine for emitting warnings when [system params are invalid](System::validate_param).
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
pub enum ParamWarnPolicy {
|
pub enum ParamWarnPolicy {
|
||||||
|
/// Stop app with a panic.
|
||||||
|
Panic,
|
||||||
/// No warning should ever be emitted.
|
/// No warning should ever be emitted.
|
||||||
Never,
|
Never,
|
||||||
/// The warning will be emitted once and status will update to [`Self::Never`].
|
/// The warning will be emitted once and status will update to [`Self::Never`].
|
||||||
@ -200,6 +202,7 @@ impl ParamWarnPolicy {
|
|||||||
/// Advances the warn policy after validation failed.
|
/// Advances the warn policy after validation failed.
|
||||||
#[inline]
|
#[inline]
|
||||||
fn advance(&mut self) {
|
fn advance(&mut self) {
|
||||||
|
// Ignore `Panic` case, because it stops execution before this function gets called.
|
||||||
*self = Self::Never;
|
*self = Self::Never;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -209,15 +212,21 @@ impl ParamWarnPolicy {
|
|||||||
where
|
where
|
||||||
P: SystemParam,
|
P: SystemParam,
|
||||||
{
|
{
|
||||||
if matches!(self, Self::Never) {
|
match self {
|
||||||
return;
|
Self::Panic => panic!(
|
||||||
|
"{0} could not access system parameter {1}",
|
||||||
|
name,
|
||||||
|
disqualified::ShortName::of::<P>()
|
||||||
|
),
|
||||||
|
Self::Once => {
|
||||||
|
bevy_utils::tracing::warn!(
|
||||||
|
"{0} did not run because it requested inaccessible system parameter {1}",
|
||||||
|
name,
|
||||||
|
disqualified::ShortName::of::<P>()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Self::Never => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
bevy_utils::tracing::warn!(
|
|
||||||
"{0} did not run because it requested inaccessible system parameter {1}",
|
|
||||||
name,
|
|
||||||
disqualified::ShortName::of::<P>()
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -232,6 +241,11 @@ where
|
|||||||
/// Set warn policy.
|
/// Set warn policy.
|
||||||
fn with_param_warn_policy(self, warn_policy: ParamWarnPolicy) -> FunctionSystem<M, F>;
|
fn with_param_warn_policy(self, warn_policy: ParamWarnPolicy) -> FunctionSystem<M, F>;
|
||||||
|
|
||||||
|
/// Warn only once about invalid system parameters.
|
||||||
|
fn param_warn_once(self) -> FunctionSystem<M, F> {
|
||||||
|
self.with_param_warn_policy(ParamWarnPolicy::Once)
|
||||||
|
}
|
||||||
|
|
||||||
/// Disable all param warnings.
|
/// Disable all param warnings.
|
||||||
fn never_param_warn(self) -> FunctionSystem<M, F> {
|
fn never_param_warn(self) -> FunctionSystem<M, F> {
|
||||||
self.with_param_warn_policy(ParamWarnPolicy::Never)
|
self.with_param_warn_policy(ParamWarnPolicy::Never)
|
||||||
|
@ -1648,7 +1648,7 @@ impl<'w, 'q, Q: QueryData, F: QueryFilter> From<&'q mut Query<'w, '_, Q, F>>
|
|||||||
/// [System parameter] that provides access to single entity's components, much like [`Query::single`]/[`Query::single_mut`].
|
/// [System parameter] that provides access to single entity's components, much like [`Query::single`]/[`Query::single_mut`].
|
||||||
///
|
///
|
||||||
/// This [`SystemParam`](crate::system::SystemParam) fails validation if zero or more than one matching entity exists.
|
/// This [`SystemParam`](crate::system::SystemParam) fails validation if zero or more than one matching entity exists.
|
||||||
/// This will cause systems that use this parameter to be skipped.
|
/// /// This will cause a panic, but can be configured to do nothing or warn once.
|
||||||
///
|
///
|
||||||
/// Use [`Option<Single<D, F>>`] instead if zero or one matching entities can exist.
|
/// Use [`Option<Single<D, F>>`] instead if zero or one matching entities can exist.
|
||||||
///
|
///
|
||||||
@ -1684,7 +1684,7 @@ impl<'w, D: QueryData, F: QueryFilter> Single<'w, D, F> {
|
|||||||
/// [System parameter] that works very much like [`Query`] except it always contains at least one matching entity.
|
/// [System parameter] that works very much like [`Query`] except it always contains at least one matching entity.
|
||||||
///
|
///
|
||||||
/// This [`SystemParam`](crate::system::SystemParam) fails validation if no matching entities exist.
|
/// This [`SystemParam`](crate::system::SystemParam) fails validation if no matching entities exist.
|
||||||
/// This will cause systems that use this parameter to be skipped.
|
/// /// This will cause a panic, but can be configured to do nothing or warn once.
|
||||||
///
|
///
|
||||||
/// Much like [`Query::is_empty`] the worst case runtime will be `O(n)` where `n` is the number of *potential* matches.
|
/// Much like [`Query::is_empty`] the worst case runtime will be `O(n)` where `n` is the number of *potential* matches.
|
||||||
/// This can be notably expensive for queries that rely on non-archetypal filters such as [`Added`](crate::query::Added) or [`Changed`](crate::query::Changed)
|
/// This can be notably expensive for queries that rely on non-archetypal filters such as [`Added`](crate::query::Added) or [`Changed`](crate::query::Changed)
|
||||||
|
@ -451,7 +451,7 @@ mod tests {
|
|||||||
|
|
||||||
let mut world = World::default();
|
let mut world = World::default();
|
||||||
// This fails because `T` has not been added to the world yet.
|
// This fails because `T` has not been added to the world yet.
|
||||||
let result = world.run_system_once(system);
|
let result = world.run_system_once(system.param_warn_once());
|
||||||
|
|
||||||
assert!(matches!(result, Err(RunSystemError::InvalidParams(_))));
|
assert!(matches!(result, Err(RunSystemError::InvalidParams(_))));
|
||||||
}
|
}
|
||||||
|
@ -1335,7 +1335,7 @@ unsafe impl<T: SystemBuffer> SystemParam for Deferred<'_, T> {
|
|||||||
/// over to another thread.
|
/// over to another thread.
|
||||||
///
|
///
|
||||||
/// This [`SystemParam`] fails validation if non-send resource doesn't exist.
|
/// This [`SystemParam`] fails validation if non-send resource doesn't exist.
|
||||||
/// This will cause systems that use this parameter to be skipped.
|
/// /// This will cause a panic, but can be configured to do nothing or warn once.
|
||||||
///
|
///
|
||||||
/// Use [`Option<NonSend<T>>`] instead if the resource might not always exist.
|
/// Use [`Option<NonSend<T>>`] instead if the resource might not always exist.
|
||||||
pub struct NonSend<'w, T: 'static> {
|
pub struct NonSend<'w, T: 'static> {
|
||||||
|
@ -965,7 +965,7 @@ mod tests {
|
|||||||
fn system(_: Res<T>) {}
|
fn system(_: Res<T>) {}
|
||||||
|
|
||||||
let mut world = World::new();
|
let mut world = World::new();
|
||||||
let id = world.register_system_cached(system);
|
let id = world.register_system(system.param_warn_once());
|
||||||
// This fails because `T` has not been added to the world yet.
|
// This fails because `T` has not been added to the world yet.
|
||||||
let result = world.run_system(id);
|
let result = world.run_system(id);
|
||||||
|
|
||||||
|
@ -131,6 +131,10 @@ impl Plugin for MeshRenderPlugin {
|
|||||||
load_internal_asset!(app, SKINNING_HANDLE, "skinning.wgsl", Shader::from_wgsl);
|
load_internal_asset!(app, SKINNING_HANDLE, "skinning.wgsl", Shader::from_wgsl);
|
||||||
load_internal_asset!(app, MORPH_HANDLE, "morph.wgsl", Shader::from_wgsl);
|
load_internal_asset!(app, MORPH_HANDLE, "morph.wgsl", Shader::from_wgsl);
|
||||||
|
|
||||||
|
if app.get_sub_app(RenderApp).is_none() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
app.add_systems(
|
app.add_systems(
|
||||||
PostUpdate,
|
PostUpdate,
|
||||||
(no_automatic_skin_batching, no_automatic_morph_batching),
|
(no_automatic_skin_batching, no_automatic_morph_batching),
|
||||||
|
@ -20,22 +20,20 @@ fn main() {
|
|||||||
App::new()
|
App::new()
|
||||||
.add_plugins(DefaultPlugins)
|
.add_plugins(DefaultPlugins)
|
||||||
.add_systems(Startup, setup)
|
.add_systems(Startup, setup)
|
||||||
// Systems that fail parameter validation will emit warnings.
|
// Default system policy is to panic if parameters fail to be fetched.
|
||||||
// The default policy is to emit a warning once per system.
|
// We overwrite that configuration, to either warn us once or never.
|
||||||
// This is good for catching unexpected behavior, but can
|
// This is good for catching unexpected behavior without crashing the app,
|
||||||
// lead to spam. You can disable invalid param warnings
|
// but can lead to spam.
|
||||||
// per system using the `.never_param_warn()` method.
|
|
||||||
.add_systems(
|
.add_systems(
|
||||||
Update,
|
Update,
|
||||||
(
|
(
|
||||||
user_input,
|
user_input.param_warn_once(),
|
||||||
move_targets.never_param_warn(),
|
move_targets.never_param_warn(),
|
||||||
move_pointer.never_param_warn(),
|
move_pointer.never_param_warn(),
|
||||||
)
|
)
|
||||||
.chain(),
|
.chain(),
|
||||||
)
|
)
|
||||||
// We will leave this systems with default warning policy.
|
.add_systems(Update, do_nothing_fail_validation.param_warn_once())
|
||||||
.add_systems(Update, do_nothing_fail_validation)
|
|
||||||
.run();
|
.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user