
# Objective First of all, this PR took heavy inspiration from #7760 and #5715. It intends to also fix #5569, but with a slightly different approach. This also fixes #9335 by reexporting `DynEq`. ## Solution The advantage of this API is that we can intern a value without allocating for zero-sized-types and for enum variants that have no fields. This PR does this automatically in the `SystemSet` and `ScheduleLabel` derive macros for unit structs and fieldless enum variants. So this should cover many internal and external use cases of `SystemSet` and `ScheduleLabel`. In these optimal use cases, no memory will be allocated. - The interning returns a `Interned<dyn SystemSet>`, which is just a wrapper around a `&'static dyn SystemSet`. - `Hash` and `Eq` are implemented in terms of the pointer value of the reference, similar to my first approach of anonymous system sets in #7676. - Therefore, `Interned<T>` does not implement `Borrow<T>`, only `Deref`. - The debug output of `Interned<T>` is the same as the interned value. Edit: - `AppLabel` is now also interned and the old `derive_label`/`define_label` macros were replaced with the new interning implementation. - Anonymous set ids are reused for different `Schedule`s, reducing the amount of leaked memory. ### Pros - `InternedSystemSet` and `InternedScheduleLabel` behave very similar to the current `BoxedSystemSet` and `BoxedScheduleLabel`, but can be copied without an allocation. - Many use cases don't allocate at all. - Very fast lookups and comparisons when using `InternedSystemSet` and `InternedScheduleLabel`. - The `intern` module might be usable in other areas. - `Interned{ScheduleLabel, SystemSet, AppLabel}` does implement `{ScheduleLabel, SystemSet, AppLabel}`, increasing ergonomics. ### Cons - Implementors of `SystemSet` and `ScheduleLabel` still need to implement `Hash` and `Eq` (and `Clone`) for it to work. ## Changelog ### Added - Added `intern` module to `bevy_utils`. - Added reexports of `DynEq` to `bevy_ecs` and `bevy_app`. ### Changed - Replaced `BoxedSystemSet` and `BoxedScheduleLabel` with `InternedSystemSet` and `InternedScheduleLabel`. - Replaced `impl AsRef<dyn ScheduleLabel>` with `impl ScheduleLabel`. - Replaced `AppLabelId` with `InternedAppLabel`. - Changed `AppLabel` to use `Debug` for error messages. - Changed `AppLabel` to use interning. - Changed `define_label`/`derive_label` to use interning. - Replaced `define_boxed_label`/`derive_boxed_label` with `define_label`/`derive_label`. - Changed anonymous set ids to be only unique inside a schedule, not globally. - Made interned label types implement their label trait. ### Removed - Removed `define_boxed_label` and `derive_boxed_label`. ## Migration guide - Replace `BoxedScheduleLabel` and `Box<dyn ScheduleLabel>` with `InternedScheduleLabel` or `Interned<dyn ScheduleLabel>`. - Replace `BoxedSystemSet` and `Box<dyn SystemSet>` with `InternedSystemSet` or `Interned<dyn SystemSet>`. - Replace `AppLabelId` with `InternedAppLabel` or `Interned<dyn AppLabel>`. - Types manually implementing `ScheduleLabel`, `AppLabel` or `SystemSet` need to implement: - `dyn_hash` directly instead of implementing `DynHash` - `as_dyn_eq` - Pass labels to `World::try_schedule_scope`, `World::schedule_scope`, `World::try_run_schedule`. `World::run_schedule`, `Schedules::remove`, `Schedules::remove_entry`, `Schedules::contains`, `Schedules::get` and `Schedules::get_mut` by value instead of by reference. --------- Co-authored-by: Joseph <21144246+JoJoJet@users.noreply.github.com> Co-authored-by: Carter Anderson <mcanders1@gmail.com>
171 lines
4.7 KiB
Rust
171 lines
4.7 KiB
Rust
use std::borrow::Cow;
|
|
|
|
use super::{ReadOnlySystem, System};
|
|
use crate::{schedule::InternedSystemSet, 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<InternedSystemSet> {
|
|
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))
|
|
}
|
|
}
|