Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
129 changes: 128 additions & 1 deletion crates/bevy_ecs/src/system/system_registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@ use crate::{
change_detection::Mut,
entity::Entity,
error::BevyError,
prelude::{FromTemplate, Template},
system::{
input::SystemInput, BoxedSystem, IntoSystem, RunSystemError, SystemParamValidationError,
},
template::TemplateContext,
world::World,
};
use alloc::boxed::Box;
use bevy_ecs_macros::{Component, Resource};
use bevy_platform::sync::{Arc, Mutex};
use bevy_utils::prelude::DebugName;
use core::{any::TypeId, marker::PhantomData};
use thiserror::Error;
Expand Down Expand Up @@ -158,6 +161,105 @@ impl<I: SystemInput, O> core::fmt::Debug for SystemId<I, O> {
}
}

impl<I: SystemInput + 'static, O: 'static> FromTemplate for SystemId<I, O> {
type Template = SystemIdTemplate<I, O>;
}

/// A [`Template`] that produces a [`SystemId`].
pub enum SystemIdTemplate<I: SystemInput + 'static = (), O: 'static = ()> {
/// Creates a [`SystemId`] by cloning the given [`SystemId`] value.
Id(SystemId<I, O>),
/// Creates a [`SystemId`] by registering the given system value using
/// [`World::register_system`]. This will cache the resulting [`SystemId`]
/// on the template and reuse it for future template builds.
///
/// This should generally be constructed using [`SystemIdTemplate::value`] or
/// [`system_value`].
Value(SystemIdValue<I, O>),
}

/// Stores an [`Arc<Mutex<SystemIdOrValue<I, O>>>`].
pub struct SystemIdValue<I: SystemInput + 'static = (), O: 'static = ()>(
Arc<Mutex<SystemIdOrValue<I, O>>>,
);

impl<I: SystemInput + 'static, O: 'static> Clone for SystemIdValue<I, O> {
fn clone(&self) -> Self {
Self(Arc::clone(&self.0))
}
}

enum SystemIdOrValue<I: SystemInput + 'static = (), O: 'static = ()> {
Id(SystemId<I, O>),
Value(Option<BoxedSystem<I, O>>),
}

impl<I: SystemInput + 'static, O: 'static> SystemIdTemplate<I, O> {
/// This will create a new [`SystemIdTemplate`] for the given `system` value.
/// This makes it possible to define systems "inline" in templates / scenes
/// that produce a [`SystemId`].
pub fn value<M>(system: impl IntoSystem<I, O, M>) -> Self {
Self::Value(SystemIdValue(Arc::new(Mutex::new(SystemIdOrValue::Value(
Some(Box::new(IntoSystem::into_system(system))),
)))))
}
}

impl<I: SystemInput + 'static, O: 'static> Template for SystemIdTemplate<I, O> {
type Output = SystemId<I, O>;

fn build_template(
&self,
context: &mut TemplateContext,
) -> crate::prelude::Result<Self::Output> {
match self {
Self::Id(id) => Ok(*id),
Self::Value(value) => {
let mut value_or_id = value.0.lock().unwrap();
match &mut *value_or_id {
SystemIdOrValue::Id(id) => Ok(*id),
SystemIdOrValue::Value(system) => {
let system = system.take().unwrap_or_else(|| unreachable!());
let id = context
.entity
.world_scope(|world| world.register_boxed_system(system));
*value_or_id = SystemIdOrValue::Id(id);
Ok(id)
}
}
}
}
}

fn clone_template(&self) -> Self {
match self {
Self::Id(id) => Self::Id(*id),
Self::Value(value) => Self::Value(value.clone()),
}
}
}

impl<I: SystemInput + 'static, O: 'static> Default for SystemIdTemplate<I, O> {
fn default() -> Self {
Self::Id(SystemId::from_entity(Entity::PLACEHOLDER))
}
}

impl<I: SystemInput + 'static, O: 'static> From<SystemId<I, O>> for SystemIdTemplate<I, O> {
fn from(id: SystemId<I, O>) -> Self {
Self::Id(id)
}
}

/// This will create a new [`SystemIdTemplate`] for the given `system` value.
/// This makes it possible to define systems "inline" in templates / scenes that
/// produce a [`SystemId`].
pub fn system_value<I: SystemInput + 'static, O: 'static, M>(
system: impl IntoSystem<I, O, M>,
) -> SystemIdTemplate<I, O> {
SystemIdTemplate::value(system)
}

/// A cached [`SystemId`] distinguished by the unique function type of its system.
///
/// This resource is inserted by [`World::register_system_cached`].
Expand Down Expand Up @@ -604,7 +706,7 @@ mod tests {

use crate::{
prelude::*,
system::{RegisteredSystemError, SystemId},
system::{system_value, RegisteredSystemError, SystemId, SystemIdTemplate},
};

#[derive(Resource, Default, PartialEq, Debug)]
Expand Down Expand Up @@ -1074,4 +1176,29 @@ mod tests {
Err(err) => panic!("Failed with wrong error. `{:?}`", err),
}
}

#[test]
fn system_id_template() {
fn my_system() {}

let mut world = World::new();

{
let my_system_id = world.register_system(my_system);
let system_id = world
.spawn_empty()
.build_template(&SystemIdTemplate::Id(my_system_id))
.unwrap();
assert_eq!(system_id, my_system_id);
}

{
let template = system_value(my_system);

let a = world.spawn_empty().build_template(&template).unwrap();
let b = world.spawn_empty().build_template(&template).unwrap();

assert_eq!(a, b);
}
}
}
21 changes: 19 additions & 2 deletions examples/ecs/callbacks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,25 @@
//! This pattern trades some performance for flexibility and works well for things like cutscenes, scripted events,
//! or one-off UI-driven interactions that don't need to run every frame.

use bevy::{ecs::system::SystemId, prelude::*};
use bevy::{
ecs::system::{system_value, SystemId},
prelude::*,
scene::ScenePlugin,
};

fn main() {
let mut app = App::new();

// These two plugins are only needed when spawning via a scene!
// Spawning via `Commands::spawn` works without them.
app.add_plugins((AssetPlugin::default(), ScenePlugin));

app.add_systems(Startup, setup_callbacks);
app.add_systems(Update, run_callbacks);
app.run();
}

#[derive(Component)]
#[derive(Component, FromTemplate)] // `FromTemplate` is only needed when spawning via a scene!
struct Callback {
system_id: SystemId<(), ()>,
}
Expand Down Expand Up @@ -42,6 +51,14 @@ fn setup_callbacks(mut commands: Commands) {
commands.spawn(trivial_callback);
commands.spawn(ordinary_system_callback);
commands.spawn(exclusive_callback);

commands.spawn_scene(bsn! {
Callback {
system_id: system_value(|| {
println!("This is a callback spawned via a scene.");
})
}
});
}

// In many cases, you might want to use an observer to detect when a callback should run,
Expand Down
Loading