#![expect(
clippy::module_inception,
reason = "This instance of module inception is being discussed; see #17353."
)]
use bevy_utils::prelude::DebugName;
use bitflags::bitflags;
use core::fmt::{Debug, Display};
use log::warn;
use crate::{
change_detection::{CheckChangeTicks, Tick},
error::BevyError,
query::FilteredAccessSet,
schedule::InternedSystemSet,
system::{input::SystemInput, SystemIn},
world::{unsafe_world_cell::UnsafeWorldCell, DeferredWorld, World},
};
use alloc::{boxed::Box, vec::Vec};
use core::any::{Any, TypeId};
use super::{IntoSystem, SystemParamValidationError};
bitflags! {
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct SystemStateFlags: u8 {
const NON_SEND = 1 << 0;
const EXCLUSIVE = 1 << 1;
const DEFERRED = 1 << 2;
}
}
#[diagnostic::on_unimplemented(message = "`{Self}` is not a system", label = "invalid system")]
pub trait System: Send + Sync + 'static {
type In: SystemInput;
type Out;
fn name(&self) -> DebugName;
#[inline]
fn type_id(&self) -> TypeId {
TypeId::of::<Self>()
}
fn flags(&self) -> SystemStateFlags;
#[inline]
fn is_send(&self) -> bool {
!self.flags().intersects(SystemStateFlags::NON_SEND)
}
#[inline]
fn is_exclusive(&self) -> bool {
self.flags().intersects(SystemStateFlags::EXCLUSIVE)
}
#[inline]
fn has_deferred(&self) -> bool {
self.flags().intersects(SystemStateFlags::DEFERRED)
}
unsafe fn run_unsafe(
&mut self,
input: SystemIn<'_, Self>,
world: UnsafeWorldCell,
) -> Result<Self::Out, RunSystemError>;
#[cfg(feature = "hotpatching")]
fn refresh_hotpatch(&mut self);
fn run(
&mut self,
input: SystemIn<'_, Self>,
world: &mut World,
) -> Result<Self::Out, RunSystemError> {
let ret = self.run_without_applying_deferred(input, world)?;
self.apply_deferred(world);
Ok(ret)
}
fn run_without_applying_deferred(
&mut self,
input: SystemIn<'_, Self>,
world: &mut World,
) -> Result<Self::Out, RunSystemError> {
let world_cell = world.as_unsafe_world_cell();
unsafe { self.validate_param_unsafe(world_cell) }?;
unsafe { self.run_unsafe(input, world_cell) }
}
fn apply_deferred(&mut self, world: &mut World);
fn queue_deferred(&mut self, world: DeferredWorld);
unsafe fn validate_param_unsafe(
&mut self,
world: UnsafeWorldCell,
) -> Result<(), SystemParamValidationError>;
fn validate_param(&mut self, world: &World) -> Result<(), SystemParamValidationError> {
let world_cell = world.as_unsafe_world_cell_readonly();
unsafe { self.validate_param_unsafe(world_cell) }
}
fn initialize(&mut self, _world: &mut World) -> FilteredAccessSet;
fn check_change_tick(&mut self, check: CheckChangeTicks);
fn default_system_sets(&self) -> Vec<InternedSystemSet> {
Vec::new()
}
fn get_last_run(&self) -> Tick;
fn set_last_run(&mut self, last_run: Tick);
}
#[diagnostic::on_unimplemented(
message = "`{Self}` is not a read-only system",
label = "invalid read-only system"
)]
pub unsafe trait ReadOnlySystem: System {
fn run_readonly(
&mut self,
input: SystemIn<'_, Self>,
world: &World,
) -> Result<Self::Out, RunSystemError> {
let world = world.as_unsafe_world_cell_readonly();
unsafe { self.validate_param_unsafe(world) }?;
unsafe { self.run_unsafe(input, world) }
}
}
pub type BoxedSystem<In = (), Out = ()> = Box<dyn System<In = In, Out = Out>>;
pub type BoxedReadOnlySystem<In = (), Out = ()> = Box<dyn ReadOnlySystem<In = In, Out = Out>>;
pub(crate) fn check_system_change_tick(
last_run: &mut Tick,
check: CheckChangeTicks,
system_name: DebugName,
) {
if last_run.check_tick(check) {
let age = check.present_tick().relative_to(*last_run).get();
warn!(
"System '{system_name}' has not run for {age} ticks. \
Changes older than {} ticks will not be detected.",
Tick::MAX.get() - 1,
);
}
}
impl<In, Out> Debug for dyn System<In = In, Out = Out>
where
In: SystemInput + 'static,
Out: 'static,
{
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("System")
.field("name", &self.name())
.field("is_exclusive", &self.is_exclusive())
.field("is_send", &self.is_send())
.finish_non_exhaustive()
}
}
pub trait RunSystemOnce: Sized {
fn run_system_once<T, Out, Marker>(self, system: T) -> Result<Out, RunSystemError>
where
T: IntoSystem<(), Out, Marker>,
{
self.run_system_once_with(system, ())
}
fn run_system_once_with<T, In, Out, Marker>(
self,
system: T,
input: SystemIn<'_, T::System>,
) -> Result<Out, RunSystemError>
where
T: IntoSystem<In, Out, Marker>,
In: SystemInput;
}
impl RunSystemOnce for &mut World {
fn run_system_once_with<T, In, Out, Marker>(
self,
system: T,
input: SystemIn<'_, T::System>,
) -> Result<Out, RunSystemError>
where
T: IntoSystem<In, Out, Marker>,
In: SystemInput,
{
let mut system: T::System = IntoSystem::into_system(system);
system.initialize(self);
system.run(input, self)
}
}
#[derive(Debug)]
pub enum RunSystemError {
Skipped(SystemParamValidationError),
Failed(BevyError),
}
impl Display for RunSystemError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::Skipped(err) => write!(
f,
"System did not run due to failed parameter validation: {err}"
),
Self::Failed(err) => write!(f, "{err}"),
}
}
}
impl<E: Any> From<E> for RunSystemError
where
BevyError: From<E>,
{
fn from(mut value: E) -> Self {
let any: &mut dyn Any = &mut value;
if let Some(err) = any.downcast_mut::<SystemParamValidationError>()
&& err.skipped
{
return Self::Skipped(core::mem::replace(err, SystemParamValidationError::EMPTY));
}
Self::Failed(From::from(value))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::prelude::*;
use alloc::string::ToString;
#[test]
fn run_system_once() {
#[derive(Resource)]
struct T(usize);
fn system(In(n): In<usize>, mut commands: Commands) -> usize {
commands.insert_resource(T(n));
n + 1
}
let mut world = World::default();
let n = world.run_system_once_with(system, 1).unwrap();
assert_eq!(n, 2);
assert_eq!(world.resource::<T>().0, 1);
}
#[derive(Resource, Default, PartialEq, Debug)]
struct Counter(u8);
fn count_up(mut counter: ResMut<Counter>) {
counter.0 += 1;
}
#[test]
fn run_two_systems() {
let mut world = World::new();
world.init_resource::<Counter>();
assert_eq!(*world.resource::<Counter>(), Counter(0));
world.run_system_once(count_up).unwrap();
assert_eq!(*world.resource::<Counter>(), Counter(1));
world.run_system_once(count_up).unwrap();
assert_eq!(*world.resource::<Counter>(), Counter(2));
}
#[derive(Component)]
struct A;
fn spawn_entity(mut commands: Commands) {
commands.spawn(A);
}
#[test]
fn command_processing() {
let mut world = World::new();
assert_eq!(world.entities.count_spawned(), 0);
world.run_system_once(spawn_entity).unwrap();
assert_eq!(world.entities.count_spawned(), 1);
}
#[test]
fn non_send_resources() {
fn non_send_count_down(mut ns: NonSendMut<Counter>) {
ns.0 -= 1;
}
let mut world = World::new();
world.insert_non_send_resource(Counter(10));
assert_eq!(*world.non_send_resource::<Counter>(), Counter(10));
world.run_system_once(non_send_count_down).unwrap();
assert_eq!(*world.non_send_resource::<Counter>(), Counter(9));
}
#[test]
fn run_system_once_invalid_params() {
#[derive(Resource)]
struct T;
fn system(_: Res<T>) {}
let mut world = World::default();
let result = world.run_system_once(system);
assert!(matches!(result, Err(RunSystemError::Failed { .. })));
let expected = "Resource does not exist";
let actual = result.unwrap_err().to_string();
assert!(
actual.contains(expected),
"Expected error message to contain `{}` but got `{}`",
expected,
actual
);
}
}