Skip to content

Commit ad527b1

Browse files
MiniaczQSpecificProtagonist
authored andcommitted
Better warnings about invalid parameters (bevyengine#15500)
# Objective System param validation warnings should be configurable and default to "warn once" (per system). Fixes: bevyengine#15391 ## Solution `SystemMeta` is given a new `ParamWarnPolicy` field. The policy decides whether warnings will be emitted by each system param when it fails validation. The policy is updated by the system after param validation fails. Example warning: ``` 2024-09-30T18:10:04.740749Z WARN bevy_ecs::system::function_system: System fallible_params::do_nothing_fail_validation will not run because it requested inaccessible system parameter Single<(), (With<Player>, With<Enemy>)> ``` Currently, only the first invalid parameter is displayed. Warnings can be disabled on function systems using `.param_never_warn()`. (there is also `.with_param_warn_policy(policy)`) ## Testing Ran `fallible_params` example. --------- Co-authored-by: SpecificProtagonist <vincentjunge@posteo.net>
1 parent 45721b1 commit ad527b1

File tree

13 files changed

+176
-58
lines changed

13 files changed

+176
-58
lines changed

crates/bevy_ecs/src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ pub mod prelude {
6060
Commands, Deferred, EntityCommand, EntityCommands, In, InMut, InRef, IntoSystem, Local,
6161
NonSend, NonSendMut, ParallelCommands, ParamSet, Populated, Query, ReadOnlySystem, Res,
6262
ResMut, Resource, Single, System, SystemIn, SystemInput, SystemParamBuilder,
63-
SystemParamFunction,
63+
SystemParamFunction, WithParamWarnPolicy,
6464
},
6565
world::{
6666
Command, EntityMut, EntityRef, EntityWorldMut, FromWorld, OnAdd, OnInsert, OnRemove,

crates/bevy_ecs/src/schedule/executor/mod.rs

-12
Original file line numberDiff line numberDiff line change
@@ -180,18 +180,6 @@ mod __rust_begin_short_backtrace {
180180
}
181181
}
182182

183-
#[macro_export]
184-
/// Emits a warning about system being skipped.
185-
macro_rules! warn_system_skipped {
186-
($ty:literal, $sys:expr) => {
187-
bevy_utils::tracing::warn!(
188-
"{} {} was skipped due to inaccessible system parameters.",
189-
$ty,
190-
$sys
191-
)
192-
};
193-
}
194-
195183
#[cfg(test)]
196184
mod tests {
197185
use crate::{

crates/bevy_ecs/src/schedule/executor/multi_threaded.rs

+1-4
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ use crate::{
1919
query::Access,
2020
schedule::{is_apply_deferred, BoxedCondition, ExecutorKind, SystemExecutor, SystemSchedule},
2121
system::BoxedSystem,
22-
warn_system_skipped,
2322
world::{unsafe_world_cell::UnsafeWorldCell, World},
2423
};
2524

@@ -524,7 +523,7 @@ impl ExecutorState {
524523
unsafe fn should_run(
525524
&mut self,
526525
system_index: usize,
527-
system: &BoxedSystem,
526+
system: &mut BoxedSystem,
528527
conditions: &mut Conditions,
529528
world: UnsafeWorldCell,
530529
) -> bool {
@@ -575,7 +574,6 @@ impl ExecutorState {
575574
// - `update_archetype_component_access` has been called for system.
576575
let valid_params = unsafe { system.validate_param_unsafe(world) };
577576
if !valid_params {
578-
warn_system_skipped!("System", system.name());
579577
self.skipped_systems.insert(system_index);
580578
}
581579
should_run &= valid_params;
@@ -751,7 +749,6 @@ unsafe fn evaluate_and_fold_conditions(
751749
// required by the condition.
752750
// - `update_archetype_component_access` has been called for condition.
753751
if !unsafe { condition.validate_param_unsafe(world) } {
754-
warn_system_skipped!("Condition", condition.name());
755752
return false;
756753
}
757754
// SAFETY:

crates/bevy_ecs/src/schedule/executor/simple.rs

-5
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ use crate::{
77
schedule::{
88
executor::is_apply_deferred, BoxedCondition, ExecutorKind, SystemExecutor, SystemSchedule,
99
},
10-
warn_system_skipped,
1110
world::World,
1211
};
1312

@@ -83,9 +82,6 @@ impl SystemExecutor for SimpleExecutor {
8382
let system = &mut schedule.systems[system_index];
8483
if should_run {
8584
let valid_params = system.validate_param(world);
86-
if !valid_params {
87-
warn_system_skipped!("System", system.name());
88-
}
8985
should_run &= valid_params;
9086
}
9187

@@ -139,7 +135,6 @@ fn evaluate_and_fold_conditions(conditions: &mut [BoxedCondition], world: &mut W
139135
.iter_mut()
140136
.map(|condition| {
141137
if !condition.validate_param(world) {
142-
warn_system_skipped!("Condition", condition.name());
143138
return false;
144139
}
145140
__rust_begin_short_backtrace::readonly_run(&mut **condition, world)

crates/bevy_ecs/src/schedule/executor/single_threaded.rs

-5
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ use fixedbitset::FixedBitSet;
55

66
use crate::{
77
schedule::{is_apply_deferred, BoxedCondition, ExecutorKind, SystemExecutor, SystemSchedule},
8-
warn_system_skipped,
98
world::World,
109
};
1110

@@ -89,9 +88,6 @@ impl SystemExecutor for SingleThreadedExecutor {
8988
let system = &mut schedule.systems[system_index];
9089
if should_run {
9190
let valid_params = system.validate_param(world);
92-
if !valid_params {
93-
warn_system_skipped!("System", system.name());
94-
}
9591
should_run &= valid_params;
9692
}
9793

@@ -171,7 +167,6 @@ fn evaluate_and_fold_conditions(conditions: &mut [BoxedCondition], world: &mut W
171167
.iter_mut()
172168
.map(|condition| {
173169
if !condition.validate_param(world) {
174-
warn_system_skipped!("Condition", condition.name());
175170
return false;
176171
}
177172
__rust_begin_short_backtrace::readonly_run(&mut **condition, world)

crates/bevy_ecs/src/system/adapter_system.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -179,8 +179,9 @@ where
179179
}
180180

181181
#[inline]
182-
unsafe fn validate_param_unsafe(&self, world: UnsafeWorldCell) -> bool {
183-
self.system.validate_param_unsafe(world)
182+
unsafe fn validate_param_unsafe(&mut self, world: UnsafeWorldCell) -> bool {
183+
// SAFETY: Delegate to other `System` implementations.
184+
unsafe { self.system.validate_param_unsafe(world) }
184185
}
185186

186187
fn initialize(&mut self, world: &mut crate::prelude::World) {

crates/bevy_ecs/src/system/combinator.rs

+6-4
Original file line numberDiff line numberDiff line change
@@ -212,8 +212,9 @@ where
212212
}
213213

214214
#[inline]
215-
unsafe fn validate_param_unsafe(&self, world: UnsafeWorldCell) -> bool {
216-
self.a.validate_param_unsafe(world) && self.b.validate_param_unsafe(world)
215+
unsafe fn validate_param_unsafe(&mut self, world: UnsafeWorldCell) -> bool {
216+
// SAFETY: Delegate to other `System` implementations.
217+
unsafe { self.a.validate_param_unsafe(world) && self.b.validate_param_unsafe(world) }
217218
}
218219

219220
fn initialize(&mut self, world: &mut World) {
@@ -430,8 +431,9 @@ where
430431
self.b.queue_deferred(world);
431432
}
432433

433-
unsafe fn validate_param_unsafe(&self, world: UnsafeWorldCell) -> bool {
434-
self.a.validate_param_unsafe(world) && self.b.validate_param_unsafe(world)
434+
unsafe fn validate_param_unsafe(&mut self, world: UnsafeWorldCell) -> bool {
435+
// SAFETY: Delegate to other `System` implementations.
436+
unsafe { self.a.validate_param_unsafe(world) && self.b.validate_param_unsafe(world) }
435437
}
436438

437439
fn validate_param(&mut self, world: &World) -> bool {

crates/bevy_ecs/src/system/exclusive_function_system.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,8 @@ where
150150
}
151151

152152
#[inline]
153-
unsafe fn validate_param_unsafe(&self, _world: UnsafeWorldCell) -> bool {
153+
unsafe fn validate_param_unsafe(&mut self, _world: UnsafeWorldCell) -> bool {
154+
// All exclusive system params are always available.
154155
true
155156
}
156157

crates/bevy_ecs/src/system/function_system.rs

+99-2
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ pub struct SystemMeta {
4343
is_send: bool,
4444
has_deferred: bool,
4545
pub(crate) last_run: Tick,
46+
param_warn_policy: ParamWarnPolicy,
4647
#[cfg(feature = "trace")]
4748
pub(crate) system_span: Span,
4849
#[cfg(feature = "trace")]
@@ -59,6 +60,7 @@ impl SystemMeta {
5960
is_send: true,
6061
has_deferred: false,
6162
last_run: Tick::new(0),
63+
param_warn_policy: ParamWarnPolicy::Once,
6264
#[cfg(feature = "trace")]
6365
system_span: info_span!("system", name = name),
6466
#[cfg(feature = "trace")]
@@ -75,6 +77,7 @@ impl SystemMeta {
7577
/// Sets the name of of this system.
7678
///
7779
/// Useful to give closure systems more readable and unique names for debugging and tracing.
80+
#[inline]
7881
pub fn set_name(&mut self, new_name: impl Into<Cow<'static, str>>) {
7982
let new_name: Cow<'static, str> = new_name.into();
8083
#[cfg(feature = "trace")]
@@ -108,9 +111,94 @@ impl SystemMeta {
108111

109112
/// Marks the system as having deferred buffers like [`Commands`](`super::Commands`)
110113
/// This lets the scheduler insert [`apply_deferred`](`crate::prelude::apply_deferred`) systems automatically.
114+
#[inline]
111115
pub fn set_has_deferred(&mut self) {
112116
self.has_deferred = true;
113117
}
118+
119+
/// Changes the warn policy.
120+
#[inline]
121+
pub(crate) fn set_param_warn_policy(&mut self, warn_policy: ParamWarnPolicy) {
122+
self.param_warn_policy = warn_policy;
123+
}
124+
125+
/// Advances the warn policy after validation failed.
126+
#[inline]
127+
pub(crate) fn advance_param_warn_policy(&mut self) {
128+
self.param_warn_policy.advance();
129+
}
130+
131+
/// Emits a warning about inaccessible system param if policy allows it.
132+
#[inline]
133+
pub fn try_warn_param<P>(&self)
134+
where
135+
P: SystemParam,
136+
{
137+
self.param_warn_policy.try_warn::<P>(&self.name);
138+
}
139+
}
140+
141+
/// State machine for emitting warnings when [system params are invalid](System::validate_param).
142+
#[derive(Clone, Copy)]
143+
pub enum ParamWarnPolicy {
144+
/// No warning should ever be emitted.
145+
Never,
146+
/// The warning will be emitted once and status will update to [`Self::Never`].
147+
Once,
148+
}
149+
150+
impl ParamWarnPolicy {
151+
/// Advances the warn policy after validation failed.
152+
#[inline]
153+
fn advance(&mut self) {
154+
*self = Self::Never;
155+
}
156+
157+
/// Emits a warning about inaccessible system param if policy allows it.
158+
#[inline]
159+
fn try_warn<P>(&self, name: &str)
160+
where
161+
P: SystemParam,
162+
{
163+
if matches!(self, Self::Never) {
164+
return;
165+
}
166+
167+
bevy_utils::tracing::warn!(
168+
"{0} did not run because it requested inaccessible system parameter {1}",
169+
name,
170+
disqualified::ShortName::of::<P>()
171+
);
172+
}
173+
}
174+
175+
/// Trait for manipulating warn policy of systems.
176+
#[doc(hidden)]
177+
pub trait WithParamWarnPolicy<M, F>
178+
where
179+
M: 'static,
180+
F: SystemParamFunction<M>,
181+
Self: Sized,
182+
{
183+
/// Set warn policy.
184+
fn with_param_warn_policy(self, warn_policy: ParamWarnPolicy) -> FunctionSystem<M, F>;
185+
186+
/// Disable all param warnings.
187+
fn never_param_warn(self) -> FunctionSystem<M, F> {
188+
self.with_param_warn_policy(ParamWarnPolicy::Never)
189+
}
190+
}
191+
192+
impl<M, F> WithParamWarnPolicy<M, F> for F
193+
where
194+
M: 'static,
195+
F: SystemParamFunction<M>,
196+
{
197+
fn with_param_warn_policy(self, param_warn_policy: ParamWarnPolicy) -> FunctionSystem<M, F> {
198+
let mut system = IntoSystem::into_system(self);
199+
system.system_meta.set_param_warn_policy(param_warn_policy);
200+
system
201+
}
114202
}
115203

116204
// TODO: Actually use this in FunctionSystem. We should probably only do this once Systems are constructed using a World reference
@@ -657,9 +745,18 @@ where
657745
}
658746

659747
#[inline]
660-
unsafe fn validate_param_unsafe(&self, world: UnsafeWorldCell) -> bool {
748+
unsafe fn validate_param_unsafe(&mut self, world: UnsafeWorldCell) -> bool {
661749
let param_state = self.param_state.as_ref().expect(Self::PARAM_MESSAGE);
662-
F::Param::validate_param(param_state, &self.system_meta, world)
750+
// SAFETY:
751+
// - The caller has invoked `update_archetype_component_access`, which will panic
752+
// if the world does not match.
753+
// - All world accesses used by `F::Param` have been registered, so the caller
754+
// will ensure that there are no data access conflicts.
755+
let is_valid = unsafe { F::Param::validate_param(param_state, &self.system_meta, world) };
756+
if !is_valid {
757+
self.system_meta.advance_param_warn_policy();
758+
}
759+
is_valid
663760
}
664761

665762
#[inline]

crates/bevy_ecs/src/system/system.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ pub trait System: Send + Sync + 'static {
117117
/// - The method [`System::update_archetype_component_access`] must be called at some
118118
/// point before this one, with the same exact [`World`]. If [`System::update_archetype_component_access`]
119119
/// panics (or otherwise does not return for any reason), this method must not be called.
120-
unsafe fn validate_param_unsafe(&self, world: UnsafeWorldCell) -> bool;
120+
unsafe fn validate_param_unsafe(&mut self, world: UnsafeWorldCell) -> bool;
121121

122122
/// Safe version of [`System::validate_param_unsafe`].
123123
/// that runs on exclusive, single-threaded `world` pointer.

0 commit comments

Comments
 (0)