diff --git a/Cargo.toml b/Cargo.toml index 8a43ae266fd9a..4a4a005e2fc15 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2031,6 +2031,17 @@ description = "Systems are skipped if their parameters cannot be acquired" category = "ECS (Entity Component System)" wasm = false +[[example]] +name = "fallible_systems" +path = "examples/ecs/fallible_systems.rs" +doc-scrape-examples = true + +[package.metadata.example.fallible_systems] +name = "Fallible Systems" +description = "Systems that return results to handle errors" +category = "ECS (Entity Component System)" +wasm = false + [[example]] name = "startup_system" path = "examples/ecs/startup_system.rs" diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index 3d33e7f367dc1..20f40f7036f8f 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -1,5 +1,7 @@ // FIXME(11590): remove this once the lint is fixed #![allow(unsafe_op_in_unsafe_fn)] +// TODO: remove once Edition 2024 is released +#![allow(dependency_on_unit_never_type_fallback)] #![doc = include_str!("../README.md")] // `rustdoc_internals` is needed for `#[doc(fake_variadics)]` #![allow(internal_features)] @@ -30,6 +32,7 @@ pub mod query; #[cfg(feature = "bevy_reflect")] pub mod reflect; pub mod removal_detection; +pub mod result; pub mod schedule; pub mod storage; pub mod system; @@ -53,6 +56,7 @@ pub mod prelude { observer::{CloneEntityWithObserversExt, Observer, Trigger}, query::{Added, AnyOf, Changed, Has, Or, QueryBuilder, QueryState, With, Without}, removal_detection::RemovedComponents, + result::{Error, Result}, schedule::{ apply_deferred, common_conditions::*, ApplyDeferred, Condition, IntoSystemConfigs, IntoSystemSet, IntoSystemSetConfigs, Schedule, Schedules, SystemSet, diff --git a/crates/bevy_ecs/src/result.rs b/crates/bevy_ecs/src/result.rs new file mode 100644 index 0000000000000..4127e2ec91d16 --- /dev/null +++ b/crates/bevy_ecs/src/result.rs @@ -0,0 +1,7 @@ +//! Contains error and result helpers for use in fallible systems. + +/// A dynamic error type for use in fallible systems. +pub type Error = Box; + +/// A result type for use in fallible systems. +pub type Result = core::result::Result; diff --git a/crates/bevy_ecs/src/schedule/config.rs b/crates/bevy_ecs/src/schedule/config.rs index 4c80d421d2193..88bf0ffc8c591 100644 --- a/crates/bevy_ecs/src/schedule/config.rs +++ b/crates/bevy_ecs/src/schedule/config.rs @@ -1,13 +1,14 @@ use variadics_please::all_tuples; use crate::{ + result::Result, schedule::{ condition::{BoxedCondition, Condition}, graph::{Ambiguity, Dependency, DependencyKind, GraphInfo}, set::{InternedSystemSet, IntoSystemSet, SystemSet}, Chain, }, - system::{BoxedSystem, IntoSystem, System}, + system::{BoxedSystem, IntoSystem, ScheduleSystem, System}, }; fn new_condition(condition: impl Condition) -> BoxedCondition { @@ -47,7 +48,7 @@ pub struct NodeConfig { } /// Stores configuration for a single system. -pub type SystemConfig = NodeConfig; +pub type SystemConfig = NodeConfig; /// A collections of generic [`NodeConfig`]s. pub enum NodeConfigs { @@ -65,10 +66,10 @@ pub enum NodeConfigs { } /// A collection of [`SystemConfig`]. -pub type SystemConfigs = NodeConfigs; +pub type SystemConfigs = NodeConfigs; impl SystemConfigs { - fn new_system(system: BoxedSystem) -> Self { + fn new_system(system: ScheduleSystem) -> Self { // include system in its default sets let sets = system.default_system_sets().into_iter().collect(); Self::NodeConfig(SystemConfig { @@ -517,18 +518,41 @@ impl IntoSystemConfigs<()> for SystemConfigs { } } -impl IntoSystemConfigs for F +#[doc(hidden)] +pub struct Infallible; + +impl IntoSystemConfigs<(Infallible, Marker)> for F where F: IntoSystem<(), (), Marker>, { fn into_configs(self) -> SystemConfigs { - SystemConfigs::new_system(Box::new(IntoSystem::into_system(self))) + let boxed_system = Box::new(IntoSystem::into_system(self)); + SystemConfigs::new_system(ScheduleSystem::Infallible(boxed_system)) } } impl IntoSystemConfigs<()> for BoxedSystem<(), ()> { fn into_configs(self) -> SystemConfigs { - SystemConfigs::new_system(self) + SystemConfigs::new_system(ScheduleSystem::Infallible(self)) + } +} + +#[doc(hidden)] +pub struct Fallible; + +impl IntoSystemConfigs<(Fallible, Marker)> for F +where + F: IntoSystem<(), Result, Marker>, +{ + fn into_configs(self) -> SystemConfigs { + let boxed_system = Box::new(IntoSystem::into_system(self)); + SystemConfigs::new_system(ScheduleSystem::Fallible(boxed_system)) + } +} + +impl IntoSystemConfigs<()> for BoxedSystem<(), Result> { + fn into_configs(self) -> SystemConfigs { + SystemConfigs::new_system(ScheduleSystem::Fallible(self)) } } diff --git a/crates/bevy_ecs/src/schedule/executor/mod.rs b/crates/bevy_ecs/src/schedule/executor/mod.rs index 09ae3c84cfa14..d0231bb2f0c5b 100644 --- a/crates/bevy_ecs/src/schedule/executor/mod.rs +++ b/crates/bevy_ecs/src/schedule/executor/mod.rs @@ -19,7 +19,7 @@ use crate::{ prelude::{IntoSystemSet, SystemSet}, query::Access, schedule::{BoxedCondition, InternedSystemSet, NodeId, SystemTypeSet}, - system::{BoxedSystem, System, SystemIn}, + system::{ScheduleSystem, System, SystemIn}, world::{unsafe_world_cell::UnsafeWorldCell, DeferredWorld, World}, }; @@ -67,7 +67,7 @@ pub struct SystemSchedule { /// List of system node ids. pub(super) system_ids: Vec, /// Indexed by system node id. - pub(super) systems: Vec, + pub(super) systems: Vec, /// Indexed by system node id. pub(super) system_conditions: Vec>, /// Indexed by system node id. @@ -140,9 +140,8 @@ pub const apply_deferred: ApplyDeferred = ApplyDeferred; pub struct ApplyDeferred; /// Returns `true` if the [`System`] is an instance of [`ApplyDeferred`]. -pub(super) fn is_apply_deferred(system: &BoxedSystem) -> bool { - // deref to use `System::type_id` instead of `Any::type_id` - system.as_ref().type_id() == TypeId::of::() +pub(super) fn is_apply_deferred(system: &ScheduleSystem) -> bool { + system.type_id() == TypeId::of::() } impl System for ApplyDeferred { @@ -247,19 +246,18 @@ mod __rust_begin_short_backtrace { use core::hint::black_box; use crate::{ - system::{ReadOnlySystem, System}, + result::Result, + system::{ReadOnlySystem, ScheduleSystem, System}, world::{unsafe_world_cell::UnsafeWorldCell, World}, }; /// # Safety /// See `System::run_unsafe`. #[inline(never)] - pub(super) unsafe fn run_unsafe( - system: &mut dyn System, - world: UnsafeWorldCell, - ) { - system.run_unsafe((), world); + pub(super) unsafe fn run_unsafe(system: &mut ScheduleSystem, world: UnsafeWorldCell) -> Result { + let result = system.run_unsafe((), world); black_box(()); + result } /// # Safety @@ -273,9 +271,10 @@ mod __rust_begin_short_backtrace { } #[inline(never)] - pub(super) fn run(system: &mut dyn System, world: &mut World) { - system.run((), world); + pub(super) fn run(system: &mut ScheduleSystem, world: &mut World) -> Result { + let result = system.run((), world); black_box(()); + result } #[inline(never)] diff --git a/crates/bevy_ecs/src/schedule/executor/multi_threaded.rs b/crates/bevy_ecs/src/schedule/executor/multi_threaded.rs index 53453240dcb88..3585d74e0e661 100644 --- a/crates/bevy_ecs/src/schedule/executor/multi_threaded.rs +++ b/crates/bevy_ecs/src/schedule/executor/multi_threaded.rs @@ -18,7 +18,7 @@ use crate::{ prelude::Resource, query::Access, schedule::{is_apply_deferred, BoxedCondition, ExecutorKind, SystemExecutor, SystemSchedule}, - system::BoxedSystem, + system::{ScheduleSystem, System}, world::{unsafe_world_cell::UnsafeWorldCell, World}, }; @@ -29,7 +29,7 @@ use super::__rust_begin_short_backtrace; /// Borrowed data used by the [`MultiThreadedExecutor`]. struct Environment<'env, 'sys> { executor: &'env MultiThreadedExecutor, - systems: &'sys [SyncUnsafeCell], + systems: &'sys [SyncUnsafeCell], conditions: SyncUnsafeCell>, world_cell: UnsafeWorldCell<'env>, } @@ -269,7 +269,7 @@ impl<'scope, 'env: 'scope, 'sys> Context<'scope, 'env, 'sys> { &self, system_index: usize, res: Result<(), Box>, - system: &BoxedSystem, + system: &ScheduleSystem, ) { // tell the executor that the system finished self.environment @@ -459,7 +459,7 @@ impl ExecutorState { fn can_run( &mut self, system_index: usize, - system: &mut BoxedSystem, + system: &mut ScheduleSystem, conditions: &mut Conditions, world: UnsafeWorldCell, ) -> bool { @@ -523,7 +523,7 @@ impl ExecutorState { unsafe fn should_run( &mut self, system_index: usize, - system: &mut BoxedSystem, + system: &mut ScheduleSystem, conditions: &mut Conditions, world: UnsafeWorldCell, ) -> bool { @@ -603,8 +603,9 @@ impl ExecutorState { // access the world data used by the system. // - `update_archetype_component_access` has been called. unsafe { - __rust_begin_short_backtrace::run_unsafe( - &mut **system, + // TODO: implement an error-handling API instead of suppressing a possible failure. + let _ = __rust_begin_short_backtrace::run_unsafe( + system, context.environment.world_cell, ); }; @@ -650,7 +651,8 @@ impl ExecutorState { // that no other systems currently have access to the world. let world = unsafe { context.environment.world_cell.world_mut() }; let res = std::panic::catch_unwind(AssertUnwindSafe(|| { - __rust_begin_short_backtrace::run(&mut **system, world); + // TODO: implement an error-handling API instead of suppressing a possible failure. + let _ = __rust_begin_short_backtrace::run(system, world); })); context.system_completed(system_index, res, system); }; @@ -710,7 +712,7 @@ impl ExecutorState { fn apply_deferred( unapplied_systems: &FixedBitSet, - systems: &[SyncUnsafeCell], + systems: &[SyncUnsafeCell], world: &mut World, ) -> Result<(), Box> { for system_index in unapplied_systems.ones() { diff --git a/crates/bevy_ecs/src/schedule/executor/simple.rs b/crates/bevy_ecs/src/schedule/executor/simple.rs index 9940bacced91b..fa3ccffb0b74d 100644 --- a/crates/bevy_ecs/src/schedule/executor/simple.rs +++ b/crates/bevy_ecs/src/schedule/executor/simple.rs @@ -7,6 +7,7 @@ use crate::{ schedule::{ executor::is_apply_deferred, BoxedCondition, ExecutorKind, SystemExecutor, SystemSchedule, }, + system::System, world::World, }; @@ -100,7 +101,8 @@ impl SystemExecutor for SimpleExecutor { } let res = std::panic::catch_unwind(AssertUnwindSafe(|| { - __rust_begin_short_backtrace::run(&mut **system, world); + // TODO: implement an error-handling API instead of suppressing a possible failure. + let _ = __rust_begin_short_backtrace::run(system, world); })); if let Err(payload) = res { eprintln!("Encountered a panic in system `{}`!", &*system.name()); @@ -119,7 +121,7 @@ impl SystemExecutor for SimpleExecutor { impl SimpleExecutor { /// Creates a new simple executor for use in a [`Schedule`](crate::schedule::Schedule). - /// This calls each system in order and immediately calls [`System::apply_deferred`](crate::system::System::apply_deferred). + /// This calls each system in order and immediately calls [`System::apply_deferred`]. pub const fn new() -> Self { Self { evaluated_sets: FixedBitSet::new(), diff --git a/crates/bevy_ecs/src/schedule/executor/single_threaded.rs b/crates/bevy_ecs/src/schedule/executor/single_threaded.rs index 9cc6199ce6a4a..cd44c4e66eb17 100644 --- a/crates/bevy_ecs/src/schedule/executor/single_threaded.rs +++ b/crates/bevy_ecs/src/schedule/executor/single_threaded.rs @@ -5,6 +5,7 @@ use fixedbitset::FixedBitSet; use crate::{ schedule::{is_apply_deferred, BoxedCondition, ExecutorKind, SystemExecutor, SystemSchedule}, + system::System, world::World, }; @@ -108,14 +109,18 @@ impl SystemExecutor for SingleThreadedExecutor { let res = std::panic::catch_unwind(AssertUnwindSafe(|| { if system.is_exclusive() { - __rust_begin_short_backtrace::run(&mut **system, world); + // TODO: implement an error-handling API instead of suppressing a possible failure. + let _ = __rust_begin_short_backtrace::run(system, world); } else { // Use run_unsafe to avoid immediately applying deferred buffers let world = world.as_unsafe_world_cell(); system.update_archetype_component_access(world); // SAFETY: We have exclusive, single-threaded access to the world and // update_archetype_component_access is being called immediately before this. - unsafe { __rust_begin_short_backtrace::run_unsafe(&mut **system, world) }; + unsafe { + // TODO: implement an error-handling API instead of suppressing a possible failure. + let _ = __rust_begin_short_backtrace::run_unsafe(system, world); + }; } })); if let Err(payload) = res { diff --git a/crates/bevy_ecs/src/schedule/schedule.rs b/crates/bevy_ecs/src/schedule/schedule.rs index 85e89a04225c2..a68edd1adb9f3 100644 --- a/crates/bevy_ecs/src/schedule/schedule.rs +++ b/crates/bevy_ecs/src/schedule/schedule.rs @@ -17,8 +17,9 @@ use crate::{ self as bevy_ecs, component::{ComponentId, Components, Tick}, prelude::Component, + result::Result, schedule::*, - system::{BoxedSystem, IntoSystem, Resource, System}, + system::{IntoSystem, Resource, ScheduleSystem, System}, world::World, }; @@ -485,7 +486,8 @@ impl Schedule { /// schedule has never been initialized or run. pub fn systems( &self, - ) -> Result + Sized, ScheduleNotInitialized> { + ) -> Result + Sized, ScheduleNotInitialized> + { if !self.executor_initialized { return Err(ScheduleNotInitialized); } @@ -563,23 +565,23 @@ impl SystemSetNode { } } -/// A [`BoxedSystem`] with metadata, stored in a [`ScheduleGraph`]. +/// A [`ScheduleSystem`] stored in a [`ScheduleGraph`]. struct SystemNode { - inner: Option, + inner: Option, } impl SystemNode { - pub fn new(system: BoxedSystem) -> Self { + pub fn new(system: ScheduleSystem) -> Self { Self { inner: Some(system), } } - pub fn get(&self) -> Option<&BoxedSystem> { + pub fn get(&self) -> Option<&ScheduleSystem> { self.inner.as_ref() } - pub fn get_mut(&mut self) -> Option<&mut BoxedSystem> { + pub fn get_mut(&mut self) -> Option<&mut ScheduleSystem> { self.inner.as_mut() } } @@ -642,13 +644,13 @@ impl ScheduleGraph { } /// Returns the system at the given [`NodeId`], if it exists. - pub fn get_system_at(&self, id: NodeId) -> Option<&dyn System> { + pub fn get_system_at(&self, id: NodeId) -> Option<&ScheduleSystem> { if !id.is_system() { return None; } self.systems .get(id.index()) - .and_then(|system| system.inner.as_deref()) + .and_then(|system| system.inner.as_ref()) } /// Returns `true` if the given system set is part of the graph. Otherwise, returns `false`. @@ -660,7 +662,7 @@ impl ScheduleGraph { /// /// Panics if it doesn't exist. #[track_caller] - pub fn system_at(&self, id: NodeId) -> &dyn System { + pub fn system_at(&self, id: NodeId) -> &ScheduleSystem { self.get_system_at(id) .ok_or_else(|| format!("system with id {id:?} does not exist in this Schedule")) .unwrap() @@ -685,15 +687,13 @@ impl ScheduleGraph { } /// Returns an iterator over all systems in this schedule, along with the conditions for each system. - pub fn systems( - &self, - ) -> impl Iterator, &[BoxedCondition])> { + pub fn systems(&self) -> impl Iterator { self.systems .iter() .zip(self.system_conditions.iter()) .enumerate() .filter_map(|(i, (system_node, condition))| { - let system = system_node.inner.as_deref()?; + let system = system_node.inner.as_ref()?; Some((NodeId::System(i), system, condition.as_slice())) }) } @@ -1196,8 +1196,8 @@ impl ScheduleGraph { let id = NodeId::System(self.systems.len()); self.systems - .push(SystemNode::new(Box::new(IntoSystem::into_system( - ApplyDeferred, + .push(SystemNode::new(ScheduleSystem::Infallible(Box::new( + IntoSystem::into_system(ApplyDeferred), )))); self.system_conditions.push(Vec::new()); @@ -1554,7 +1554,7 @@ trait ProcessNodeConfig: Sized { fn process_config(schedule_graph: &mut ScheduleGraph, config: NodeConfig) -> NodeId; } -impl ProcessNodeConfig for BoxedSystem { +impl ProcessNodeConfig for ScheduleSystem { fn process_config(schedule_graph: &mut ScheduleGraph, config: NodeConfig) -> NodeId { schedule_graph.add_system_inner(config).unwrap() } diff --git a/crates/bevy_ecs/src/schedule/stepping.rs b/crates/bevy_ecs/src/schedule/stepping.rs index 61cfc3ca244cb..c5a964a2cbd89 100644 --- a/crates/bevy_ecs/src/schedule/stepping.rs +++ b/crates/bevy_ecs/src/schedule/stepping.rs @@ -4,7 +4,7 @@ use std::collections::HashMap; use crate::{ schedule::{InternedScheduleLabel, NodeId, Schedule, ScheduleLabel}, - system::{IntoSystem, ResMut, Resource}, + system::{IntoSystem, ResMut, Resource, System}, }; use bevy_utils::{ tracing::{info, warn}, diff --git a/crates/bevy_ecs/src/system/mod.rs b/crates/bevy_ecs/src/system/mod.rs index b230535c0bf0b..58f5800ede3ed 100644 --- a/crates/bevy_ecs/src/system/mod.rs +++ b/crates/bevy_ecs/src/system/mod.rs @@ -79,6 +79,12 @@ //! } //! ``` //! +//! # System return type +//! +//! Systems added to a schedule through [`add_systems`](crate::schedule::Schedule) may either return +//! empty `()` or a [`Result`](crate::result::Result). Other contexts (like one shot systems) allow +//! systems to return arbitrary values. +//! //! # System parameter list //! Following is the complete list of accepted types as system parameters: //! @@ -121,6 +127,7 @@ mod function_system; mod input; mod observer_system; mod query; +mod schedule_system; #[allow(clippy::module_inception)] mod system; mod system_name; @@ -139,6 +146,7 @@ pub use function_system::*; pub use input::*; pub use observer_system::*; pub use query::*; +pub use schedule_system::*; pub use system::*; pub use system_name::*; pub use system_param::*; @@ -322,6 +330,7 @@ mod tests { prelude::{AnyOf, EntityRef}, query::{Added, Changed, Or, With, Without}, removal_detection::RemovedComponents, + result::Result, schedule::{ common_conditions::resource_exists, ApplyDeferred, Condition, IntoSystemConfigs, Schedule, @@ -371,7 +380,7 @@ mod tests { system.run((), &mut world); } - fn run_system>(world: &mut World, system: S) { + fn run_system>(world: &mut World, system: S) { let mut schedule = Schedule::default(); schedule.add_systems(system); schedule.run(world); @@ -1749,4 +1758,15 @@ mod tests { sched.run(&mut world); assert_eq!(world.get_resource(), Some(&C(3))); } + + #[test] + fn simple_fallible_system() { + fn sys() -> Result { + Err("error")?; + Ok(()) + } + + let mut world = World::new(); + run_system(&mut world, sys); + } } diff --git a/crates/bevy_ecs/src/system/schedule_system.rs b/crates/bevy_ecs/src/system/schedule_system.rs new file mode 100644 index 0000000000000..c88db47f04936 --- /dev/null +++ b/crates/bevy_ecs/src/system/schedule_system.rs @@ -0,0 +1,183 @@ +use alloc::borrow::Cow; +use core::any::TypeId; + +use crate::{ + archetype::ArchetypeComponentId, + component::{ComponentId, Tick}, + query::Access, + result::Result, + schedule::InternedSystemSet, + system::{input::SystemIn, BoxedSystem, System}, + world::{unsafe_world_cell::UnsafeWorldCell, DeferredWorld, World}, +}; + +/// A type which wraps and unifies the different sorts of systems that can be added to a schedule. +pub enum ScheduleSystem { + /// A system that does not return a result. + Infallible(BoxedSystem<(), ()>), + /// A system that does return a result. + Fallible(BoxedSystem<(), Result>), +} + +impl System for ScheduleSystem { + type In = (); + type Out = Result; + + #[inline(always)] + fn name(&self) -> Cow<'static, str> { + match self { + ScheduleSystem::Infallible(inner_system) => inner_system.name(), + ScheduleSystem::Fallible(inner_system) => inner_system.name(), + } + } + + #[inline(always)] + fn type_id(&self) -> TypeId { + match self { + ScheduleSystem::Infallible(inner_system) => inner_system.type_id(), + ScheduleSystem::Fallible(inner_system) => inner_system.type_id(), + } + } + + #[inline(always)] + fn component_access(&self) -> &Access { + match self { + ScheduleSystem::Infallible(inner_system) => inner_system.component_access(), + ScheduleSystem::Fallible(inner_system) => inner_system.component_access(), + } + } + + #[inline(always)] + fn archetype_component_access(&self) -> &Access { + match self { + ScheduleSystem::Infallible(inner_system) => inner_system.archetype_component_access(), + ScheduleSystem::Fallible(inner_system) => inner_system.archetype_component_access(), + } + } + + #[inline(always)] + fn is_exclusive(&self) -> bool { + match self { + ScheduleSystem::Infallible(inner_system) => inner_system.is_exclusive(), + ScheduleSystem::Fallible(inner_system) => inner_system.is_exclusive(), + } + } + + #[inline(always)] + fn has_deferred(&self) -> bool { + match self { + ScheduleSystem::Infallible(inner_system) => inner_system.has_deferred(), + ScheduleSystem::Fallible(inner_system) => inner_system.has_deferred(), + } + } + + #[inline(always)] + unsafe fn run_unsafe( + &mut self, + input: SystemIn<'_, Self>, + world: UnsafeWorldCell, + ) -> Self::Out { + match self { + ScheduleSystem::Infallible(inner_system) => { + inner_system.run_unsafe(input, world); + Ok(()) + } + ScheduleSystem::Fallible(inner_system) => inner_system.run_unsafe(input, world), + } + } + + #[inline(always)] + fn run(&mut self, input: SystemIn<'_, Self>, world: &mut World) -> Self::Out { + match self { + ScheduleSystem::Infallible(inner_system) => { + inner_system.run(input, world); + Ok(()) + } + ScheduleSystem::Fallible(inner_system) => inner_system.run(input, world), + } + } + + #[inline(always)] + fn apply_deferred(&mut self, world: &mut World) { + match self { + ScheduleSystem::Infallible(inner_system) => inner_system.apply_deferred(world), + ScheduleSystem::Fallible(inner_system) => inner_system.apply_deferred(world), + } + } + + #[inline(always)] + fn queue_deferred(&mut self, world: DeferredWorld) { + match self { + ScheduleSystem::Infallible(inner_system) => inner_system.queue_deferred(world), + ScheduleSystem::Fallible(inner_system) => inner_system.queue_deferred(world), + } + } + + #[inline(always)] + fn is_send(&self) -> bool { + match self { + ScheduleSystem::Infallible(inner_system) => inner_system.is_send(), + ScheduleSystem::Fallible(inner_system) => inner_system.is_send(), + } + } + + #[inline(always)] + unsafe fn validate_param_unsafe(&mut self, world: UnsafeWorldCell) -> bool { + match self { + ScheduleSystem::Infallible(inner_system) => inner_system.validate_param_unsafe(world), + ScheduleSystem::Fallible(inner_system) => inner_system.validate_param_unsafe(world), + } + } + + #[inline(always)] + fn initialize(&mut self, world: &mut World) { + match self { + ScheduleSystem::Infallible(inner_system) => inner_system.initialize(world), + ScheduleSystem::Fallible(inner_system) => inner_system.initialize(world), + } + } + + #[inline(always)] + fn update_archetype_component_access(&mut self, world: UnsafeWorldCell) { + match self { + ScheduleSystem::Infallible(inner_system) => { + inner_system.update_archetype_component_access(world); + } + ScheduleSystem::Fallible(inner_system) => { + inner_system.update_archetype_component_access(world); + } + } + } + + #[inline(always)] + fn check_change_tick(&mut self, change_tick: Tick) { + match self { + ScheduleSystem::Infallible(inner_system) => inner_system.check_change_tick(change_tick), + ScheduleSystem::Fallible(inner_system) => inner_system.check_change_tick(change_tick), + } + } + + #[inline(always)] + fn default_system_sets(&self) -> Vec { + match self { + ScheduleSystem::Infallible(inner_system) => inner_system.default_system_sets(), + ScheduleSystem::Fallible(inner_system) => inner_system.default_system_sets(), + } + } + + #[inline(always)] + fn get_last_run(&self) -> Tick { + match self { + ScheduleSystem::Infallible(inner_system) => inner_system.get_last_run(), + ScheduleSystem::Fallible(inner_system) => inner_system.get_last_run(), + } + } + + #[inline(always)] + fn set_last_run(&mut self, last_run: Tick) { + match self { + ScheduleSystem::Infallible(inner_system) => inner_system.set_last_run(last_run), + ScheduleSystem::Fallible(inner_system) => inner_system.set_last_run(last_run), + } + } +} diff --git a/examples/README.md b/examples/README.md index 2e48c40886f68..1ddb014c97132 100644 --- a/examples/README.md +++ b/examples/README.md @@ -299,6 +299,7 @@ Example | Description [ECS Guide](../examples/ecs/ecs_guide.rs) | Full guide to Bevy's ECS [Event](../examples/ecs/event.rs) | Illustrates event creation, activation, and reception [Fallible System Parameters](../examples/ecs/fallible_params.rs) | Systems are skipped if their parameters cannot be acquired +[Fallible Systems](../examples/ecs/fallible_systems.rs) | Systems that return results to handle errors [Fixed Timestep](../examples/ecs/fixed_timestep.rs) | Shows how to create systems that run every fixed timestep, rather than every tick [Generic System](../examples/ecs/generic_system.rs) | Shows how to create systems that can be reused with different types [Hierarchy](../examples/ecs/hierarchy.rs) | Creates a hierarchy of parents and children entities diff --git a/examples/ecs/fallible_systems.rs b/examples/ecs/fallible_systems.rs new file mode 100644 index 0000000000000..740976dadfa6b --- /dev/null +++ b/examples/ecs/fallible_systems.rs @@ -0,0 +1,79 @@ +//! Showcases how fallible systems can be make use of rust's powerful result handling syntax. + +use bevy::math::sampling::UniformMeshSampler; +use bevy::prelude::*; + +use rand::distributions::Distribution; + +fn main() { + App::new() + .add_plugins(DefaultPlugins) + .add_systems(Startup, setup) + .run(); +} + +/// An example of a system that calls several fallible functions with the questionmark operator. +fn setup( + mut commands: Commands, + mut meshes: ResMut>, + mut materials: ResMut>, +) -> Result { + let mut rng = rand::thread_rng(); + + // Make a plane for establishing space. + commands.spawn(( + Mesh3d(meshes.add(Plane3d::default().mesh().size(12.0, 12.0))), + MeshMaterial3d(materials.add(Color::srgb(0.3, 0.5, 0.3))), + Transform::from_xyz(0.0, -2.5, 0.0), + )); + + // Spawn a light: + commands.spawn(( + PointLight { + shadows_enabled: true, + ..default() + }, + Transform::from_xyz(4.0, 8.0, 4.0), + )); + + // Spawn a camera: + commands.spawn(( + Camera3d::default(), + Transform::from_xyz(-2.0, 3.0, 5.0).looking_at(Vec3::ZERO, Vec3::Y), + )); + + // Create a new sphere mesh: + let mut sphere_mesh = Sphere::new(1.0).mesh().ico(7)?; + sphere_mesh.generate_tangents()?; + + // Spawn the mesh into the scene: + let mut sphere = commands.spawn(( + Mesh3d(meshes.add(sphere_mesh.clone())), + MeshMaterial3d(materials.add(StandardMaterial::default())), + Transform::from_xyz(-1.0, 1.0, 0.0), + )); + + // Generate random sample points: + let triangles = sphere_mesh.triangles()?; + let distribution = UniformMeshSampler::try_new(triangles)?; + + // Setup sample points: + let point_mesh = meshes.add(Sphere::new(0.01).mesh().ico(3)?); + let point_material = materials.add(StandardMaterial { + base_color: Srgba::RED.into(), + emissive: LinearRgba::rgb(1.0, 0.0, 0.0), + ..default() + }); + + // Add sample points as children of the sphere: + for point in distribution.sample_iter(&mut rng).take(10000) { + sphere.with_child(( + Mesh3d(point_mesh.clone()), + MeshMaterial3d(point_material.clone()), + Transform::from_translation(point), + )); + } + + // Indicate the system completed successfully: + Ok(()) +}