forked from bevyengine/bevy
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add
system.map(...)
for transforming the output of a system (bevyen…
…gine#8526) # Objective Any time we wish to transform the output of a system, we currently use system piping to do so: ```rust my_system.pipe(|In(x)| do_something(x)) ``` Unfortunately, system piping is not a zero cost abstraction. Each call to `.pipe` requires allocating two extra access sets: one for the second system and one for the combined accesses of both systems. This also adds extra work to each call to `update_archetype_component_access`, which stacks as one adds multiple layers of system piping. ## Solution Add the `AdapterSystem` abstraction: similar to `CombinatorSystem`, this allows you to implement a trait to generically control how a system is run and how its inputs and outputs are processed. Unlike `CombinatorSystem`, this does not have any overhead when computing world accesses which makes it ideal for simple operations such as inverting or ignoring the output of a system. Add the extension method `.map(...)`: this is similar to `.pipe(...)`, only it accepts a closure as an argument instead of an `In<T>` system. ```rust my_system.map(do_something) ``` This has the added benefit of making system names less messy: a system that ignores its output will just be called `my_system`, instead of `Pipe(my_system, ignore)` --- ## Changelog TODO ## Migration Guide The `system_adapter` functions have been deprecated: use `.map` instead, which is a lightweight alternative to `.pipe`. ```rust // Before: my_system.pipe(system_adapter::ignore) my_system.pipe(system_adapter::unwrap) my_system.pipe(system_adapter::new(T::from)) // After: my_system.map(std::mem::drop) my_system.map(Result::unwrap) my_system.map(T::from) // Before: my_system.pipe(system_adapter::info) my_system.pipe(system_adapter::dbg) my_system.pipe(system_adapter::warn) my_system.pipe(system_adapter::error) // After: my_system.map(bevy_utils::info) my_system.map(bevy_utils::dbg) my_system.map(bevy_utils::warn) my_system.map(bevy_utils::error) ``` --------- Co-authored-by: Alice Cecile <[email protected]>
- Loading branch information
Showing
6 changed files
with
261 additions
and
98 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,170 @@ | ||
use std::borrow::Cow; | ||
|
||
use super::{ReadOnlySystem, System}; | ||
use crate::world::unsafe_world_cell::UnsafeWorldCell; | ||
|
||
/// Customizes the behavior of an [`AdapterSystem`] | ||
/// | ||
/// # Examples | ||
/// | ||
/// ``` | ||
/// # use bevy_ecs::prelude::*; | ||
/// use bevy_ecs::system::{Adapt, AdapterSystem}; | ||
/// | ||
/// // A system adapter that inverts the result of a system. | ||
/// // NOTE: Instead of manually implementing this, you can just use `bevy_ecs::schedule::common_conditions::not`. | ||
/// pub type NotSystem<S> = AdapterSystem<NotMarker, S>; | ||
/// | ||
/// // This struct is used to customize the behavior of our adapter. | ||
/// pub struct NotMarker; | ||
/// | ||
/// impl<S> Adapt<S> for NotMarker | ||
/// where | ||
/// S: System, | ||
/// S::Out: std::ops::Not, | ||
/// { | ||
/// type In = S::In; | ||
/// type Out = <S::Out as std::ops::Not>::Output; | ||
/// | ||
/// fn adapt( | ||
/// &mut self, | ||
/// input: Self::In, | ||
/// run_system: impl FnOnce(S::In) -> S::Out, | ||
/// ) -> Self::Out { | ||
/// !run_system(input) | ||
/// } | ||
/// } | ||
/// # let mut world = World::new(); | ||
/// # let mut system = NotSystem::new(NotMarker, IntoSystem::into_system(|| false), "".into()); | ||
/// # system.initialize(&mut world); | ||
/// # assert!(system.run((), &mut world)); | ||
/// ``` | ||
pub trait Adapt<S: System>: Send + Sync + 'static { | ||
/// The [input](System::In) type for an [`AdapterSystem`]. | ||
type In; | ||
/// The [output](System::Out) type for an [`AdapterSystem`]. | ||
type Out; | ||
|
||
/// When used in an [`AdapterSystem`], this function customizes how the system | ||
/// is run and how its inputs/outputs are adapted. | ||
fn adapt(&mut self, input: Self::In, run_system: impl FnOnce(S::In) -> S::Out) -> Self::Out; | ||
} | ||
|
||
/// A [`System`] that takes the output of `S` and transforms it by applying `Func` to it. | ||
#[derive(Clone)] | ||
pub struct AdapterSystem<Func, S> { | ||
func: Func, | ||
system: S, | ||
name: Cow<'static, str>, | ||
} | ||
|
||
impl<Func, S> AdapterSystem<Func, S> | ||
where | ||
Func: Adapt<S>, | ||
S: System, | ||
{ | ||
/// Creates a new [`System`] that uses `func` to adapt `system`, via the [`Adapt`] trait. | ||
pub const fn new(func: Func, system: S, name: Cow<'static, str>) -> Self { | ||
Self { func, system, name } | ||
} | ||
} | ||
|
||
impl<Func, S> System for AdapterSystem<Func, S> | ||
where | ||
Func: Adapt<S>, | ||
S: System, | ||
{ | ||
type In = Func::In; | ||
type Out = Func::Out; | ||
|
||
fn name(&self) -> Cow<'static, str> { | ||
self.name.clone() | ||
} | ||
|
||
fn type_id(&self) -> std::any::TypeId { | ||
std::any::TypeId::of::<Self>() | ||
} | ||
|
||
fn component_access(&self) -> &crate::query::Access<crate::component::ComponentId> { | ||
self.system.component_access() | ||
} | ||
|
||
#[inline] | ||
fn archetype_component_access( | ||
&self, | ||
) -> &crate::query::Access<crate::archetype::ArchetypeComponentId> { | ||
self.system.archetype_component_access() | ||
} | ||
|
||
fn is_send(&self) -> bool { | ||
self.system.is_send() | ||
} | ||
|
||
fn is_exclusive(&self) -> bool { | ||
self.system.is_exclusive() | ||
} | ||
|
||
#[inline] | ||
unsafe fn run_unsafe(&mut self, input: Self::In, world: UnsafeWorldCell) -> Self::Out { | ||
// SAFETY: `system.run_unsafe` has the same invariants as `self.run_unsafe`. | ||
self.func | ||
.adapt(input, |input| self.system.run_unsafe(input, world)) | ||
} | ||
|
||
#[inline] | ||
fn run(&mut self, input: Self::In, world: &mut crate::prelude::World) -> Self::Out { | ||
self.func | ||
.adapt(input, |input| self.system.run(input, world)) | ||
} | ||
|
||
#[inline] | ||
fn apply_deferred(&mut self, world: &mut crate::prelude::World) { | ||
self.system.apply_deferred(world); | ||
} | ||
|
||
fn initialize(&mut self, world: &mut crate::prelude::World) { | ||
self.system.initialize(world); | ||
} | ||
|
||
#[inline] | ||
fn update_archetype_component_access(&mut self, world: UnsafeWorldCell) { | ||
self.system.update_archetype_component_access(world); | ||
} | ||
|
||
fn check_change_tick(&mut self, change_tick: crate::component::Tick) { | ||
self.system.check_change_tick(change_tick); | ||
} | ||
|
||
fn get_last_run(&self) -> crate::component::Tick { | ||
self.system.get_last_run() | ||
} | ||
|
||
fn set_last_run(&mut self, last_run: crate::component::Tick) { | ||
self.system.set_last_run(last_run); | ||
} | ||
|
||
fn default_system_sets(&self) -> Vec<Box<dyn crate::schedule::SystemSet>> { | ||
self.system.default_system_sets() | ||
} | ||
} | ||
|
||
// SAFETY: The inner system is read-only. | ||
unsafe impl<Func, S> ReadOnlySystem for AdapterSystem<Func, S> | ||
where | ||
Func: Adapt<S>, | ||
S: ReadOnlySystem, | ||
{ | ||
} | ||
|
||
impl<F, S, Out> Adapt<S> for F | ||
where | ||
S: System, | ||
F: Send + Sync + 'static + FnMut(S::Out) -> Out, | ||
{ | ||
type In = S::In; | ||
type Out = Out; | ||
|
||
fn adapt(&mut self, input: S::In, run_system: impl FnOnce(S::In) -> S::Out) -> Out { | ||
self(run_system(input)) | ||
} | ||
} |
Oops, something went wrong.