
# Objective There are two related problems here: 1. Users should be able to change the fallback behavior of *all* ECS-based errors in their application by setting the `GLOBAL_ERROR_HANDLER`. See #18351 for earlier work in this vein. 2. The existing solution (#15500) for customizing this behavior is high on boilerplate, not global and adds a great deal of complexity. The consensus is that the default behavior when a parameter fails validation should be set based on the kind of system parameter in question: `Single` / `Populated` should silently skip the system, but `Res` should panic. Setting this behavior at the system level is a bandaid that makes getting to that ideal behavior more painful, and can mask real failures (if a resource is missing but you've ignored a system to make the Single stop panicking you're going to have a bad day). ## Solution I've removed the existing `ParamWarnPolicy`-based configuration, and wired up the `GLOBAL_ERROR_HANDLER`/`default_error_handler` to the various schedule executors to properly plumb through errors . Additionally, I've done a small cleanup pass on the corresponding example. ## Testing I've run the `fallible_params` example, with both the default and a custom global error handler. The former panics (as expected), and the latter spams the error console with warnings 🥲 ## Questions for reviewers 1. Currently, failed system param validation will result in endless console spam. Do you want me to implement a solution for warn_once-style debouncing somehow? 2. Currently, the error reporting for failed system param validation is very limited: all we get is that a system param failed validation and the name of the system. Do you want me to implement improved error reporting by bubbling up errors in this PR? 3. There is broad consensus that the default behavior for failed system param validation should be set on a per-system param basis. Would you like me to implement that in this PR? My gut instinct is that we absolutely want to solve 2 and 3, but it will be much easier to do that work (and review it) if we split the PRs apart. ## Migration Guide `ParamWarnPolicy` and the `WithParamWarnPolicy` have been removed completely. Failures during system param validation are now handled via the `GLOBAL_ERROR_HANDLER`: please see the `bevy_ecs::error` module docs for more information. --------- Co-authored-by: MiniaczQ <xnetroidpl@gmail.com>
158 lines
5.0 KiB
Rust
158 lines
5.0 KiB
Rust
use crate::MainWorld;
|
|
use bevy_ecs::{
|
|
component::Tick,
|
|
prelude::*,
|
|
system::{ReadOnlySystemParam, SystemMeta, SystemParam, SystemParamItem, SystemState},
|
|
world::unsafe_world_cell::UnsafeWorldCell,
|
|
};
|
|
use core::ops::{Deref, DerefMut};
|
|
|
|
/// A helper for accessing [`MainWorld`] content using a system parameter.
|
|
///
|
|
/// A [`SystemParam`] adapter which applies the contained `SystemParam` to the [`World`]
|
|
/// contained in [`MainWorld`]. This parameter only works for systems run
|
|
/// during the [`ExtractSchedule`](crate::ExtractSchedule).
|
|
///
|
|
/// This requires that the contained [`SystemParam`] does not mutate the world, as it
|
|
/// uses a read-only reference to [`MainWorld`] internally.
|
|
///
|
|
/// ## Context
|
|
///
|
|
/// [`ExtractSchedule`] is used to extract (move) data from the simulation world ([`MainWorld`]) to the
|
|
/// render world. The render world drives rendering each frame (generally to a `Window`).
|
|
/// This design is used to allow performing calculations related to rendering a prior frame at the same
|
|
/// time as the next frame is simulated, which increases throughput (FPS).
|
|
///
|
|
/// [`Extract`] is used to get data from the main world during [`ExtractSchedule`].
|
|
///
|
|
/// ## Examples
|
|
///
|
|
/// ```
|
|
/// use bevy_ecs::prelude::*;
|
|
/// use bevy_render::Extract;
|
|
/// use bevy_render::sync_world::RenderEntity;
|
|
/// # #[derive(Component)]
|
|
/// // Do make sure to sync the cloud entities before extracting them.
|
|
/// # struct Cloud;
|
|
/// fn extract_clouds(mut commands: Commands, clouds: Extract<Query<RenderEntity, With<Cloud>>>) {
|
|
/// for cloud in &clouds {
|
|
/// commands.entity(cloud).insert(Cloud);
|
|
/// }
|
|
/// }
|
|
/// ```
|
|
///
|
|
/// [`ExtractSchedule`]: crate::ExtractSchedule
|
|
/// [Window]: bevy_window::Window
|
|
pub struct Extract<'w, 's, P>
|
|
where
|
|
P: ReadOnlySystemParam + 'static,
|
|
{
|
|
item: SystemParamItem<'w, 's, P>,
|
|
}
|
|
|
|
#[doc(hidden)]
|
|
pub struct ExtractState<P: SystemParam + 'static> {
|
|
state: SystemState<P>,
|
|
main_world_state: <Res<'static, MainWorld> as SystemParam>::State,
|
|
}
|
|
|
|
// SAFETY: The only `World` access (`Res<MainWorld>`) is read-only.
|
|
unsafe impl<P> ReadOnlySystemParam for Extract<'_, '_, P> where P: ReadOnlySystemParam {}
|
|
|
|
// SAFETY: The only `World` access is properly registered by `Res<MainWorld>::init_state`.
|
|
// This call will also ensure that there are no conflicts with prior params.
|
|
unsafe impl<P> SystemParam for Extract<'_, '_, P>
|
|
where
|
|
P: ReadOnlySystemParam,
|
|
{
|
|
type State = ExtractState<P>;
|
|
type Item<'w, 's> = Extract<'w, 's, P>;
|
|
|
|
fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State {
|
|
let mut main_world = world.resource_mut::<MainWorld>();
|
|
ExtractState {
|
|
state: SystemState::new(&mut main_world),
|
|
main_world_state: Res::<MainWorld>::init_state(world, system_meta),
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
unsafe fn validate_param(
|
|
state: &Self::State,
|
|
_system_meta: &SystemMeta,
|
|
world: UnsafeWorldCell,
|
|
) -> bool {
|
|
// SAFETY: Read-only access to world data registered in `init_state`.
|
|
let result = unsafe { world.get_resource_by_id(state.main_world_state) };
|
|
let Some(main_world) = result else {
|
|
return false;
|
|
};
|
|
// SAFETY: Type is guaranteed by `SystemState`.
|
|
let main_world: &World = unsafe { main_world.deref() };
|
|
// SAFETY: We provide the main world on which this system state was initialized on.
|
|
unsafe {
|
|
SystemState::<P>::validate_param(
|
|
&state.state,
|
|
main_world.as_unsafe_world_cell_readonly(),
|
|
)
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
unsafe fn get_param<'w, 's>(
|
|
state: &'s mut Self::State,
|
|
system_meta: &SystemMeta,
|
|
world: UnsafeWorldCell<'w>,
|
|
change_tick: Tick,
|
|
) -> Self::Item<'w, 's> {
|
|
// SAFETY:
|
|
// - The caller ensures that `world` is the same one that `init_state` was called with.
|
|
// - The caller ensures that no other `SystemParam`s will conflict with the accesses we have registered.
|
|
let main_world = unsafe {
|
|
Res::<MainWorld>::get_param(
|
|
&mut state.main_world_state,
|
|
system_meta,
|
|
world,
|
|
change_tick,
|
|
)
|
|
};
|
|
let item = state.state.get(main_world.into_inner());
|
|
Extract { item }
|
|
}
|
|
}
|
|
|
|
impl<'w, 's, P> Deref for Extract<'w, 's, P>
|
|
where
|
|
P: ReadOnlySystemParam,
|
|
{
|
|
type Target = SystemParamItem<'w, 's, P>;
|
|
|
|
#[inline]
|
|
fn deref(&self) -> &Self::Target {
|
|
&self.item
|
|
}
|
|
}
|
|
|
|
impl<'w, 's, P> DerefMut for Extract<'w, 's, P>
|
|
where
|
|
P: ReadOnlySystemParam,
|
|
{
|
|
#[inline]
|
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
|
&mut self.item
|
|
}
|
|
}
|
|
|
|
impl<'a, 'w, 's, P> IntoIterator for &'a Extract<'w, 's, P>
|
|
where
|
|
P: ReadOnlySystemParam,
|
|
&'a SystemParamItem<'w, 's, P>: IntoIterator,
|
|
{
|
|
type Item = <&'a SystemParamItem<'w, 's, P> as IntoIterator>::Item;
|
|
type IntoIter = <&'a SystemParamItem<'w, 's, P> as IntoIterator>::IntoIter;
|
|
|
|
fn into_iter(self) -> Self::IntoIter {
|
|
(&self.item).into_iter()
|
|
}
|
|
}
|