Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add cached run_system API #14920

Merged
merged 22 commits into from
Sep 23, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion crates/bevy_ecs/src/system/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ mod parallel_scope;

use core::panic::Location;

use super::{Deferred, IntoObserverSystem, IntoSystem, RegisterSystem, Resource};
use super::{
Deferred, IntoObserverSystem, IntoSystem, RegisterSystem, Resource, RunSystemCachedWith,
};
use crate::{
self as bevy_ecs,
bundle::{Bundle, InsertMode},
Expand Down Expand Up @@ -726,6 +728,26 @@ impl<'w, 's> Commands<'w, 's> {
SystemId::from_entity(entity)
}

/// Similar to [`Self::run_system`], but caching the [`SystemId`] in a
/// [`CachedSystemId`](crate::system::CachedSystemId).
pub fn run_system_cached<M: 'static, S: IntoSystem<(), (), M> + 'static>(&mut self, system: S) {
self.run_system_cached_with(system, ());
}

/// Similar to [`Self::run_system_with_input`], but caching the [`SystemId`] in a
/// [`CachedSystemId`](crate::system::CachedSystemId).
pub fn run_system_cached_with<
I: 'static + Send,
M: 'static,
S: IntoSystem<I, (), M> + 'static,
>(
&mut self,
system: S,
input: I,
) {
self.push(RunSystemCachedWith::new(system, input));
}

/// Pushes a generic [`Command`] to the command queue.
///
/// `command` can be a built-in command, custom struct that implements [`Command`] or a closure
Expand Down
127 changes: 115 additions & 12 deletions crates/bevy_ecs/src/system/system_registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::entity::Entity;
use crate::system::{BoxedSystem, IntoSystem};
use crate::world::{Command, World};
use crate::{self as bevy_ecs};
use bevy_ecs_macros::Component;
use bevy_ecs_macros::{Component, Resource};
use thiserror::Error;

/// A small wrapper for [`BoxedSystem`] that also keeps track whether or not the system has been initialized.
Expand Down Expand Up @@ -102,10 +102,32 @@ impl<I, O> std::fmt::Debug for SystemId<I, O> {
}
}

/// A cached [`SystemId`] distinguished by the unique function type of its system.
///
/// This resource is inserted as part of [`World::register_system_cached`].
#[derive(Resource)]
pub struct CachedSystemId<S, I = (), O = ()> {
/// The cached `SystemId`.
pub id: SystemId<I, O>,
_marker: std::marker::PhantomData<fn() -> S>,
}

impl<S, I, O> CachedSystemId<S, I, O> {
/// Creates a new `CachedSystemId` struct given a `SystemId`.
pub fn new(id: SystemId<I, O>) -> Self {
Self {
id,
_marker: std::marker::PhantomData,
}
}
}

impl World {
/// Registers a system and returns a [`SystemId`] so it can later be called by [`World::run_system`].
///
/// It's possible to register the same systems more than once, they'll be stored separately.
/// It's possible to register the same system multiple times, which will create multiple
/// registered systems. To avoid this behavior, consider using
/// [`World::register_system_cached`] instead.
///
/// This is different from adding systems to a [`Schedule`](crate::schedule::Schedule),
/// because the [`SystemId`] that is returned can be used anywhere in the [`World`] to run the associated system.
Expand Down Expand Up @@ -320,6 +342,46 @@ impl World {
}
Ok(result)
}

/// Registers a system and caches its [`SystemId`].
///
/// The first time this is called for a particular system, it will register that system and
/// store its [`SystemId`] in a [`CachedSystemId`] resource. Every subsequent call will
/// retrieve the cached ID instead of creating a new registered system.
///
/// If you don't need to cache the `SystemId`, use [`World::register_system`] instead.
pub fn register_system_cached<I: 'static, O: 'static, M, S: IntoSystem<I, O, M> + 'static>(
&mut self,
system: S,
) -> SystemId<I, O> {
match self.get_resource::<CachedSystemId<S, I, O>>() {
Some(cached) => cached.id,
None => {
let id = self.register_system(system);
self.insert_resource(CachedSystemId::<S, I, O>::new(id));
id
}
}
}

/// Runs a system, registering it and caching its [`SystemId`] if necessary.
pub fn run_system_cached<O: 'static, M, S: IntoSystem<(), O, M> + 'static>(
&mut self,
system: S,
) -> Result<O, RegisteredSystemError<(), O>> {
self.run_system_cached_with(system, ())
}

/// Runs a system with an input, registering the system and caching its [`SystemId`] if
/// necessary.
pub fn run_system_cached_with<I: 'static, O: 'static, M, S: IntoSystem<I, O, M> + 'static>(
&mut self,
system: S,
input: I,
) -> Result<O, RegisteredSystemError<I, O>> {
let id = self.register_system_cached(system);
self.run_system_with_input(id, input)
}
}

/// The [`Command`] type for [`World::run_system`] or [`World::run_system_with_input`].
Expand Down Expand Up @@ -353,7 +415,7 @@ pub struct RunSystemWithInput<I: 'static> {
pub type RunSystem = RunSystemWithInput<()>;

impl RunSystem {
/// Creates a new [`Command`] struct, which can be added to [`Commands`](crate::system::Commands)
/// Creates a new [`Command`] struct, which can be added to [`Commands`](crate::system::Commands).
pub fn new(system_id: SystemId) -> Self {
Self::new_with_input(system_id, ())
}
Expand All @@ -374,16 +436,16 @@ impl<I: 'static + Send> Command for RunSystemWithInput<I> {
}
}

/// The [`Command`] type for registering one shot systems from [Commands](crate::system::Commands).
/// The [`Command`] type for registering one shot systems from [`Commands`](crate::system::Commands).
///
/// This command needs an already boxed system to register, and an already spawned entity
/// This command needs an already boxed system to register, and an already spawned entity.
pub struct RegisterSystem<I: 'static, O: 'static> {
system: BoxedSystem<I, O>,
entity: Entity,
}

impl<I: 'static, O: 'static> RegisterSystem<I, O> {
/// Creates a new [Command] struct, which can be added to [Commands](crate::system::Commands)
/// Creates a new [`Command`] struct, which can be added to [`Commands`](crate::system::Commands).
pub fn new<M, S: IntoSystem<I, O, M> + 'static>(system: S, entity: Entity) -> Self {
Self {
system: Box::new(IntoSystem::into_system(system)),
Expand All @@ -394,12 +456,53 @@ impl<I: 'static, O: 'static> RegisterSystem<I, O> {

impl<I: 'static + Send, O: 'static + Send> Command for RegisterSystem<I, O> {
fn apply(self, world: &mut World) {
let _ = world.get_entity_mut(self.entity).map(|mut entity| {
entity.insert(RegisteredSystem {
initialized: false,
system: self.system,
});
});
if let Some(mut entity) = world.get_entity_mut(self.entity) {
entity.insert((
RegisteredSystem {
initialized: false,
system: self.system,
},
SystemIdMarker,
));
}
}
}

/// The [`Command`] type for registering, caching, and running a one shot system from
/// [`Commands`](crate::system::Commands).
///
/// This command needs an already boxed system to register.
pub struct RunSystemCachedWith<I: 'static, O: 'static, M, S: IntoSystem<I, O, M> + 'static> {
system: BoxedSystem<I, O>,
input: I,
_marker: std::marker::PhantomData<fn() -> (M, S)>,
}

impl<I: 'static, O: 'static, M, S: IntoSystem<I, O, M> + 'static> RunSystemCachedWith<I, O, M, S> {
/// Creates a new [`Command`] struct, which can be added to
/// [`Commands`](crate::system::Commands).
pub fn new(system: S, input: I) -> Self {
Self {
system: Box::new(IntoSystem::into_system(system)),
input,
_marker: std::marker::PhantomData,
}
}
}

impl<I: 'static + Send, O: 'static, M: 'static, S: IntoSystem<I, O, M> + 'static> Command
for RunSystemCachedWith<I, O, M, S>
{
fn apply(self, world: &mut World) {
let id = match world.get_resource::<CachedSystemId<S, I, O>>() {
Some(cached) => cached.id,
None => {
let id = world.register_boxed_system(self.system);
world.insert_resource(CachedSystemId::<S, I, O>::new(id));
id
}
};
let _ = world.run_system_with_input(id, self.input);
}
}

Expand Down