Skip to content

Commit 5289e18

Browse files
MiniaczQalice-i-cecilebushrat011899
authored
System param validation for observers, system registry and run once (#15526)
# Objective Fixes #15394 ## Solution Observers now validate params. System registry has a new error variant for when system running fails due to invalid parameters. Run once now returns a `Result<Out, RunOnceError>` instead of `Out`. This is more inline with system registry, which also returns a result. I'll address warning messages in #15500. ## Testing Added one test for each case. --- ## Migration Guide - `RunSystemOnce::run_system_once` and `RunSystemOnce::run_system_once_with` now return a `Result<Out>` instead of just `Out` --------- Co-authored-by: Alice Cecile <[email protected]> Co-authored-by: Zachary Harrold <[email protected]>
1 parent 39d96ef commit 5289e18

File tree

10 files changed

+199
-95
lines changed

10 files changed

+199
-95
lines changed

crates/bevy_ecs/src/observer/mod.rs

+21
Original file line numberDiff line numberDiff line change
@@ -1190,4 +1190,25 @@ mod tests {
11901190
// after the observer's spawn_empty.
11911191
world.despawn(ent);
11921192
}
1193+
1194+
#[test]
1195+
fn observer_invalid_params() {
1196+
#[derive(Event)]
1197+
struct EventA;
1198+
1199+
#[derive(Resource)]
1200+
struct ResA;
1201+
1202+
#[derive(Resource)]
1203+
struct ResB;
1204+
1205+
let mut world = World::new();
1206+
// This fails because `ResA` is not present in the world
1207+
world.observe(|_: Trigger<EventA>, _: Res<ResA>, mut commands: Commands| {
1208+
commands.insert_resource(ResB);
1209+
});
1210+
world.trigger(EventA);
1211+
1212+
assert!(world.get_resource::<ResB>().is_none());
1213+
}
11931214
}

crates/bevy_ecs/src/observer/runner.rs

+4-2
Original file line numberDiff line numberDiff line change
@@ -374,8 +374,10 @@ fn observer_system_runner<E: Event, B: Bundle, S: ObserverSystem<E, B>>(
374374
// - system is the same type erased system from above
375375
unsafe {
376376
(*system).update_archetype_component_access(world);
377-
(*system).run_unsafe(trigger, world);
378-
(*system).queue_deferred(world.into_deferred());
377+
if (*system).validate_param_unsafe(world) {
378+
(*system).run_unsafe(trigger, world);
379+
(*system).queue_deferred(world.into_deferred());
380+
}
379381
}
380382
}
381383

crates/bevy_ecs/src/system/builder.rs

+20-20
Original file line numberDiff line numberDiff line change
@@ -386,8 +386,8 @@ mod tests {
386386
.build_state(&mut world)
387387
.build_system(local_system);
388388

389-
let result = world.run_system_once(system);
390-
assert_eq!(result, 10);
389+
let output = world.run_system_once(system).unwrap();
390+
assert_eq!(output, 10);
391391
}
392392

393393
#[test]
@@ -403,8 +403,8 @@ mod tests {
403403
.build_state(&mut world)
404404
.build_system(query_system);
405405

406-
let result = world.run_system_once(system);
407-
assert_eq!(result, 1);
406+
let output = world.run_system_once(system).unwrap();
407+
assert_eq!(output, 1);
408408
}
409409

410410
#[test]
@@ -418,8 +418,8 @@ mod tests {
418418

419419
let system = (state,).build_state(&mut world).build_system(query_system);
420420

421-
let result = world.run_system_once(system);
422-
assert_eq!(result, 1);
421+
let output = world.run_system_once(system).unwrap();
422+
assert_eq!(output, 1);
423423
}
424424

425425
#[test]
@@ -433,8 +433,8 @@ mod tests {
433433
.build_state(&mut world)
434434
.build_system(multi_param_system);
435435

436-
let result = world.run_system_once(system);
437-
assert_eq!(result, 1);
436+
let output = world.run_system_once(system).unwrap();
437+
assert_eq!(output, 1);
438438
}
439439

440440
#[test]
@@ -464,8 +464,8 @@ mod tests {
464464
count
465465
});
466466

467-
let result = world.run_system_once(system);
468-
assert_eq!(result, 3);
467+
let output = world.run_system_once(system).unwrap();
468+
assert_eq!(output, 3);
469469
}
470470

471471
#[test]
@@ -479,8 +479,8 @@ mod tests {
479479
.build_state(&mut world)
480480
.build_system(|a, b| *a + *b + 1);
481481

482-
let result = world.run_system_once(system);
483-
assert_eq!(result, 1);
482+
let output = world.run_system_once(system).unwrap();
483+
assert_eq!(output, 1);
484484
}
485485

486486
#[test]
@@ -506,8 +506,8 @@ mod tests {
506506
params.p0().iter().count() + params.p1().iter().count()
507507
});
508508

509-
let result = world.run_system_once(system);
510-
assert_eq!(result, 5);
509+
let output = world.run_system_once(system).unwrap();
510+
assert_eq!(output, 5);
511511
}
512512

513513
#[test]
@@ -535,8 +535,8 @@ mod tests {
535535
count
536536
});
537537

538-
let result = world.run_system_once(system);
539-
assert_eq!(result, 5);
538+
let output = world.run_system_once(system).unwrap();
539+
assert_eq!(output, 5);
540540
}
541541

542542
#[test]
@@ -564,8 +564,8 @@ mod tests {
564564
},
565565
);
566566

567-
let result = world.run_system_once(system);
568-
assert_eq!(result, 4);
567+
let output = world.run_system_once(system).unwrap();
568+
assert_eq!(output, 4);
569569
}
570570

571571
#[derive(SystemParam)]
@@ -591,7 +591,7 @@ mod tests {
591591
.build_state(&mut world)
592592
.build_system(|param: CustomParam| *param.local + param.query.iter().count());
593593

594-
let result = world.run_system_once(system);
595-
assert_eq!(result, 101);
594+
let output = world.run_system_once(system).unwrap();
595+
assert_eq!(output, 101);
596596
}
597597
}

crates/bevy_ecs/src/system/system.rs

+50-14
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use bevy_utils::tracing::warn;
22
use core::fmt::Debug;
3+
use thiserror::Error;
34

45
use crate::{
56
archetype::ArchetypeComponentId,
@@ -269,7 +270,7 @@ where
269270
/// let mut world = World::default();
270271
/// let entity = world.run_system_once(|mut commands: Commands| {
271272
/// commands.spawn_empty().id()
272-
/// });
273+
/// }).unwrap();
273274
/// # assert!(world.get_entity(entity).is_some());
274275
/// ```
275276
///
@@ -289,7 +290,7 @@ where
289290
/// world.spawn(T(1));
290291
/// let count = world.run_system_once(|query: Query<&T>| {
291292
/// query.iter().filter(|t| t.0 == 1).count()
292-
/// });
293+
/// }).unwrap();
293294
///
294295
/// # assert_eq!(count, 2);
295296
/// ```
@@ -311,25 +312,25 @@ where
311312
/// world.spawn(T(0));
312313
/// world.spawn(T(1));
313314
/// world.spawn(T(1));
314-
/// let count = world.run_system_once(count);
315+
/// let count = world.run_system_once(count).unwrap();
315316
///
316317
/// # assert_eq!(count, 2);
317318
/// ```
318319
pub trait RunSystemOnce: Sized {
319-
/// Runs a system and applies its deferred parameters.
320-
fn run_system_once<T, Out, Marker>(self, system: T) -> Out
320+
/// Tries to run a system and apply its deferred parameters.
321+
fn run_system_once<T, Out, Marker>(self, system: T) -> Result<Out, RunSystemError>
321322
where
322323
T: IntoSystem<(), Out, Marker>,
323324
{
324325
self.run_system_once_with((), system)
325326
}
326327

327-
/// Runs a system with given input and applies its deferred parameters.
328+
/// Tries to run a system with given input and apply deferred parameters.
328329
fn run_system_once_with<T, In, Out, Marker>(
329330
self,
330331
input: SystemIn<'_, T::System>,
331332
system: T,
332-
) -> Out
333+
) -> Result<Out, RunSystemError>
333334
where
334335
T: IntoSystem<In, Out, Marker>,
335336
In: SystemInput;
@@ -340,14 +341,36 @@ impl RunSystemOnce for &mut World {
340341
self,
341342
input: SystemIn<'_, T::System>,
342343
system: T,
343-
) -> Out
344+
) -> Result<Out, RunSystemError>
344345
where
345346
T: IntoSystem<In, Out, Marker>,
346347
In: SystemInput,
347348
{
348349
let mut system: T::System = IntoSystem::into_system(system);
349350
system.initialize(self);
350-
system.run(input, self)
351+
if system.validate_param(self) {
352+
Ok(system.run(input, self))
353+
} else {
354+
Err(RunSystemError::InvalidParams(system.name()))
355+
}
356+
}
357+
}
358+
359+
/// Running system failed.
360+
#[derive(Error)]
361+
pub enum RunSystemError {
362+
/// System could not be run due to parameters that failed validation.
363+
///
364+
/// This can occur because the data required by the system was not present in the world.
365+
#[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.")]
366+
InvalidParams(Cow<'static, str>),
367+
}
368+
369+
impl Debug for RunSystemError {
370+
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
371+
match self {
372+
Self::InvalidParams(arg0) => f.debug_tuple("InvalidParams").field(arg0).finish(),
373+
}
351374
}
352375
}
353376

@@ -369,7 +392,7 @@ mod tests {
369392
}
370393

371394
let mut world = World::default();
372-
let n = world.run_system_once_with(1, system);
395+
let n = world.run_system_once_with(1, system).unwrap();
373396
assert_eq!(n, 2);
374397
assert_eq!(world.resource::<T>().0, 1);
375398
}
@@ -387,9 +410,9 @@ mod tests {
387410
let mut world = World::new();
388411
world.init_resource::<Counter>();
389412
assert_eq!(*world.resource::<Counter>(), Counter(0));
390-
world.run_system_once(count_up);
413+
world.run_system_once(count_up).unwrap();
391414
assert_eq!(*world.resource::<Counter>(), Counter(1));
392-
world.run_system_once(count_up);
415+
world.run_system_once(count_up).unwrap();
393416
assert_eq!(*world.resource::<Counter>(), Counter(2));
394417
}
395418

@@ -402,7 +425,7 @@ mod tests {
402425
fn command_processing() {
403426
let mut world = World::new();
404427
assert_eq!(world.entities.len(), 0);
405-
world.run_system_once(spawn_entity);
428+
world.run_system_once(spawn_entity).unwrap();
406429
assert_eq!(world.entities.len(), 1);
407430
}
408431

@@ -415,7 +438,20 @@ mod tests {
415438
let mut world = World::new();
416439
world.insert_non_send_resource(Counter(10));
417440
assert_eq!(*world.non_send_resource::<Counter>(), Counter(10));
418-
world.run_system_once(non_send_count_down);
441+
world.run_system_once(non_send_count_down).unwrap();
419442
assert_eq!(*world.non_send_resource::<Counter>(), Counter(9));
420443
}
444+
445+
#[test]
446+
fn run_system_once_invalid_params() {
447+
struct T;
448+
impl Resource for T {}
449+
fn system(_: Res<T>) {}
450+
451+
let mut world = World::default();
452+
// This fails because `T` has not been added to the world yet.
453+
let result = world.run_system_once(system);
454+
455+
assert!(matches!(result, Err(RunSystemError::InvalidParams(_))));
456+
}
421457
}

crates/bevy_ecs/src/system/system_name.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ mod tests {
141141
let mut world = World::default();
142142
let system =
143143
IntoSystem::into_system(|name: SystemName| name.name().to_owned()).with_name("testing");
144-
let name = world.run_system_once(system);
144+
let name = world.run_system_once(system).unwrap();
145145
assert_eq!(name, "testing");
146146
}
147147

@@ -151,7 +151,7 @@ mod tests {
151151
let system =
152152
IntoSystem::into_system(|_world: &mut World, name: SystemName| name.name().to_owned())
153153
.with_name("testing");
154-
let name = world.run_system_once(system);
154+
let name = world.run_system_once(system).unwrap();
155155
assert_eq!(name, "testing");
156156
}
157157
}

0 commit comments

Comments
 (0)