Replace ValidationOutcome with Result (#18541)
# Objective Make it easier to short-circuit system parameter validation. Simplify the API surface by combining `ValidationOutcome` with `SystemParamValidationError`. ## Solution Replace `ValidationOutcome` with `Result<(), SystemParamValidationError>`. Move the docs from `ValidationOutcome` to `SystemParamValidationError`. Add a `skipped` field to `SystemParamValidationError` to distinguish the `Skipped` and `Invalid` variants. Use the `?` operator to short-circuit validation in tuples of system params.
This commit is contained in:
parent
9705eaef02
commit
765e5842cd
@ -429,7 +429,7 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream {
|
|||||||
state: &'s Self::State,
|
state: &'s Self::State,
|
||||||
system_meta: &#path::system::SystemMeta,
|
system_meta: &#path::system::SystemMeta,
|
||||||
world: #path::world::unsafe_world_cell::UnsafeWorldCell<'w>,
|
world: #path::world::unsafe_world_cell::UnsafeWorldCell<'w>,
|
||||||
) -> #path::system::ValidationOutcome {
|
) -> Result<(), #path::system::SystemParamValidationError> {
|
||||||
<(#(#tuple_types,)*) as #path::system::SystemParam>::validate_param(&state.state, system_meta, world)
|
<(#(#tuple_types,)*) as #path::system::SystemParam>::validate_param(&state.state, system_meta, world)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ use crate::{
|
|||||||
observer::{ObserverDescriptor, ObserverTrigger},
|
observer::{ObserverDescriptor, ObserverTrigger},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
query::DebugCheckedUnwrap,
|
query::DebugCheckedUnwrap,
|
||||||
system::{IntoObserverSystem, ObserverSystem, SystemParamValidationError, ValidationOutcome},
|
system::{IntoObserverSystem, ObserverSystem},
|
||||||
world::DeferredWorld,
|
world::DeferredWorld,
|
||||||
};
|
};
|
||||||
use bevy_ptr::PtrMut;
|
use bevy_ptr::PtrMut;
|
||||||
@ -406,7 +406,7 @@ fn observer_system_runner<E: Event, B: Bundle, S: ObserverSystem<E, B>>(
|
|||||||
unsafe {
|
unsafe {
|
||||||
(*system).update_archetype_component_access(world);
|
(*system).update_archetype_component_access(world);
|
||||||
match (*system).validate_param_unsafe(world) {
|
match (*system).validate_param_unsafe(world) {
|
||||||
ValidationOutcome::Valid => {
|
Ok(()) => {
|
||||||
if let Err(err) = (*system).run_unsafe(trigger, world) {
|
if let Err(err) = (*system).run_unsafe(trigger, world) {
|
||||||
error_handler(
|
error_handler(
|
||||||
err,
|
err,
|
||||||
@ -418,14 +418,17 @@ fn observer_system_runner<E: Event, B: Bundle, S: ObserverSystem<E, B>>(
|
|||||||
};
|
};
|
||||||
(*system).queue_deferred(world.into_deferred());
|
(*system).queue_deferred(world.into_deferred());
|
||||||
}
|
}
|
||||||
ValidationOutcome::Invalid => error_handler(
|
Err(e) => {
|
||||||
SystemParamValidationError.into(),
|
if !e.skipped {
|
||||||
|
error_handler(
|
||||||
|
e.into(),
|
||||||
ErrorContext::Observer {
|
ErrorContext::Observer {
|
||||||
name: (*system).name(),
|
name: (*system).name(),
|
||||||
last_run: (*system).get_last_run(),
|
last_run: (*system).get_last_run(),
|
||||||
},
|
},
|
||||||
),
|
);
|
||||||
ValidationOutcome::Skipped => (),
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,7 @@ use crate::{
|
|||||||
prelude::{IntoSystemSet, SystemSet},
|
prelude::{IntoSystemSet, SystemSet},
|
||||||
query::Access,
|
query::Access,
|
||||||
schedule::{BoxedCondition, InternedSystemSet, NodeId, SystemTypeSet},
|
schedule::{BoxedCondition, InternedSystemSet, NodeId, SystemTypeSet},
|
||||||
system::{ScheduleSystem, System, SystemIn, ValidationOutcome},
|
system::{ScheduleSystem, System, SystemIn, SystemParamValidationError},
|
||||||
world::{unsafe_world_cell::UnsafeWorldCell, DeferredWorld, World},
|
world::{unsafe_world_cell::UnsafeWorldCell, DeferredWorld, World},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -221,10 +221,13 @@ impl System for ApplyDeferred {
|
|||||||
|
|
||||||
fn queue_deferred(&mut self, _world: DeferredWorld) {}
|
fn queue_deferred(&mut self, _world: DeferredWorld) {}
|
||||||
|
|
||||||
unsafe fn validate_param_unsafe(&mut self, _world: UnsafeWorldCell) -> ValidationOutcome {
|
unsafe fn validate_param_unsafe(
|
||||||
|
&mut self,
|
||||||
|
_world: UnsafeWorldCell,
|
||||||
|
) -> Result<(), SystemParamValidationError> {
|
||||||
// This system is always valid to run because it doesn't do anything,
|
// This system is always valid to run because it doesn't do anything,
|
||||||
// and only used as a marker for the executor.
|
// and only used as a marker for the executor.
|
||||||
ValidationOutcome::Valid
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn initialize(&mut self, _world: &mut World) {}
|
fn initialize(&mut self, _world: &mut World) {}
|
||||||
|
@ -18,7 +18,7 @@ use crate::{
|
|||||||
prelude::Resource,
|
prelude::Resource,
|
||||||
query::Access,
|
query::Access,
|
||||||
schedule::{is_apply_deferred, BoxedCondition, ExecutorKind, SystemExecutor, SystemSchedule},
|
schedule::{is_apply_deferred, BoxedCondition, ExecutorKind, SystemExecutor, SystemSchedule},
|
||||||
system::{ScheduleSystem, SystemParamValidationError, ValidationOutcome},
|
system::ScheduleSystem,
|
||||||
world::{unsafe_world_cell::UnsafeWorldCell, World},
|
world::{unsafe_world_cell::UnsafeWorldCell, World},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -582,18 +582,19 @@ impl ExecutorState {
|
|||||||
// required by the system.
|
// required by the system.
|
||||||
// - `update_archetype_component_access` has been called for system.
|
// - `update_archetype_component_access` has been called for system.
|
||||||
let valid_params = match unsafe { system.validate_param_unsafe(world) } {
|
let valid_params = match unsafe { system.validate_param_unsafe(world) } {
|
||||||
ValidationOutcome::Valid => true,
|
Ok(()) => true,
|
||||||
ValidationOutcome::Invalid => {
|
Err(e) => {
|
||||||
|
if !e.skipped {
|
||||||
error_handler(
|
error_handler(
|
||||||
SystemParamValidationError.into(),
|
e.into(),
|
||||||
ErrorContext::System {
|
ErrorContext::System {
|
||||||
name: system.name(),
|
name: system.name(),
|
||||||
last_run: system.get_last_run(),
|
last_run: system.get_last_run(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
}
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
ValidationOutcome::Skipped => false,
|
|
||||||
};
|
};
|
||||||
if !valid_params {
|
if !valid_params {
|
||||||
self.skipped_systems.insert(system_index);
|
self.skipped_systems.insert(system_index);
|
||||||
@ -796,18 +797,19 @@ unsafe fn evaluate_and_fold_conditions(
|
|||||||
// required by the condition.
|
// required by the condition.
|
||||||
// - `update_archetype_component_access` has been called for condition.
|
// - `update_archetype_component_access` has been called for condition.
|
||||||
match unsafe { condition.validate_param_unsafe(world) } {
|
match unsafe { condition.validate_param_unsafe(world) } {
|
||||||
ValidationOutcome::Valid => (),
|
Ok(()) => (),
|
||||||
ValidationOutcome::Invalid => {
|
Err(e) => {
|
||||||
|
if !e.skipped {
|
||||||
error_handler(
|
error_handler(
|
||||||
SystemParamValidationError.into(),
|
e.into(),
|
||||||
ErrorContext::System {
|
ErrorContext::System {
|
||||||
name: condition.name(),
|
name: condition.name(),
|
||||||
last_run: condition.get_last_run(),
|
last_run: condition.get_last_run(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
ValidationOutcome::Skipped => return false,
|
|
||||||
}
|
}
|
||||||
// SAFETY:
|
// SAFETY:
|
||||||
// - The caller ensures that `world` has permission to read any data
|
// - The caller ensures that `world` has permission to read any data
|
||||||
|
@ -12,7 +12,6 @@ use crate::{
|
|||||||
schedule::{
|
schedule::{
|
||||||
executor::is_apply_deferred, BoxedCondition, ExecutorKind, SystemExecutor, SystemSchedule,
|
executor::is_apply_deferred, BoxedCondition, ExecutorKind, SystemExecutor, SystemSchedule,
|
||||||
},
|
},
|
||||||
system::{SystemParamValidationError, ValidationOutcome},
|
|
||||||
world::World,
|
world::World,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -89,18 +88,19 @@ impl SystemExecutor for SimpleExecutor {
|
|||||||
let system = &mut schedule.systems[system_index];
|
let system = &mut schedule.systems[system_index];
|
||||||
if should_run {
|
if should_run {
|
||||||
let valid_params = match system.validate_param(world) {
|
let valid_params = match system.validate_param(world) {
|
||||||
ValidationOutcome::Valid => true,
|
Ok(()) => true,
|
||||||
ValidationOutcome::Invalid => {
|
Err(e) => {
|
||||||
|
if !e.skipped {
|
||||||
error_handler(
|
error_handler(
|
||||||
SystemParamValidationError.into(),
|
e.into(),
|
||||||
ErrorContext::System {
|
ErrorContext::System {
|
||||||
name: system.name(),
|
name: system.name(),
|
||||||
last_run: system.get_last_run(),
|
last_run: system.get_last_run(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
}
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
ValidationOutcome::Skipped => false,
|
|
||||||
};
|
};
|
||||||
should_run &= valid_params;
|
should_run &= valid_params;
|
||||||
}
|
}
|
||||||
@ -177,18 +177,19 @@ fn evaluate_and_fold_conditions(conditions: &mut [BoxedCondition], world: &mut W
|
|||||||
.iter_mut()
|
.iter_mut()
|
||||||
.map(|condition| {
|
.map(|condition| {
|
||||||
match condition.validate_param(world) {
|
match condition.validate_param(world) {
|
||||||
ValidationOutcome::Valid => (),
|
Ok(()) => (),
|
||||||
ValidationOutcome::Invalid => {
|
Err(e) => {
|
||||||
|
if !e.skipped {
|
||||||
error_handler(
|
error_handler(
|
||||||
SystemParamValidationError.into(),
|
e.into(),
|
||||||
ErrorContext::System {
|
ErrorContext::System {
|
||||||
name: condition.name(),
|
name: condition.name(),
|
||||||
last_run: condition.get_last_run(),
|
last_run: condition.get_last_run(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
ValidationOutcome::Skipped => return false,
|
|
||||||
}
|
}
|
||||||
__rust_begin_short_backtrace::readonly_run(&mut **condition, world)
|
__rust_begin_short_backtrace::readonly_run(&mut **condition, world)
|
||||||
})
|
})
|
||||||
|
@ -10,7 +10,6 @@ use std::eprintln;
|
|||||||
use crate::{
|
use crate::{
|
||||||
error::{default_error_handler, BevyError, ErrorContext},
|
error::{default_error_handler, BevyError, ErrorContext},
|
||||||
schedule::{is_apply_deferred, BoxedCondition, ExecutorKind, SystemExecutor, SystemSchedule},
|
schedule::{is_apply_deferred, BoxedCondition, ExecutorKind, SystemExecutor, SystemSchedule},
|
||||||
system::{SystemParamValidationError, ValidationOutcome},
|
|
||||||
world::World,
|
world::World,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -95,18 +94,19 @@ impl SystemExecutor for SingleThreadedExecutor {
|
|||||||
let system = &mut schedule.systems[system_index];
|
let system = &mut schedule.systems[system_index];
|
||||||
if should_run {
|
if should_run {
|
||||||
let valid_params = match system.validate_param(world) {
|
let valid_params = match system.validate_param(world) {
|
||||||
ValidationOutcome::Valid => true,
|
Ok(()) => true,
|
||||||
ValidationOutcome::Invalid => {
|
Err(e) => {
|
||||||
|
if !e.skipped {
|
||||||
error_handler(
|
error_handler(
|
||||||
SystemParamValidationError.into(),
|
e.into(),
|
||||||
ErrorContext::System {
|
ErrorContext::System {
|
||||||
name: system.name(),
|
name: system.name(),
|
||||||
last_run: system.get_last_run(),
|
last_run: system.get_last_run(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
}
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
ValidationOutcome::Skipped => false,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
should_run &= valid_params;
|
should_run &= valid_params;
|
||||||
@ -221,18 +221,19 @@ fn evaluate_and_fold_conditions(conditions: &mut [BoxedCondition], world: &mut W
|
|||||||
.iter_mut()
|
.iter_mut()
|
||||||
.map(|condition| {
|
.map(|condition| {
|
||||||
match condition.validate_param(world) {
|
match condition.validate_param(world) {
|
||||||
ValidationOutcome::Valid => (),
|
Ok(()) => (),
|
||||||
ValidationOutcome::Invalid => {
|
Err(e) => {
|
||||||
|
if !e.skipped {
|
||||||
error_handler(
|
error_handler(
|
||||||
SystemParamValidationError.into(),
|
e.into(),
|
||||||
ErrorContext::System {
|
ErrorContext::System {
|
||||||
name: condition.name(),
|
name: condition.name(),
|
||||||
last_run: condition.get_last_run(),
|
last_run: condition.get_last_run(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
ValidationOutcome::Skipped => return false,
|
|
||||||
}
|
}
|
||||||
__rust_begin_short_backtrace::readonly_run(&mut **condition, world)
|
__rust_begin_short_backtrace::readonly_run(&mut **condition, world)
|
||||||
})
|
})
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use alloc::{borrow::Cow, vec::Vec};
|
use alloc::{borrow::Cow, vec::Vec};
|
||||||
|
|
||||||
use super::{IntoSystem, ReadOnlySystem, System, ValidationOutcome};
|
use super::{IntoSystem, ReadOnlySystem, System, SystemParamValidationError};
|
||||||
use crate::{
|
use crate::{
|
||||||
schedule::InternedSystemSet,
|
schedule::InternedSystemSet,
|
||||||
system::{input::SystemInput, SystemIn},
|
system::{input::SystemInput, SystemIn},
|
||||||
@ -179,7 +179,10 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
unsafe fn validate_param_unsafe(&mut self, world: UnsafeWorldCell) -> ValidationOutcome {
|
unsafe fn validate_param_unsafe(
|
||||||
|
&mut self,
|
||||||
|
world: UnsafeWorldCell,
|
||||||
|
) -> Result<(), SystemParamValidationError> {
|
||||||
// SAFETY: Delegate to other `System` implementations.
|
// SAFETY: Delegate to other `System` implementations.
|
||||||
unsafe { self.system.validate_param_unsafe(world) }
|
unsafe { self.system.validate_param_unsafe(world) }
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@ use crate::{
|
|||||||
prelude::World,
|
prelude::World,
|
||||||
query::Access,
|
query::Access,
|
||||||
schedule::InternedSystemSet,
|
schedule::InternedSystemSet,
|
||||||
system::{input::SystemInput, SystemIn, ValidationOutcome},
|
system::{input::SystemInput, SystemIn, SystemParamValidationError},
|
||||||
world::unsafe_world_cell::UnsafeWorldCell,
|
world::unsafe_world_cell::UnsafeWorldCell,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -212,7 +212,10 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
unsafe fn validate_param_unsafe(&mut self, world: UnsafeWorldCell) -> ValidationOutcome {
|
unsafe fn validate_param_unsafe(
|
||||||
|
&mut self,
|
||||||
|
world: UnsafeWorldCell,
|
||||||
|
) -> Result<(), SystemParamValidationError> {
|
||||||
// SAFETY: Delegate to other `System` implementations.
|
// SAFETY: Delegate to other `System` implementations.
|
||||||
unsafe { self.a.validate_param_unsafe(world) }
|
unsafe { self.a.validate_param_unsafe(world) }
|
||||||
}
|
}
|
||||||
@ -431,18 +434,18 @@ where
|
|||||||
self.b.queue_deferred(world);
|
self.b.queue_deferred(world);
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn validate_param_unsafe(&mut self, world: UnsafeWorldCell) -> ValidationOutcome {
|
unsafe fn validate_param_unsafe(
|
||||||
|
&mut self,
|
||||||
|
world: UnsafeWorldCell,
|
||||||
|
) -> Result<(), SystemParamValidationError> {
|
||||||
// SAFETY: Delegate to other `System` implementations.
|
// SAFETY: Delegate to other `System` implementations.
|
||||||
unsafe { self.a.validate_param_unsafe(world) }
|
unsafe { self.a.validate_param_unsafe(world) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_param(&mut self, world: &World) -> ValidationOutcome {
|
fn validate_param(&mut self, world: &World) -> Result<(), SystemParamValidationError> {
|
||||||
// This follows the logic of `ValidationOutcome::combine`, but short-circuits
|
self.a.validate_param(world)?;
|
||||||
let validate_a = self.a.validate_param(world);
|
self.b.validate_param(world)?;
|
||||||
match validate_a {
|
Ok(())
|
||||||
ValidationOutcome::Valid => self.b.validate_param(world),
|
|
||||||
ValidationOutcome::Invalid | ValidationOutcome::Skipped => validate_a,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn initialize(&mut self, world: &mut World) {
|
fn initialize(&mut self, world: &mut World) {
|
||||||
|
@ -27,7 +27,7 @@ use crate::{
|
|||||||
schedule::ScheduleLabel,
|
schedule::ScheduleLabel,
|
||||||
system::{
|
system::{
|
||||||
Deferred, IntoObserverSystem, IntoSystem, RegisteredSystem, SystemId, SystemInput,
|
Deferred, IntoObserverSystem, IntoSystem, RegisteredSystem, SystemId, SystemInput,
|
||||||
ValidationOutcome,
|
SystemParamValidationError,
|
||||||
},
|
},
|
||||||
world::{
|
world::{
|
||||||
command_queue::RawCommandQueue, unsafe_world_cell::UnsafeWorldCell, CommandQueue,
|
command_queue::RawCommandQueue, unsafe_world_cell::UnsafeWorldCell, CommandQueue,
|
||||||
@ -182,7 +182,7 @@ const _: () = {
|
|||||||
state: &Self::State,
|
state: &Self::State,
|
||||||
system_meta: &bevy_ecs::system::SystemMeta,
|
system_meta: &bevy_ecs::system::SystemMeta,
|
||||||
world: UnsafeWorldCell,
|
world: UnsafeWorldCell,
|
||||||
) -> ValidationOutcome {
|
) -> Result<(), SystemParamValidationError> {
|
||||||
<(Deferred<CommandQueue>, &Entities) as bevy_ecs::system::SystemParam>::validate_param(
|
<(Deferred<CommandQueue>, &Entities) as bevy_ecs::system::SystemParam>::validate_param(
|
||||||
&state.state,
|
&state.state,
|
||||||
system_meta,
|
system_meta,
|
||||||
|
@ -14,7 +14,7 @@ use alloc::{borrow::Cow, vec, vec::Vec};
|
|||||||
use core::marker::PhantomData;
|
use core::marker::PhantomData;
|
||||||
use variadics_please::all_tuples;
|
use variadics_please::all_tuples;
|
||||||
|
|
||||||
use super::ValidationOutcome;
|
use super::SystemParamValidationError;
|
||||||
|
|
||||||
/// A function system that runs with exclusive [`World`] access.
|
/// A function system that runs with exclusive [`World`] access.
|
||||||
///
|
///
|
||||||
@ -156,9 +156,12 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
unsafe fn validate_param_unsafe(&mut self, _world: UnsafeWorldCell) -> ValidationOutcome {
|
unsafe fn validate_param_unsafe(
|
||||||
|
&mut self,
|
||||||
|
_world: UnsafeWorldCell,
|
||||||
|
) -> Result<(), SystemParamValidationError> {
|
||||||
// All exclusive system params are always available.
|
// All exclusive system params are always available.
|
||||||
ValidationOutcome::Valid
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -18,7 +18,7 @@ use variadics_please::all_tuples;
|
|||||||
#[cfg(feature = "trace")]
|
#[cfg(feature = "trace")]
|
||||||
use tracing::{info_span, Span};
|
use tracing::{info_span, Span};
|
||||||
|
|
||||||
use super::{IntoSystem, ReadOnlySystem, SystemParamBuilder, ValidationOutcome};
|
use super::{IntoSystem, ReadOnlySystem, SystemParamBuilder, SystemParamValidationError};
|
||||||
|
|
||||||
/// The metadata of a [`System`].
|
/// The metadata of a [`System`].
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -417,7 +417,10 @@ impl<Param: SystemParam> SystemState<Param> {
|
|||||||
/// - The passed [`UnsafeWorldCell`] must have read-only access to
|
/// - The passed [`UnsafeWorldCell`] must have read-only access to
|
||||||
/// world data in `archetype_component_access`.
|
/// world data in `archetype_component_access`.
|
||||||
/// - `world` must be the same [`World`] that was used to initialize [`state`](SystemParam::init_state).
|
/// - `world` must be the same [`World`] that was used to initialize [`state`](SystemParam::init_state).
|
||||||
pub unsafe fn validate_param(state: &Self, world: UnsafeWorldCell) -> ValidationOutcome {
|
pub unsafe fn validate_param(
|
||||||
|
state: &Self,
|
||||||
|
world: UnsafeWorldCell,
|
||||||
|
) -> Result<(), SystemParamValidationError> {
|
||||||
// SAFETY: Delegated to existing `SystemParam` implementations.
|
// SAFETY: Delegated to existing `SystemParam` implementations.
|
||||||
unsafe { Param::validate_param(&state.param_state, &state.meta, world) }
|
unsafe { Param::validate_param(&state.param_state, &state.meta, world) }
|
||||||
}
|
}
|
||||||
@ -747,7 +750,10 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
unsafe fn validate_param_unsafe(&mut self, world: UnsafeWorldCell) -> ValidationOutcome {
|
unsafe fn validate_param_unsafe(
|
||||||
|
&mut self,
|
||||||
|
world: UnsafeWorldCell,
|
||||||
|
) -> Result<(), SystemParamValidationError> {
|
||||||
let param_state = &self.state.as_ref().expect(Self::ERROR_UNINITIALIZED).param;
|
let param_state = &self.state.as_ref().expect(Self::ERROR_UNINITIALIZED).param;
|
||||||
// SAFETY:
|
// SAFETY:
|
||||||
// - The caller has invoked `update_archetype_component_access`, which will panic
|
// - The caller has invoked `update_archetype_component_access`, which will panic
|
||||||
|
@ -12,7 +12,7 @@ use crate::{
|
|||||||
world::{unsafe_world_cell::UnsafeWorldCell, DeferredWorld, World},
|
world::{unsafe_world_cell::UnsafeWorldCell, DeferredWorld, World},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{IntoSystem, ValidationOutcome};
|
use super::{IntoSystem, SystemParamValidationError};
|
||||||
|
|
||||||
/// Implemented for [`System`]s that have a [`Trigger`] as the first argument.
|
/// Implemented for [`System`]s that have a [`Trigger`] as the first argument.
|
||||||
pub trait ObserverSystem<E: 'static, B: Bundle, Out = Result>:
|
pub trait ObserverSystem<E: 'static, B: Bundle, Out = Result>:
|
||||||
@ -155,7 +155,10 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
unsafe fn validate_param_unsafe(&mut self, world: UnsafeWorldCell) -> ValidationOutcome {
|
unsafe fn validate_param_unsafe(
|
||||||
|
&mut self,
|
||||||
|
world: UnsafeWorldCell,
|
||||||
|
) -> Result<(), SystemParamValidationError> {
|
||||||
self.observer.validate_param_unsafe(world)
|
self.observer.validate_param_unsafe(world)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2574,7 +2574,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 the system to be skipped, according to the rules laid out in [`ValidationOutcome`](crate::system::ValidationOutcome).
|
/// This will cause the system to be skipped, according to the rules laid out in [`SystemParamValidationError`](crate::system::SystemParamValidationError).
|
||||||
///
|
///
|
||||||
/// 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.
|
||||||
///
|
///
|
||||||
@ -2610,7 +2610,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 the system to be skipped, according to the rules laid out in [`ValidationOutcome`](crate::system::ValidationOutcome).
|
/// This will cause the system to be skipped, according to the rules laid out in [`SystemParamValidationError`](crate::system::SystemParamValidationError).
|
||||||
///
|
///
|
||||||
/// 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)
|
||||||
|
@ -9,7 +9,7 @@ use crate::{
|
|||||||
world::{unsafe_world_cell::UnsafeWorldCell, DeferredWorld, World},
|
world::{unsafe_world_cell::UnsafeWorldCell, DeferredWorld, World},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{IntoSystem, ValidationOutcome};
|
use super::{IntoSystem, SystemParamValidationError};
|
||||||
|
|
||||||
/// A wrapper system to change a system that returns `()` to return `Ok(())` to make it into a [`ScheduleSystem`]
|
/// A wrapper system to change a system that returns `()` to return `Ok(())` to make it into a [`ScheduleSystem`]
|
||||||
pub struct InfallibleSystemWrapper<S: System<In = (), Out = ()>>(S);
|
pub struct InfallibleSystemWrapper<S: System<In = (), Out = ()>>(S);
|
||||||
@ -82,7 +82,10 @@ impl<S: System<In = (), Out = ()>> System for InfallibleSystemWrapper<S> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
unsafe fn validate_param_unsafe(&mut self, world: UnsafeWorldCell) -> ValidationOutcome {
|
unsafe fn validate_param_unsafe(
|
||||||
|
&mut self,
|
||||||
|
world: UnsafeWorldCell,
|
||||||
|
) -> Result<(), SystemParamValidationError> {
|
||||||
self.0.validate_param_unsafe(world)
|
self.0.validate_param_unsafe(world)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@ use crate::{
|
|||||||
use alloc::{borrow::Cow, boxed::Box, vec::Vec};
|
use alloc::{borrow::Cow, boxed::Box, vec::Vec};
|
||||||
use core::any::TypeId;
|
use core::any::TypeId;
|
||||||
|
|
||||||
use super::{IntoSystem, ValidationOutcome};
|
use super::{IntoSystem, SystemParamValidationError};
|
||||||
|
|
||||||
/// An ECS system that can be added to a [`Schedule`](crate::schedule::Schedule)
|
/// An ECS system that can be added to a [`Schedule`](crate::schedule::Schedule)
|
||||||
///
|
///
|
||||||
@ -132,11 +132,14 @@ pub trait System: Send + Sync + 'static {
|
|||||||
/// - The method [`System::update_archetype_component_access`] must be called at some
|
/// - The method [`System::update_archetype_component_access`] must be called at some
|
||||||
/// point before this one, with the same exact [`World`]. If [`System::update_archetype_component_access`]
|
/// point before this one, with the same exact [`World`]. If [`System::update_archetype_component_access`]
|
||||||
/// panics (or otherwise does not return for any reason), this method must not be called.
|
/// panics (or otherwise does not return for any reason), this method must not be called.
|
||||||
unsafe fn validate_param_unsafe(&mut self, world: UnsafeWorldCell) -> ValidationOutcome;
|
unsafe fn validate_param_unsafe(
|
||||||
|
&mut self,
|
||||||
|
world: UnsafeWorldCell,
|
||||||
|
) -> Result<(), SystemParamValidationError>;
|
||||||
|
|
||||||
/// Safe version of [`System::validate_param_unsafe`].
|
/// Safe version of [`System::validate_param_unsafe`].
|
||||||
/// that runs on exclusive, single-threaded `world` pointer.
|
/// that runs on exclusive, single-threaded `world` pointer.
|
||||||
fn validate_param(&mut self, world: &World) -> ValidationOutcome {
|
fn validate_param(&mut self, world: &World) -> Result<(), SystemParamValidationError> {
|
||||||
let world_cell = world.as_unsafe_world_cell_readonly();
|
let world_cell = world.as_unsafe_world_cell_readonly();
|
||||||
self.update_archetype_component_access(world_cell);
|
self.update_archetype_component_access(world_cell);
|
||||||
// SAFETY:
|
// SAFETY:
|
||||||
@ -364,12 +367,10 @@ impl RunSystemOnce for &mut World {
|
|||||||
let mut system: T::System = IntoSystem::into_system(system);
|
let mut system: T::System = IntoSystem::into_system(system);
|
||||||
system.initialize(self);
|
system.initialize(self);
|
||||||
match system.validate_param(self) {
|
match system.validate_param(self) {
|
||||||
ValidationOutcome::Valid => Ok(system.run(input, self)),
|
Ok(()) => Ok(system.run(input, self)),
|
||||||
// TODO: should we expse the fact that the system was skipped to the user?
|
// TODO: should we expse the fact that the system was skipped to the user?
|
||||||
// Should we somehow unify this better with system error handling?
|
// Should we somehow unify this better with system error handling?
|
||||||
ValidationOutcome::Invalid | ValidationOutcome::Skipped => {
|
Err(_) => Err(RunSystemError::InvalidParams(system.name())),
|
||||||
Err(RunSystemError::InvalidParams(system.name()))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -275,8 +275,8 @@ pub unsafe trait SystemParam: Sized {
|
|||||||
state: &Self::State,
|
state: &Self::State,
|
||||||
system_meta: &SystemMeta,
|
system_meta: &SystemMeta,
|
||||||
world: UnsafeWorldCell,
|
world: UnsafeWorldCell,
|
||||||
) -> ValidationOutcome {
|
) -> Result<(), SystemParamValidationError> {
|
||||||
ValidationOutcome::Valid
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a parameter to be passed into a [`SystemParamFunction`](super::SystemParamFunction).
|
/// Creates a parameter to be passed into a [`SystemParamFunction`](super::SystemParamFunction).
|
||||||
@ -310,43 +310,6 @@ unsafe impl<'w, 's, D: ReadOnlyQueryData + 'static, F: QueryFilter + 'static> Re
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The outcome of system / system param validation,
|
|
||||||
/// used by system executors to determine what to do with a system.
|
|
||||||
///
|
|
||||||
/// The behavior of each system parameter can be controlled by returning a different outcome from [`SystemParam::validate_param`].
|
|
||||||
pub enum ValidationOutcome {
|
|
||||||
/// All system parameters were validated successfully and the system can be run.
|
|
||||||
Valid,
|
|
||||||
/// At least one system parameter failed validation, and an error must be handled.
|
|
||||||
/// By default, this will result in a panic. See [`crate::error`] for more information.
|
|
||||||
///
|
|
||||||
/// This is the default behavior, and is suitable for system params that should *always* be valid,
|
|
||||||
/// either because sensible fallback behavior exists (like [`Query`] or because
|
|
||||||
/// failures in validation should be considered a bug in the user's logic that must be immediately addressed (like [`Res`]).
|
|
||||||
Invalid,
|
|
||||||
/// At least one system parameter failed validation, but the system should be skipped.
|
|
||||||
/// This is suitable for system params that are intended to only operate in certain application states, such as [`Single`].
|
|
||||||
Skipped,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ValidationOutcome {
|
|
||||||
/// Combines two [`ValidationOutcome`]s, returning the most severe one.
|
|
||||||
///
|
|
||||||
/// If either outcome is [`ValidationOutcome::Invalid`], the result will be [`ValidationOutcome::Invalid`].
|
|
||||||
/// Otherwise, if either outcome is [`ValidationOutcome::Skipped`], the result will be [`ValidationOutcome::Skipped`].
|
|
||||||
/// Finally, if both outcomes are [`ValidationOutcome::Valid`], the result will be [`ValidationOutcome::Valid`].
|
|
||||||
///
|
|
||||||
/// When called, you should typically return early if the result is [`ValidationOutcome::Invalid`] or [`ValidationOutcome::Skipped`],
|
|
||||||
/// to avoid unnecessary work validating irrelevant system parameters.
|
|
||||||
pub const fn combine(self, other: Self) -> Self {
|
|
||||||
match (self, other) {
|
|
||||||
(Self::Invalid, _) | (_, Self::Invalid) => Self::Invalid,
|
|
||||||
(Self::Skipped, _) | (_, Self::Skipped) => Self::Skipped,
|
|
||||||
(Self::Valid, Self::Valid) => Self::Valid,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SAFETY: Relevant query ComponentId and ArchetypeComponentId access is applied to SystemMeta. If
|
// SAFETY: Relevant query ComponentId and ArchetypeComponentId access is applied to SystemMeta. If
|
||||||
// this Query conflicts with any prior access, a panic will occur.
|
// this Query conflicts with any prior access, a panic will occur.
|
||||||
unsafe impl<D: QueryData + 'static, F: QueryFilter + 'static> SystemParam for Query<'_, '_, D, F> {
|
unsafe impl<D: QueryData + 'static, F: QueryFilter + 'static> SystemParam for Query<'_, '_, D, F> {
|
||||||
@ -465,7 +428,7 @@ unsafe impl<'a, D: QueryData + 'static, F: QueryFilter + 'static> SystemParam fo
|
|||||||
state: &Self::State,
|
state: &Self::State,
|
||||||
system_meta: &SystemMeta,
|
system_meta: &SystemMeta,
|
||||||
world: UnsafeWorldCell,
|
world: UnsafeWorldCell,
|
||||||
) -> ValidationOutcome {
|
) -> Result<(), SystemParamValidationError> {
|
||||||
// SAFETY: State ensures that the components it accesses are not mutably accessible elsewhere
|
// SAFETY: State ensures that the components it accesses are not mutably accessible elsewhere
|
||||||
// and the query is read only.
|
// and the query is read only.
|
||||||
// The caller ensures the world matches the one used in init_state.
|
// The caller ensures the world matches the one used in init_state.
|
||||||
@ -477,8 +440,8 @@ unsafe impl<'a, D: QueryData + 'static, F: QueryFilter + 'static> SystemParam fo
|
|||||||
)
|
)
|
||||||
};
|
};
|
||||||
match query.single_inner() {
|
match query.single_inner() {
|
||||||
Ok(_) => ValidationOutcome::Valid,
|
Ok(_) => Ok(()),
|
||||||
Err(_) => ValidationOutcome::Skipped,
|
Err(_) => Err(SystemParamValidationError::skipped()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -532,7 +495,7 @@ unsafe impl<'a, D: QueryData + 'static, F: QueryFilter + 'static> SystemParam
|
|||||||
state: &Self::State,
|
state: &Self::State,
|
||||||
system_meta: &SystemMeta,
|
system_meta: &SystemMeta,
|
||||||
world: UnsafeWorldCell,
|
world: UnsafeWorldCell,
|
||||||
) -> ValidationOutcome {
|
) -> Result<(), SystemParamValidationError> {
|
||||||
// SAFETY: State ensures that the components it accesses are not mutably accessible elsewhere
|
// SAFETY: State ensures that the components it accesses are not mutably accessible elsewhere
|
||||||
// and the query is read only.
|
// and the query is read only.
|
||||||
// The caller ensures the world matches the one used in init_state.
|
// The caller ensures the world matches the one used in init_state.
|
||||||
@ -544,8 +507,10 @@ unsafe impl<'a, D: QueryData + 'static, F: QueryFilter + 'static> SystemParam
|
|||||||
)
|
)
|
||||||
};
|
};
|
||||||
match query.single_inner() {
|
match query.single_inner() {
|
||||||
Ok(_) | Err(QuerySingleError::NoEntities(_)) => ValidationOutcome::Valid,
|
Ok(_) | Err(QuerySingleError::NoEntities(_)) => Ok(()),
|
||||||
Err(QuerySingleError::MultipleEntities(_)) => ValidationOutcome::Skipped,
|
Err(QuerySingleError::MultipleEntities(_)) => {
|
||||||
|
Err(SystemParamValidationError::skipped())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -600,7 +565,7 @@ unsafe impl<D: QueryData + 'static, F: QueryFilter + 'static> SystemParam
|
|||||||
state: &Self::State,
|
state: &Self::State,
|
||||||
system_meta: &SystemMeta,
|
system_meta: &SystemMeta,
|
||||||
world: UnsafeWorldCell,
|
world: UnsafeWorldCell,
|
||||||
) -> ValidationOutcome {
|
) -> Result<(), SystemParamValidationError> {
|
||||||
// SAFETY:
|
// SAFETY:
|
||||||
// - We have read-only access to the components accessed by query.
|
// - We have read-only access to the components accessed by query.
|
||||||
// - The caller ensures the world matches the one used in init_state.
|
// - The caller ensures the world matches the one used in init_state.
|
||||||
@ -612,9 +577,9 @@ unsafe impl<D: QueryData + 'static, F: QueryFilter + 'static> SystemParam
|
|||||||
)
|
)
|
||||||
};
|
};
|
||||||
if query.is_empty() {
|
if query.is_empty() {
|
||||||
ValidationOutcome::Skipped
|
Err(SystemParamValidationError::skipped())
|
||||||
} else {
|
} else {
|
||||||
ValidationOutcome::Valid
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -810,7 +775,7 @@ macro_rules! impl_param_set {
|
|||||||
state: &'s Self::State,
|
state: &'s Self::State,
|
||||||
system_meta: &SystemMeta,
|
system_meta: &SystemMeta,
|
||||||
world: UnsafeWorldCell<'w>,
|
world: UnsafeWorldCell<'w>,
|
||||||
) -> ValidationOutcome {
|
) -> Result<(), SystemParamValidationError> {
|
||||||
<($($param,)*) as SystemParam>::validate_param(state, system_meta, world)
|
<($($param,)*) as SystemParam>::validate_param(state, system_meta, world)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -888,16 +853,16 @@ unsafe impl<'a, T: Resource> SystemParam for Res<'a, T> {
|
|||||||
&component_id: &Self::State,
|
&component_id: &Self::State,
|
||||||
_system_meta: &SystemMeta,
|
_system_meta: &SystemMeta,
|
||||||
world: UnsafeWorldCell,
|
world: UnsafeWorldCell,
|
||||||
) -> ValidationOutcome {
|
) -> Result<(), SystemParamValidationError> {
|
||||||
// SAFETY: Read-only access to resource metadata.
|
// SAFETY: Read-only access to resource metadata.
|
||||||
if unsafe { world.storages() }
|
if unsafe { world.storages() }
|
||||||
.resources
|
.resources
|
||||||
.get(component_id)
|
.get(component_id)
|
||||||
.is_some_and(ResourceData::is_present)
|
.is_some_and(ResourceData::is_present)
|
||||||
{
|
{
|
||||||
ValidationOutcome::Valid
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
ValidationOutcome::Invalid
|
Err(SystemParamValidationError::invalid())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1001,16 +966,16 @@ unsafe impl<'a, T: Resource> SystemParam for ResMut<'a, T> {
|
|||||||
&component_id: &Self::State,
|
&component_id: &Self::State,
|
||||||
_system_meta: &SystemMeta,
|
_system_meta: &SystemMeta,
|
||||||
world: UnsafeWorldCell,
|
world: UnsafeWorldCell,
|
||||||
) -> ValidationOutcome {
|
) -> Result<(), SystemParamValidationError> {
|
||||||
// SAFETY: Read-only access to resource metadata.
|
// SAFETY: Read-only access to resource metadata.
|
||||||
if unsafe { world.storages() }
|
if unsafe { world.storages() }
|
||||||
.resources
|
.resources
|
||||||
.get(component_id)
|
.get(component_id)
|
||||||
.is_some_and(ResourceData::is_present)
|
.is_some_and(ResourceData::is_present)
|
||||||
{
|
{
|
||||||
ValidationOutcome::Valid
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
ValidationOutcome::Invalid
|
Err(SystemParamValidationError::invalid())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1572,16 +1537,16 @@ unsafe impl<'a, T: 'static> SystemParam for NonSend<'a, T> {
|
|||||||
&component_id: &Self::State,
|
&component_id: &Self::State,
|
||||||
_system_meta: &SystemMeta,
|
_system_meta: &SystemMeta,
|
||||||
world: UnsafeWorldCell,
|
world: UnsafeWorldCell,
|
||||||
) -> ValidationOutcome {
|
) -> Result<(), SystemParamValidationError> {
|
||||||
// SAFETY: Read-only access to resource metadata.
|
// SAFETY: Read-only access to resource metadata.
|
||||||
if unsafe { world.storages() }
|
if unsafe { world.storages() }
|
||||||
.non_send_resources
|
.non_send_resources
|
||||||
.get(component_id)
|
.get(component_id)
|
||||||
.is_some_and(ResourceData::is_present)
|
.is_some_and(ResourceData::is_present)
|
||||||
{
|
{
|
||||||
ValidationOutcome::Valid
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
ValidationOutcome::Invalid
|
Err(SystemParamValidationError::invalid())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1682,16 +1647,16 @@ unsafe impl<'a, T: 'static> SystemParam for NonSendMut<'a, T> {
|
|||||||
&component_id: &Self::State,
|
&component_id: &Self::State,
|
||||||
_system_meta: &SystemMeta,
|
_system_meta: &SystemMeta,
|
||||||
world: UnsafeWorldCell,
|
world: UnsafeWorldCell,
|
||||||
) -> ValidationOutcome {
|
) -> Result<(), SystemParamValidationError> {
|
||||||
// SAFETY: Read-only access to resource metadata.
|
// SAFETY: Read-only access to resource metadata.
|
||||||
if unsafe { world.storages() }
|
if unsafe { world.storages() }
|
||||||
.non_send_resources
|
.non_send_resources
|
||||||
.get(component_id)
|
.get(component_id)
|
||||||
.is_some_and(ResourceData::is_present)
|
.is_some_and(ResourceData::is_present)
|
||||||
{
|
{
|
||||||
ValidationOutcome::Valid
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
ValidationOutcome::Invalid
|
Err(SystemParamValidationError::invalid())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1900,23 +1865,11 @@ unsafe impl<T: SystemParam> SystemParam for Vec<T> {
|
|||||||
state: &Self::State,
|
state: &Self::State,
|
||||||
system_meta: &SystemMeta,
|
system_meta: &SystemMeta,
|
||||||
world: UnsafeWorldCell,
|
world: UnsafeWorldCell,
|
||||||
) -> ValidationOutcome {
|
) -> Result<(), SystemParamValidationError> {
|
||||||
let mut validation_state = ValidationOutcome::Valid;
|
|
||||||
|
|
||||||
for state in state {
|
for state in state {
|
||||||
validation_state =
|
T::validate_param(state, system_meta, world)?;
|
||||||
validation_state.combine(T::validate_param(state, system_meta, world));
|
|
||||||
|
|
||||||
// Short-circuit to avoid wasted validation work
|
|
||||||
if matches!(
|
|
||||||
validation_state,
|
|
||||||
ValidationOutcome::Invalid | ValidationOutcome::Skipped
|
|
||||||
) {
|
|
||||||
return validation_state;
|
|
||||||
}
|
}
|
||||||
}
|
Ok(())
|
||||||
|
|
||||||
validation_state
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
@ -2100,12 +2053,12 @@ macro_rules! impl_system_param_tuple {
|
|||||||
state: &Self::State,
|
state: &Self::State,
|
||||||
system_meta: &SystemMeta,
|
system_meta: &SystemMeta,
|
||||||
world: UnsafeWorldCell,
|
world: UnsafeWorldCell,
|
||||||
) -> ValidationOutcome {
|
) -> Result<(), SystemParamValidationError> {
|
||||||
// PERF: short-circuit to avoid wasted validation work
|
|
||||||
let ($($param,)*) = state;
|
let ($($param,)*) = state;
|
||||||
// Run validation on each parameter in the tuple,
|
$(
|
||||||
// combining the results into a single `ValidationOutcome`.
|
$param::validate_param($param, system_meta, world)?;
|
||||||
ValidationOutcome::Valid$(.combine($param::validate_param($param, system_meta, world)))*
|
)*
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
@ -2272,7 +2225,7 @@ unsafe impl<P: SystemParam + 'static> SystemParam for StaticSystemParam<'_, '_,
|
|||||||
state: &Self::State,
|
state: &Self::State,
|
||||||
system_meta: &SystemMeta,
|
system_meta: &SystemMeta,
|
||||||
world: UnsafeWorldCell,
|
world: UnsafeWorldCell,
|
||||||
) -> ValidationOutcome {
|
) -> Result<(), SystemParamValidationError> {
|
||||||
P::validate_param(state, system_meta, world)
|
P::validate_param(state, system_meta, world)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2522,7 +2475,7 @@ trait DynParamState: Sync + Send {
|
|||||||
&self,
|
&self,
|
||||||
system_meta: &SystemMeta,
|
system_meta: &SystemMeta,
|
||||||
world: UnsafeWorldCell,
|
world: UnsafeWorldCell,
|
||||||
) -> ValidationOutcome;
|
) -> Result<(), SystemParamValidationError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A wrapper around a [`SystemParam::State`] that can be used as a trait object in a [`DynSystemParam`].
|
/// A wrapper around a [`SystemParam::State`] that can be used as a trait object in a [`DynSystemParam`].
|
||||||
@ -2550,7 +2503,7 @@ impl<T: SystemParam + 'static> DynParamState for ParamState<T> {
|
|||||||
&self,
|
&self,
|
||||||
system_meta: &SystemMeta,
|
system_meta: &SystemMeta,
|
||||||
world: UnsafeWorldCell,
|
world: UnsafeWorldCell,
|
||||||
) -> ValidationOutcome {
|
) -> Result<(), SystemParamValidationError> {
|
||||||
T::validate_param(&self.0, system_meta, world)
|
T::validate_param(&self.0, system_meta, world)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2570,7 +2523,7 @@ unsafe impl SystemParam for DynSystemParam<'_, '_> {
|
|||||||
state: &Self::State,
|
state: &Self::State,
|
||||||
system_meta: &SystemMeta,
|
system_meta: &SystemMeta,
|
||||||
world: UnsafeWorldCell,
|
world: UnsafeWorldCell,
|
||||||
) -> ValidationOutcome {
|
) -> Result<(), SystemParamValidationError> {
|
||||||
state.0.validate_param(system_meta, world)
|
state.0.validate_param(system_meta, world)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2665,12 +2618,38 @@ unsafe impl SystemParam for FilteredResourcesMut<'_, '_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An error that occurs when a system parameter is not valid.
|
/// An error that occurs when a system parameter is not valid,
|
||||||
|
/// used by system executors to determine what to do with a system.
|
||||||
///
|
///
|
||||||
/// Generated when [`SystemParam::validate_param`] returns `false`,
|
/// Returned as an error from [`SystemParam::validate_param`],
|
||||||
/// and handled using the unified error handling mechanisms defined in [`bevy_ecs::error`].
|
/// and handled using the unified error handling mechanisms defined in [`bevy_ecs::error`].
|
||||||
#[derive(Debug, PartialEq, Eq, Clone, Display, Error)]
|
#[derive(Debug, PartialEq, Eq, Clone, Display, Error)]
|
||||||
pub struct SystemParamValidationError;
|
pub struct SystemParamValidationError {
|
||||||
|
/// Whether the system should be skipped.
|
||||||
|
///
|
||||||
|
/// If `false`, the error should be handled.
|
||||||
|
/// By default, this will result in a panic. See [`crate::error`] for more information.
|
||||||
|
///
|
||||||
|
/// This is the default behavior, and is suitable for system params that should *always* be valid,
|
||||||
|
/// either because sensible fallback behavior exists (like [`Query`] or because
|
||||||
|
/// failures in validation should be considered a bug in the user's logic that must be immediately addressed (like [`Res`]).
|
||||||
|
///
|
||||||
|
/// If `true`, the system should be skipped.
|
||||||
|
/// This is suitable for system params that are intended to only operate in certain application states, such as [`Single`].
|
||||||
|
pub skipped: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SystemParamValidationError {
|
||||||
|
/// Constructs a `SystemParamValidationError` that skips the system.
|
||||||
|
pub const fn skipped() -> Self {
|
||||||
|
Self { skipped: true }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Constructs a `SystemParamValidationError` for an invalid parameter that should be treated as an error.
|
||||||
|
pub const fn invalid() -> Self {
|
||||||
|
Self { skipped: false }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
@ -13,8 +13,6 @@ use bevy_reflect::{std_traits::ReflectDefault, Reflect};
|
|||||||
use core::marker::PhantomData;
|
use core::marker::PhantomData;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use super::ValidationOutcome;
|
|
||||||
|
|
||||||
/// A small wrapper for [`BoxedSystem`] that also keeps track whether or not the system has been initialized.
|
/// A small wrapper for [`BoxedSystem`] that also keeps track whether or not the system has been initialized.
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
#[require(SystemIdMarker)]
|
#[require(SystemIdMarker)]
|
||||||
@ -353,7 +351,7 @@ impl World {
|
|||||||
initialized = true;
|
initialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
let result = if let ValidationOutcome::Valid = system.validate_param(self) {
|
let result = if system.validate_param(self).is_ok() {
|
||||||
// Wait to run the commands until the system is available again.
|
// Wait to run the commands until the system is available again.
|
||||||
// This is needed so the systems can recursively run themselves.
|
// This is needed so the systems can recursively run themselves.
|
||||||
let ret = system.run_without_applying_deferred(input, self);
|
let ret = system.run_without_applying_deferred(input, self);
|
||||||
|
@ -13,7 +13,7 @@ use bevy_ecs::{
|
|||||||
resource::Resource,
|
resource::Resource,
|
||||||
system::{
|
system::{
|
||||||
Deferred, ReadOnlySystemParam, Res, SystemBuffer, SystemMeta, SystemParam,
|
Deferred, ReadOnlySystemParam, Res, SystemBuffer, SystemMeta, SystemParam,
|
||||||
ValidationOutcome,
|
SystemParamValidationError,
|
||||||
},
|
},
|
||||||
world::{unsafe_world_cell::UnsafeWorldCell, World},
|
world::{unsafe_world_cell::UnsafeWorldCell, World},
|
||||||
};
|
};
|
||||||
@ -225,7 +225,7 @@ where
|
|||||||
state: &Self::State,
|
state: &Self::State,
|
||||||
system_meta: &SystemMeta,
|
system_meta: &SystemMeta,
|
||||||
world: UnsafeWorldCell,
|
world: UnsafeWorldCell,
|
||||||
) -> ValidationOutcome {
|
) -> Result<(), SystemParamValidationError> {
|
||||||
// SAFETY: Delegated to existing `SystemParam` implementations.
|
// SAFETY: Delegated to existing `SystemParam` implementations.
|
||||||
unsafe { GizmosState::<Config, Clear>::validate_param(&state.state, system_meta, world) }
|
unsafe { GizmosState::<Config, Clear>::validate_param(&state.state, system_meta, world) }
|
||||||
}
|
}
|
||||||
|
@ -3,8 +3,8 @@ use bevy_ecs::{
|
|||||||
component::Tick,
|
component::Tick,
|
||||||
prelude::*,
|
prelude::*,
|
||||||
system::{
|
system::{
|
||||||
ReadOnlySystemParam, SystemMeta, SystemParam, SystemParamItem, SystemState,
|
ReadOnlySystemParam, SystemMeta, SystemParam, SystemParamItem, SystemParamValidationError,
|
||||||
ValidationOutcome,
|
SystemState,
|
||||||
},
|
},
|
||||||
world::unsafe_world_cell::UnsafeWorldCell,
|
world::unsafe_world_cell::UnsafeWorldCell,
|
||||||
};
|
};
|
||||||
@ -84,11 +84,11 @@ where
|
|||||||
state: &Self::State,
|
state: &Self::State,
|
||||||
_system_meta: &SystemMeta,
|
_system_meta: &SystemMeta,
|
||||||
world: UnsafeWorldCell,
|
world: UnsafeWorldCell,
|
||||||
) -> ValidationOutcome {
|
) -> Result<(), SystemParamValidationError> {
|
||||||
// SAFETY: Read-only access to world data registered in `init_state`.
|
// SAFETY: Read-only access to world data registered in `init_state`.
|
||||||
let result = unsafe { world.get_resource_by_id(state.main_world_state) };
|
let result = unsafe { world.get_resource_by_id(state.main_world_state) };
|
||||||
let Some(main_world) = result else {
|
let Some(main_world) = result else {
|
||||||
return ValidationOutcome::Invalid;
|
return Err(SystemParamValidationError::invalid());
|
||||||
};
|
};
|
||||||
// SAFETY: Type is guaranteed by `SystemState`.
|
// SAFETY: Type is guaranteed by `SystemState`.
|
||||||
let main_world: &World = unsafe { main_world.deref() };
|
let main_world: &World = unsafe { main_world.deref() };
|
||||||
|
@ -9,16 +9,14 @@
|
|||||||
//!
|
//!
|
||||||
//! Other system parameters, such as [`Query`], will never fail validation: returning a query with no matching entities is valid.
|
//! Other system parameters, such as [`Query`], will never fail validation: returning a query with no matching entities is valid.
|
||||||
//!
|
//!
|
||||||
//! The result of failed system parameter validation is determined by the [`ValidationOutcome`] returned
|
//! The result of failed system parameter validation is determined by the [`SystemParamValidationError`] returned
|
||||||
//! by [`SystemParam::validate_param`] for each system parameter.
|
//! by [`SystemParam::validate_param`] for each system parameter.
|
||||||
//! Each system will pass, fail, or skip based on the joint outcome of all its parameters,
|
//! Each system will pass if all of its parameters are valid, or else return [`SystemParamValidationError`] for the first failing parameter.
|
||||||
//! according to the rules defined in [`ValidationOutcome::combine`].
|
|
||||||
//!
|
//!
|
||||||
//! To learn more about setting the fallback behavior for [`ValidationOutcome`] failures,
|
//! To learn more about setting the fallback behavior for [`SystemParamValidationError`] failures,
|
||||||
//! please see the `error_handling.rs` example.
|
//! please see the `error_handling.rs` example.
|
||||||
//!
|
//!
|
||||||
//! [`ValidationOutcome`]: bevy::ecs::system::ValidationOutcome
|
//! [`SystemParamValidationError`]: bevy::ecs::system::SystemParamValidationError
|
||||||
//! [`ValidationOutcome::combine`]: bevy::ecs::system::ValidationOutcome::combine
|
|
||||||
//! [`SystemParam::validate_param`]: bevy::ecs::system::SystemParam::validate_param
|
//! [`SystemParam::validate_param`]: bevy::ecs::system::SystemParam::validate_param
|
||||||
|
|
||||||
use bevy::ecs::error::{warn, GLOBAL_ERROR_HANDLER};
|
use bevy::ecs::error::{warn, GLOBAL_ERROR_HANDLER};
|
||||||
|
Loading…
Reference in New Issue
Block a user