Path: blob/main/crates/bevy_ecs/src/system/exclusive_function_system.rs
6604 views
use crate::{1component::{CheckChangeTicks, Tick},2error::Result,3query::FilteredAccessSet,4schedule::{InternedSystemSet, SystemSet},5system::{6check_system_change_tick, ExclusiveSystemParam, ExclusiveSystemParamItem, IntoResult,7IntoSystem, System, SystemIn, SystemInput, SystemMeta,8},9world::{unsafe_world_cell::UnsafeWorldCell, World},10};1112use alloc::{borrow::Cow, vec, vec::Vec};13use bevy_utils::prelude::DebugName;14use core::marker::PhantomData;15use variadics_please::all_tuples;1617use super::{RunSystemError, SystemParamValidationError, SystemStateFlags};1819/// A function system that runs with exclusive [`World`] access.20///21/// You get this by calling [`IntoSystem::into_system`] on a function that only accepts22/// [`ExclusiveSystemParam`]s.23///24/// [`ExclusiveFunctionSystem`] must be `.initialized` before they can be run.25pub struct ExclusiveFunctionSystem<Marker, Out, F>26where27F: ExclusiveSystemParamFunction<Marker>,28{29func: F,30#[cfg(feature = "hotpatching")]31current_ptr: subsecond::HotFnPtr,32param_state: Option<<F::Param as ExclusiveSystemParam>::State>,33system_meta: SystemMeta,34// NOTE: PhantomData<fn()-> T> gives this safe Send/Sync impls35marker: PhantomData<fn() -> (Marker, Out)>,36}3738impl<Marker, Out, F> ExclusiveFunctionSystem<Marker, Out, F>39where40F: ExclusiveSystemParamFunction<Marker>,41{42/// Return this system with a new name.43///44/// Useful to give closure systems more readable and unique names for debugging and tracing.45pub fn with_name(mut self, new_name: impl Into<Cow<'static, str>>) -> Self {46self.system_meta.set_name(new_name);47self48}49}5051/// A marker type used to distinguish exclusive function systems from regular function systems.52#[doc(hidden)]53pub struct IsExclusiveFunctionSystem;5455impl<Out, Marker, F> IntoSystem<F::In, Out, (IsExclusiveFunctionSystem, Marker, Out)> for F56where57Out: 'static,58Marker: 'static,59F::Out: IntoResult<Out>,60F: ExclusiveSystemParamFunction<Marker>,61{62type System = ExclusiveFunctionSystem<Marker, Out, F>;63fn into_system(func: Self) -> Self::System {64ExclusiveFunctionSystem {65func,66#[cfg(feature = "hotpatching")]67current_ptr: subsecond::HotFn::current(68<F as ExclusiveSystemParamFunction<Marker>>::run,69)70.ptr_address(),71param_state: None,72system_meta: SystemMeta::new::<F>(),73marker: PhantomData,74}75}76}7778const PARAM_MESSAGE: &str = "System's param_state was not found. Did you forget to initialize this system before running it?";7980impl<Marker, Out, F> System for ExclusiveFunctionSystem<Marker, Out, F>81where82Marker: 'static,83Out: 'static,84F::Out: IntoResult<Out>,85F: ExclusiveSystemParamFunction<Marker>,86{87type In = F::In;88type Out = Out;8990#[inline]91fn name(&self) -> DebugName {92self.system_meta.name.clone()93}9495#[inline]96fn flags(&self) -> SystemStateFlags {97// non-send , exclusive , no deferred98// the executor runs exclusive systems on the main thread, so this99// field reflects that constraint100// exclusive systems have no deferred system params101SystemStateFlags::NON_SEND | SystemStateFlags::EXCLUSIVE102}103104#[inline]105unsafe fn run_unsafe(106&mut self,107input: SystemIn<'_, Self>,108world: UnsafeWorldCell,109) -> Result<Self::Out, RunSystemError> {110// SAFETY: The safety is upheld by the caller.111let world = unsafe { world.world_mut() };112world.last_change_tick_scope(self.system_meta.last_run, |world| {113#[cfg(feature = "trace")]114let _span_guard = self.system_meta.system_span.enter();115116let params = F::Param::get_param(117self.param_state.as_mut().expect(PARAM_MESSAGE),118&self.system_meta,119);120121#[cfg(feature = "hotpatching")]122let out = {123let mut hot_fn =124subsecond::HotFn::current(<F as ExclusiveSystemParamFunction<Marker>>::run);125// SAFETY:126// - pointer used to call is from the current jump table127unsafe {128hot_fn129.try_call_with_ptr(self.current_ptr, (&mut self.func, world, input, params))130.expect("Error calling hotpatched system. Run a full rebuild")131}132};133#[cfg(not(feature = "hotpatching"))]134let out = self.func.run(world, input, params);135136world.flush();137self.system_meta.last_run = world.increment_change_tick();138139IntoResult::into_result(out)140})141}142143#[cfg(feature = "hotpatching")]144#[inline]145fn refresh_hotpatch(&mut self) {146let new = subsecond::HotFn::current(<F as ExclusiveSystemParamFunction<Marker>>::run)147.ptr_address();148if new != self.current_ptr {149log::debug!("system {} hotpatched", self.name());150}151self.current_ptr = new;152}153154#[inline]155fn apply_deferred(&mut self, _world: &mut World) {156// "pure" exclusive systems do not have any buffers to apply.157// Systems made by piping a normal system with an exclusive system158// might have buffers to apply, but this is handled by `PipeSystem`.159}160161#[inline]162fn queue_deferred(&mut self, _world: crate::world::DeferredWorld) {163// "pure" exclusive systems do not have any buffers to apply.164// Systems made by piping a normal system with an exclusive system165// might have buffers to apply, but this is handled by `PipeSystem`.166}167168#[inline]169unsafe fn validate_param_unsafe(170&mut self,171_world: UnsafeWorldCell,172) -> Result<(), SystemParamValidationError> {173// All exclusive system params are always available.174Ok(())175}176177#[inline]178fn initialize(&mut self, world: &mut World) -> FilteredAccessSet {179self.system_meta.last_run = world.change_tick().relative_to(Tick::MAX);180self.param_state = Some(F::Param::init(world, &mut self.system_meta));181FilteredAccessSet::new()182}183184#[inline]185fn check_change_tick(&mut self, check: CheckChangeTicks) {186check_system_change_tick(187&mut self.system_meta.last_run,188check,189self.system_meta.name.clone(),190);191}192193fn default_system_sets(&self) -> Vec<InternedSystemSet> {194let set = crate::schedule::SystemTypeSet::<Self>::new();195vec![set.intern()]196}197198fn get_last_run(&self) -> Tick {199self.system_meta.last_run200}201202fn set_last_run(&mut self, last_run: Tick) {203self.system_meta.last_run = last_run;204}205}206207/// A trait implemented for all exclusive system functions that can be used as [`System`]s.208///209/// This trait can be useful for making your own systems which accept other systems,210/// sometimes called higher order systems.211#[diagnostic::on_unimplemented(212message = "`{Self}` is not an exclusive system",213label = "invalid system"214)]215pub trait ExclusiveSystemParamFunction<Marker>: Send + Sync + 'static {216/// The input type to this system. See [`System::In`].217type In: SystemInput;218219/// The return type of this system. See [`System::Out`].220type Out;221222/// The [`ExclusiveSystemParam`]'s defined by this system's `fn` parameters.223type Param: ExclusiveSystemParam;224225/// Executes this system once. See [`System::run`].226fn run(227&mut self,228world: &mut World,229input: <Self::In as SystemInput>::Inner<'_>,230param_value: ExclusiveSystemParamItem<Self::Param>,231) -> Self::Out;232}233234/// A marker type used to distinguish exclusive function systems with and without input.235#[doc(hidden)]236pub struct HasExclusiveSystemInput;237238macro_rules! impl_exclusive_system_function {239($($param: ident),*) => {240#[expect(241clippy::allow_attributes,242reason = "This is within a macro, and as such, the below lints may not always apply."243)]244#[allow(245non_snake_case,246reason = "Certain variable names are provided by the caller, not by us."247)]248impl<Out, Func, $($param: ExclusiveSystemParam),*> ExclusiveSystemParamFunction<fn($($param,)*) -> Out> for Func249where250Func: Send + Sync + 'static,251for <'a> &'a mut Func:252FnMut(&mut World, $($param),*) -> Out +253FnMut(&mut World, $(ExclusiveSystemParamItem<$param>),*) -> Out,254Out: 'static,255{256type In = ();257type Out = Out;258type Param = ($($param,)*);259#[inline]260fn run(&mut self, world: &mut World, _in: (), param_value: ExclusiveSystemParamItem< ($($param,)*)>) -> Out {261// Yes, this is strange, but `rustc` fails to compile this impl262// without using this function. It fails to recognize that `func`263// is a function, potentially because of the multiple impls of `FnMut`264fn call_inner<Out, $($param,)*>(265mut f: impl FnMut(&mut World, $($param,)*) -> Out,266world: &mut World,267$($param: $param,)*268) -> Out {269f(world, $($param,)*)270}271let ($($param,)*) = param_value;272call_inner(self, world, $($param),*)273}274}275276#[expect(277clippy::allow_attributes,278reason = "This is within a macro, and as such, the below lints may not always apply."279)]280#[allow(281non_snake_case,282reason = "Certain variable names are provided by the caller, not by us."283)]284impl<In, Out, Func, $($param: ExclusiveSystemParam),*> ExclusiveSystemParamFunction<(HasExclusiveSystemInput, fn(In, $($param,)*) -> Out)> for Func285where286Func: Send + Sync + 'static,287for <'a> &'a mut Func:288FnMut(In, &mut World, $($param),*) -> Out +289FnMut(In::Param<'_>, &mut World, $(ExclusiveSystemParamItem<$param>),*) -> Out,290In: SystemInput + 'static,291Out: 'static,292{293type In = In;294type Out = Out;295type Param = ($($param,)*);296#[inline]297fn run(&mut self, world: &mut World, input: In::Inner<'_>, param_value: ExclusiveSystemParamItem< ($($param,)*)>) -> Out {298// Yes, this is strange, but `rustc` fails to compile this impl299// without using this function. It fails to recognize that `func`300// is a function, potentially because of the multiple impls of `FnMut`301fn call_inner<In: SystemInput, Out, $($param,)*>(302_: PhantomData<In>,303mut f: impl FnMut(In::Param<'_>, &mut World, $($param,)*) -> Out,304input: In::Inner<'_>,305world: &mut World,306$($param: $param,)*307) -> Out {308f(In::wrap(input), world, $($param,)*)309}310let ($($param,)*) = param_value;311call_inner(PhantomData::<In>, self, input, world, $($param),*)312}313}314};315}316// Note that we rely on the highest impl to be <= the highest order of the tuple impls317// of `SystemParam` created.318all_tuples!(impl_exclusive_system_function, 0, 16, F);319320#[cfg(test)]321mod tests {322use crate::system::input::SystemInput;323324use super::*;325326#[test]327fn into_system_type_id_consistency() {328fn test<T, In: SystemInput, Out, Marker>(function: T)329where330T: IntoSystem<In, Out, Marker> + Copy,331{332fn reference_system(_world: &mut World) {}333334use core::any::TypeId;335336let system = IntoSystem::into_system(function);337338assert_eq!(339system.type_id(),340function.system_type_id(),341"System::type_id should be consistent with IntoSystem::system_type_id"342);343344assert_eq!(345system.type_id(),346TypeId::of::<T::System>(),347"System::type_id should be consistent with TypeId::of::<T::System>()"348);349350assert_ne!(351system.type_id(),352IntoSystem::into_system(reference_system).type_id(),353"Different systems should have different TypeIds"354);355}356357fn exclusive_function_system(_world: &mut World) {}358359test(exclusive_function_system);360}361}362363364