bevy/crates/bevy_ecs/src/system/function_system.rs
Alexander Sepity afb33234db Optional .system(), part 3 (#2422)
# Objective

- Continue work of #2398 and #2403.
- Make `.system()` syntax optional when using `.config()` API.

## Solution

- Introduce new prelude trait, `ConfigurableSystem`, that shorthands `my_system.system().config(...)` as `my_system.config(...)`.
- Expand `configure_system_local` test to also cover the new syntax.
2021-07-01 19:09:34 +00:00

470 lines
16 KiB
Rust

use crate::{
archetype::{Archetype, ArchetypeComponentId, ArchetypeGeneration, ArchetypeId},
component::ComponentId,
query::{Access, FilteredAccessSet},
system::{
check_system_change_tick, ReadOnlySystemParamFetch, System, SystemId, SystemParam,
SystemParamFetch, SystemParamState,
},
world::{World, WorldId},
};
use bevy_ecs_macros::all_tuples;
use std::{borrow::Cow, marker::PhantomData};
/// The metadata of a [`System`].
pub struct SystemMeta {
pub(crate) id: SystemId,
pub(crate) name: Cow<'static, str>,
pub(crate) component_access_set: FilteredAccessSet<ComponentId>,
pub(crate) archetype_component_access: Access<ArchetypeComponentId>,
// NOTE: this must be kept private. making a SystemMeta non-send is irreversible to prevent
// SystemParams from overriding each other
is_send: bool,
pub(crate) last_change_tick: u32,
}
impl SystemMeta {
fn new<T>() -> Self {
Self {
name: std::any::type_name::<T>().into(),
archetype_component_access: Access::default(),
component_access_set: FilteredAccessSet::default(),
is_send: true,
id: SystemId::new(),
last_change_tick: 0,
}
}
/// Returns true if the system is [`Send`].
#[inline]
pub fn is_send(&self) -> bool {
self.is_send
}
/// Sets the system to be not [`Send`].
///
/// This is irreversible.
#[inline]
pub fn set_non_send(&mut self) {
self.is_send = false;
}
}
// TODO: Actually use this in FunctionSystem. We should probably only do this once Systems are constructed using a World reference
// (to avoid the need for unwrapping to retrieve SystemMeta)
/// Holds on to persistent state required to drive [`SystemParam`] for a [`System`].
pub struct SystemState<Param: SystemParam> {
meta: SystemMeta,
param_state: <Param as SystemParam>::Fetch,
world_id: WorldId,
archetype_generation: ArchetypeGeneration,
}
impl<Param: SystemParam> SystemState<Param> {
pub fn new(world: &mut World) -> Self {
let config = <Param::Fetch as SystemParamState>::default_config();
Self::with_config(world, config)
}
pub fn with_config(
world: &mut World,
config: <Param::Fetch as SystemParamState>::Config,
) -> Self {
let mut meta = SystemMeta::new::<Param>();
let param_state = <Param::Fetch as SystemParamState>::init(world, &mut meta, config);
Self {
meta,
param_state,
world_id: world.id(),
archetype_generation: ArchetypeGeneration::initial(),
}
}
#[inline]
pub fn meta(&self) -> &SystemMeta {
&self.meta
}
/// Retrieve the [`SystemParam`] values. This can only be called when all parameters are read-only.
#[inline]
pub fn get<'a>(&'a mut self, world: &'a World) -> <Param::Fetch as SystemParamFetch<'a>>::Item
where
Param::Fetch: ReadOnlySystemParamFetch,
{
self.validate_world_and_update_archetypes(world);
// SAFE: Param is read-only and doesn't allow mutable access to World. It also matches the World this SystemState was created with.
unsafe { self.get_unchecked_manual(world) }
}
/// Retrieve the mutable [`SystemParam`] values.
#[inline]
pub fn get_mut<'a>(
&'a mut self,
world: &'a mut World,
) -> <Param::Fetch as SystemParamFetch<'a>>::Item {
self.validate_world_and_update_archetypes(world);
// SAFE: World is uniquely borrowed and matches the World this SystemState was created with.
unsafe { self.get_unchecked_manual(world) }
}
/// Applies all state queued up for [`SystemParam`] values. For example, this will apply commands queued up
/// by a [`Commands`](`super::Commands`) parameter to the given [`World`].
/// This function should be called manually after the values returned by [`SystemState::get`] and [`SystemState::get_mut`]
/// are finished being used.
pub fn apply(&mut self, world: &mut World) {
self.param_state.apply(world);
}
#[inline]
pub fn matches_world(&self, world: &World) -> bool {
self.world_id == world.id()
}
fn validate_world_and_update_archetypes(&mut self, world: &World) {
assert!(self.matches_world(world), "Encountered a mismatched World. A SystemState cannot be used with Worlds other than the one it was created with.");
let archetypes = world.archetypes();
let new_generation = archetypes.generation();
let old_generation = std::mem::replace(&mut self.archetype_generation, new_generation);
let archetype_index_range = old_generation.value()..new_generation.value();
for archetype_index in archetype_index_range {
self.param_state.new_archetype(
&archetypes[ArchetypeId::new(archetype_index)],
&mut self.meta,
);
}
}
/// Retrieve the [`SystemParam`] values. This will not update archetypes automatically.
///
/// # Safety
/// This call might access any of the input parameters in a way that violates Rust's mutability rules. Make sure the data
/// access is safe in the context of global [`World`] access. The passed-in [`World`] _must_ be the [`World`] the [`SystemState`] was
/// created with.
#[inline]
pub unsafe fn get_unchecked_manual<'a>(
&'a mut self,
world: &'a World,
) -> <Param::Fetch as SystemParamFetch<'a>>::Item {
let change_tick = world.increment_change_tick();
let param = <Param::Fetch as SystemParamFetch>::get_param(
&mut self.param_state,
&self.meta,
world,
change_tick,
);
self.meta.last_change_tick = change_tick;
param
}
}
/// Conversion trait to turn something into a [`System`].
///
/// Use this to get a system from a function. Also note that every system implements this trait as
/// well.
///
/// # Examples
///
/// ```
/// use bevy_ecs::system::IntoSystem;
/// use bevy_ecs::system::Res;
///
/// fn my_system_function(an_usize_resource: Res<usize>) {}
///
/// let system = my_system_function.system();
/// ```
// This trait has to be generic because we have potentially overlapping impls, in particular
// because Rust thinks a type could impl multiple different `FnMut` combinations
// even though none can currently
pub trait IntoSystem<In, Out, Params> {
type System: System<In = In, Out = Out>;
/// Turns this value into its corresponding [`System`].
fn system(self) -> Self::System;
}
pub struct AlreadyWasSystem;
// Systems implicitly implement IntoSystem
impl<In, Out, Sys: System<In = In, Out = Out>> IntoSystem<In, Out, AlreadyWasSystem> for Sys {
type System = Sys;
fn system(self) -> Sys {
self
}
}
/// Wrapper type to mark a [`SystemParam`] as an input.
///
/// [`System`]s may take an optional input which they require to be passed to them when they
/// are being [`run`](System::run). For [`FunctionSystems`](FunctionSystem) the input may be marked
/// with this `In` type, but only the first param of a function may be tagged as an input. This also
/// means a system can only have one or zero input paramaters.
///
/// # Examples
///
/// Here is a simple example of a system that takes a [`usize`] returning the square of it.
///
/// ```
/// use bevy_ecs::prelude::*;
///
/// fn main() {
/// let mut square_system = square.system();
///
/// let mut world = World::default();
/// square_system.initialize(&mut world);
/// assert_eq!(square_system.run(12, &mut world), 144);
/// }
///
/// fn square(In(input): In<usize>) -> usize {
/// input * input
/// }
/// ```
pub struct In<In>(pub In);
pub struct InputMarker;
/// The [`System`] counter part of an ordinary function.
///
/// You get this by calling [`IntoSystem::system`] on a function that only accepts
/// [`SystemParam`]s. The output of the system becomes the functions return type, while the input
/// becomes the functions [`In`] tagged parameter or `()` if no such paramater exists.
pub struct FunctionSystem<In, Out, Param, Marker, F>
where
Param: SystemParam,
{
func: F,
param_state: Option<Param::Fetch>,
system_meta: SystemMeta,
config: Option<<Param::Fetch as SystemParamState>::Config>,
// NOTE: PhantomData<fn()-> T> gives this safe Send/Sync impls
#[allow(clippy::type_complexity)]
marker: PhantomData<fn() -> (In, Out, Marker)>,
}
impl<In, Out, Param: SystemParam, Marker, F> FunctionSystem<In, Out, Param, Marker, F> {
/// Gives mutable access to the systems config via a callback. This is useful to set up system
/// [`Local`](crate::system::Local)s.
///
/// # Examples
///
/// ```
/// # use bevy_ecs::prelude::*;
/// # let world = &mut World::default();
/// fn local_is_42(local: Local<usize>) {
/// assert_eq!(*local, 42);
/// }
/// let mut system = local_is_42.system().config(|config| config.0 = Some(42));
/// system.initialize(world);
/// system.run((), world);
/// ```
pub fn config(
mut self,
f: impl FnOnce(&mut <Param::Fetch as SystemParamState>::Config),
) -> Self {
f(self.config.as_mut().unwrap());
self
}
}
/// Provides `my_system.config(...)` API.
pub trait ConfigurableSystem<In, Out, Param: SystemParam, Marker>:
IntoSystem<In, Out, (IsFunctionSystem, Param, Marker)>
{
/// See [`FunctionSystem::config()`](crate::system::FunctionSystem::config).
fn config(
self,
f: impl FnOnce(&mut <Param::Fetch as SystemParamState>::Config),
) -> Self::System;
}
impl<In, Out, Param: SystemParam, Marker, F> ConfigurableSystem<In, Out, Param, Marker> for F
where
In: 'static,
Out: 'static,
Param: SystemParam + 'static,
Marker: 'static,
F: SystemParamFunction<In, Out, Param, Marker>
+ IntoSystem<
In,
Out,
(IsFunctionSystem, Param, Marker),
System = FunctionSystem<In, Out, Param, Marker, F>,
> + Send
+ Sync
+ 'static,
{
fn config(
self,
f: impl FnOnce(&mut <<Param as SystemParam>::Fetch as SystemParamState>::Config),
) -> Self::System {
self.system().config(f)
}
}
pub struct IsFunctionSystem;
impl<In, Out, Param, Marker, F> IntoSystem<In, Out, (IsFunctionSystem, Param, Marker)> for F
where
In: 'static,
Out: 'static,
Param: SystemParam + 'static,
Marker: 'static,
F: SystemParamFunction<In, Out, Param, Marker> + Send + Sync + 'static,
{
type System = FunctionSystem<In, Out, Param, Marker, F>;
fn system(self) -> Self::System {
FunctionSystem {
func: self,
param_state: None,
config: Some(<Param::Fetch as SystemParamState>::default_config()),
system_meta: SystemMeta::new::<F>(),
marker: PhantomData,
}
}
}
impl<In, Out, Param, Marker, F> System for FunctionSystem<In, Out, Param, Marker, F>
where
In: 'static,
Out: 'static,
Param: SystemParam + 'static,
Marker: 'static,
F: SystemParamFunction<In, Out, Param, Marker> + Send + Sync + 'static,
{
type In = In;
type Out = Out;
#[inline]
fn name(&self) -> Cow<'static, str> {
self.system_meta.name.clone()
}
#[inline]
fn id(&self) -> SystemId {
self.system_meta.id
}
#[inline]
fn new_archetype(&mut self, archetype: &Archetype) {
let param_state = self.param_state.as_mut().unwrap();
param_state.new_archetype(archetype, &mut self.system_meta);
}
#[inline]
fn component_access(&self) -> &Access<ComponentId> {
&self.system_meta.component_access_set.combined_access()
}
#[inline]
fn archetype_component_access(&self) -> &Access<ArchetypeComponentId> {
&self.system_meta.archetype_component_access
}
#[inline]
fn is_send(&self) -> bool {
self.system_meta.is_send
}
#[inline]
unsafe fn run_unsafe(&mut self, input: Self::In, world: &World) -> Self::Out {
let change_tick = world.increment_change_tick();
let out = self.func.run(
input,
self.param_state.as_mut().unwrap(),
&self.system_meta,
world,
change_tick,
);
self.system_meta.last_change_tick = change_tick;
out
}
#[inline]
fn apply_buffers(&mut self, world: &mut World) {
let param_state = self.param_state.as_mut().unwrap();
param_state.apply(world);
}
#[inline]
fn initialize(&mut self, world: &mut World) {
self.param_state = Some(<Param::Fetch as SystemParamState>::init(
world,
&mut self.system_meta,
self.config.take().unwrap(),
));
}
#[inline]
fn check_change_tick(&mut self, change_tick: u32) {
check_system_change_tick(
&mut self.system_meta.last_change_tick,
change_tick,
self.system_meta.name.as_ref(),
);
}
}
/// A trait implemented for all functions that can be used as [`System`]s.
pub trait SystemParamFunction<In, Out, Param: SystemParam, Marker>: Send + Sync + 'static {
/// # Safety
///
/// This call might access any of the input parameters in an unsafe way. Make sure the data
/// access is safe in the context of the system scheduler.
unsafe fn run(
&mut self,
input: In,
state: &mut Param::Fetch,
system_meta: &SystemMeta,
world: &World,
change_tick: u32,
) -> Out;
}
macro_rules! impl_system_function {
($($param: ident),*) => {
#[allow(non_snake_case)]
impl<Out, Func: Send + Sync + 'static, $($param: SystemParam),*> SystemParamFunction<(), Out, ($($param,)*), ()> for Func
where
for <'a> &'a mut Func:
FnMut($($param),*) -> Out +
FnMut($(<<$param as SystemParam>::Fetch as SystemParamFetch>::Item),*) -> Out, Out: 'static
{
#[inline]
unsafe fn run(&mut self, _input: (), state: &mut <($($param,)*) as SystemParam>::Fetch, system_meta: &SystemMeta, world: &World, change_tick: u32) -> Out {
// Yes, this is strange, but rustc fails to compile this impl
// without using this function.
#[allow(clippy::too_many_arguments)]
fn call_inner<Out, $($param,)*>(
mut f: impl FnMut($($param,)*)->Out,
$($param: $param,)*
)->Out{
f($($param,)*)
}
let ($($param,)*) = <<($($param,)*) as SystemParam>::Fetch as SystemParamFetch>::get_param(state, system_meta, world, change_tick);
call_inner(self, $($param),*)
}
}
#[allow(non_snake_case)]
impl<Input, Out, Func: Send + Sync + 'static, $($param: SystemParam),*> SystemParamFunction<Input, Out, ($($param,)*), InputMarker> for Func
where
for <'a> &'a mut Func:
FnMut(In<Input>, $($param),*) -> Out +
FnMut(In<Input>, $(<<$param as SystemParam>::Fetch as SystemParamFetch>::Item),*) -> Out, Out: 'static
{
#[inline]
unsafe fn run(&mut self, input: Input, state: &mut <($($param,)*) as SystemParam>::Fetch, system_meta: &SystemMeta, world: &World, change_tick: u32) -> Out {
#[allow(clippy::too_many_arguments)]
fn call_inner<Input, Out, $($param,)*>(
mut f: impl FnMut(In<Input>, $($param,)*)->Out,
input: In<Input>,
$($param: $param,)*
)->Out{
f(input, $($param,)*)
}
let ($($param,)*) = <<($($param,)*) as SystemParam>::Fetch as SystemParamFetch>::get_param(state, system_meta, world, change_tick);
call_inner(self, In(input), $($param),*)
}
}
};
}
all_tuples!(impl_system_function, 0, 16, F);