#[cfg(feature = "hotpatching")]
use crate::{change_detection::DetectChanges, HotPatchChanges};
use crate::{
change_detection::Mut,
entity::Entity,
error::BevyError,
system::{
input::SystemInput, BoxedSystem, IntoSystem, RunSystemError, SystemParamValidationError,
},
world::World,
};
use alloc::boxed::Box;
use bevy_ecs_macros::{Component, Resource};
use bevy_utils::prelude::DebugName;
use core::{any::TypeId, marker::PhantomData};
use thiserror::Error;
#[derive(Component)]
#[require(SystemIdMarker = SystemIdMarker::typed_system_id_marker::<I, O>())]
pub(crate) struct RegisteredSystem<I, O> {
initialized: bool,
system: Option<BoxedSystem<I, O>>,
}
impl<I, O> RegisteredSystem<I, O> {
pub fn new(system: BoxedSystem<I, O>) -> Self {
RegisteredSystem {
initialized: false,
system: Some(system),
}
}
}
#[derive(Debug, Clone)]
struct TypeIdAndName {
type_id: TypeId,
name: DebugName,
}
impl TypeIdAndName {
fn new<T: 'static>() -> Self {
Self {
type_id: TypeId::of::<T>(),
name: DebugName::type_name::<T>(),
}
}
}
impl Default for TypeIdAndName {
fn default() -> Self {
Self {
type_id: TypeId::of::<()>(),
name: DebugName::type_name::<()>(),
}
}
}
#[derive(Debug, Default, Clone, Component)]
pub struct SystemIdMarker {
input_type_id: TypeIdAndName,
output_type_id: TypeIdAndName,
}
impl SystemIdMarker {
fn typed_system_id_marker<I: 'static, O: 'static>() -> Self {
Self {
input_type_id: TypeIdAndName::new::<I>(),
output_type_id: TypeIdAndName::new::<O>(),
}
}
}
pub struct RemovedSystem<I = (), O = ()> {
initialized: bool,
system: BoxedSystem<I, O>,
}
impl<I, O> RemovedSystem<I, O> {
pub fn initialized(&self) -> bool {
self.initialized
}
pub fn system(self) -> BoxedSystem<I, O> {
self.system
}
}
pub struct SystemId<I: SystemInput = (), O = ()> {
pub(crate) entity: Entity,
pub(crate) marker: PhantomData<fn(I) -> O>,
}
impl<I: SystemInput, O> SystemId<I, O> {
pub fn entity(self) -> Entity {
self.entity
}
pub fn from_entity(entity: Entity) -> Self {
Self {
entity,
marker: PhantomData,
}
}
}
impl<I: SystemInput, O> Eq for SystemId<I, O> {}
impl<I: SystemInput, O> Copy for SystemId<I, O> {}
impl<I: SystemInput, O> Clone for SystemId<I, O> {
fn clone(&self) -> Self {
*self
}
}
impl<I: SystemInput, O> PartialEq for SystemId<I, O> {
fn eq(&self, other: &Self) -> bool {
self.entity == other.entity && self.marker == other.marker
}
}
impl<I: SystemInput, O> core::hash::Hash for SystemId<I, O> {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.entity.hash(state);
}
}
impl<I: SystemInput, O> core::fmt::Debug for SystemId<I, O> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_tuple("SystemId").field(&self.entity).finish()
}
}
#[derive(Resource)]
pub struct CachedSystemId<S> {
pub entity: Entity,
_marker: PhantomData<fn() -> S>,
}
impl<S> CachedSystemId<S> {
pub fn new<I: SystemInput, O>(id: SystemId<I, O>) -> Self {
Self {
entity: id.entity(),
_marker: PhantomData,
}
}
}
impl World {
pub fn register_system<I, O, M>(
&mut self,
system: impl IntoSystem<I, O, M> + 'static,
) -> SystemId<I, O>
where
I: SystemInput + 'static,
O: 'static,
{
self.register_boxed_system(Box::new(IntoSystem::into_system(system)))
}
pub fn register_boxed_system<I, O>(&mut self, system: BoxedSystem<I, O>) -> SystemId<I, O>
where
I: SystemInput + 'static,
O: 'static,
{
let entity = self.spawn(RegisteredSystem::new(system)).id();
SystemId::from_entity(entity)
}
pub fn unregister_system<I, O>(
&mut self,
id: SystemId<I, O>,
) -> Result<RemovedSystem<I, O>, RegisteredSystemError<I, O>>
where
I: SystemInput + 'static,
O: 'static,
{
match self.get_entity_mut(id.entity) {
Ok(mut entity) => {
let registered_system = entity
.take::<RegisteredSystem<I, O>>()
.ok_or(RegisteredSystemError::SelfRemove(id))?;
entity.despawn();
Ok(RemovedSystem {
initialized: registered_system.initialized,
system: registered_system
.system
.ok_or(RegisteredSystemError::SystemMissing(id))?,
})
}
Err(_) => Err(RegisteredSystemError::SystemIdNotRegistered(id)),
}
}
pub fn run_system<O: 'static>(
&mut self,
id: SystemId<(), O>,
) -> Result<O, RegisteredSystemError<(), O>> {
self.run_system_with(id, ())
}
pub fn run_system_with<I, O>(
&mut self,
id: SystemId<I, O>,
input: I::Inner<'_>,
) -> Result<O, RegisteredSystemError<I, O>>
where
I: SystemInput + 'static,
O: 'static,
{
let mut entity = self
.get_entity_mut(id.entity)
.map_err(|_| RegisteredSystemError::SystemIdNotRegistered(id))?;
let Some(mut registered_system) = entity.get_mut::<RegisteredSystem<I, O>>() else {
let Some(system_id_marker) = entity.get::<SystemIdMarker>() else {
return Err(RegisteredSystemError::SystemIdNotRegistered(id));
};
if system_id_marker.input_type_id.type_id != TypeId::of::<I>()
|| system_id_marker.output_type_id.type_id != TypeId::of::<O>()
{
return Err(RegisteredSystemError::IncorrectType(
id,
system_id_marker.clone(),
));
}
return Err(RegisteredSystemError::MissingRegisteredSystemComponent(id));
};
let mut system = registered_system
.system
.take()
.ok_or(RegisteredSystemError::SystemMissing(id))?;
if !registered_system.initialized {
system.initialize(self);
}
#[cfg(feature = "hotpatching")]
if self
.get_resource_ref::<HotPatchChanges>()
.map(|r| r.last_changed())
.unwrap_or_default()
.is_newer_than(system.get_last_run(), self.change_tick())
{
system.refresh_hotpatch();
}
let result = system.run_without_applying_deferred(input, self);
system.queue_deferred(self.into());
if let Ok(mut entity) = self.get_entity_mut(id.entity)
&& let Some(mut registered_system) = entity.get_mut::<RegisteredSystem<I, O>>()
{
registered_system.system = Some(system);
registered_system.initialized = true;
}
self.flush();
Ok(result?)
}
pub fn register_system_cached<I, O, M, S>(&mut self, system: S) -> SystemId<I, O>
where
I: SystemInput + 'static,
O: 'static,
S: IntoSystem<I, O, M> + 'static,
{
const {
assert!(
size_of::<S>() == 0,
"Non-ZST systems (e.g. capturing closures, function pointers) cannot be cached.",
);
}
if !self.contains_resource::<CachedSystemId<S>>() {
let id = self.register_system(system);
self.insert_resource(CachedSystemId::<S>::new(id));
return id;
}
self.resource_scope(|world, mut id: Mut<CachedSystemId<S>>| {
if let Ok(mut entity) = world.get_entity_mut(id.entity) {
if !entity.contains::<RegisteredSystem<I, O>>() {
entity.insert(RegisteredSystem::new(Box::new(IntoSystem::into_system(
system,
))));
}
} else {
id.entity = world.register_system(system).entity();
}
SystemId::from_entity(id.entity)
})
}
pub fn unregister_system_cached<I, O, M, S>(
&mut self,
_system: S,
) -> Result<RemovedSystem<I, O>, RegisteredSystemError<I, O>>
where
I: SystemInput + 'static,
O: 'static,
S: IntoSystem<I, O, M> + 'static,
{
let id = self
.remove_resource::<CachedSystemId<S>>()
.ok_or(RegisteredSystemError::SystemNotCached)?;
self.unregister_system(SystemId::<I, O>::from_entity(id.entity))
}
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, ())
}
pub fn run_system_cached_with<I, O, M, S>(
&mut self,
system: S,
input: I::Inner<'_>,
) -> Result<O, RegisteredSystemError<I, O>>
where
I: SystemInput + 'static,
O: 'static,
S: IntoSystem<I, O, M> + 'static,
{
let id = self.register_system_cached(system);
self.run_system_with(id, input)
}
}
#[derive(Error)]
pub enum RegisteredSystemError<I: SystemInput = (), O = ()> {
#[error("System {0:?} was not registered")]
SystemIdNotRegistered(SystemId<I, O>),
#[error("Cached system was not found")]
SystemNotCached,
#[error("System {0:?} does not have a RegisteredSystem component. This only happens if app code removed the component.")]
MissingRegisteredSystemComponent(SystemId<I, O>),
#[error("System {0:?} tried to remove itself")]
SelfRemove(SystemId<I, O>),
#[error("System did not run due to failed parameter validation: {0}")]
Skipped(SystemParamValidationError),
#[error("System returned error: {0}")]
Failed(BevyError),
#[error("Could not get system from `{}`, entity was `SystemId<{}, {}>`", DebugName::type_name::<SystemId<I, O>>(), .1.input_type_id.name, .1.output_type_id.name)]
IncorrectType(SystemId<I, O>, SystemIdMarker),
#[error("The system is not present in the RegisteredSystem component. This can happen if the system was called recursively or if the system panicked on the last run.")]
SystemMissing(SystemId<I, O>),
}
impl<I: SystemInput, O> From<RunSystemError> for RegisteredSystemError<I, O> {
fn from(value: RunSystemError) -> Self {
match value {
RunSystemError::Skipped(err) => Self::Skipped(err),
RunSystemError::Failed(err) => Self::Failed(err),
}
}
}
impl<I: SystemInput, O> core::fmt::Debug for RegisteredSystemError<I, O> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::SystemIdNotRegistered(arg0) => {
f.debug_tuple("SystemIdNotRegistered").field(arg0).finish()
}
Self::SystemNotCached => write!(f, "SystemNotCached"),
Self::MissingRegisteredSystemComponent(arg0) => f
.debug_tuple("MissingRegisteredSystemComponent")
.field(arg0)
.finish(),
Self::SelfRemove(arg0) => f.debug_tuple("SelfRemove").field(arg0).finish(),
Self::Skipped(arg0) => f.debug_tuple("Skipped").field(arg0).finish(),
Self::Failed(arg0) => f.debug_tuple("Failed").field(arg0).finish(),
Self::IncorrectType(arg0, arg1) => f
.debug_tuple("IncorrectType")
.field(arg0)
.field(arg1)
.finish(),
Self::SystemMissing(arg0) => f.debug_tuple("SystemMissing").field(arg0).finish(),
}
}
}
#[cfg(test)]
mod tests {
use core::cell::Cell;
use bevy_utils::default;
use crate::{
prelude::*,
system::{RegisteredSystemError, SystemId},
};
#[derive(Resource, Default, PartialEq, Debug)]
struct Counter(u8);
#[test]
fn change_detection() {
#[derive(Resource, Default)]
struct ChangeDetector;
fn count_up_iff_changed(
mut counter: ResMut<Counter>,
change_detector: ResMut<ChangeDetector>,
) {
if change_detector.is_changed() {
counter.0 += 1;
}
}
let mut world = World::new();
world.init_resource::<ChangeDetector>();
world.init_resource::<Counter>();
assert_eq!(*world.resource::<Counter>(), Counter(0));
let id = world.register_system(count_up_iff_changed);
world.run_system(id).expect("system runs successfully");
assert_eq!(*world.resource::<Counter>(), Counter(1));
world.run_system(id).expect("system runs successfully");
assert_eq!(*world.resource::<Counter>(), Counter(1));
world.resource_mut::<ChangeDetector>().set_changed();
world.run_system(id).expect("system runs successfully");
assert_eq!(*world.resource::<Counter>(), Counter(2));
}
#[test]
fn local_variables() {
fn doubling(last_counter: Local<Counter>, mut counter: ResMut<Counter>) {
counter.0 += last_counter.0 .0;
last_counter.0 .0 = counter.0;
}
let mut world = World::new();
world.insert_resource(Counter(1));
assert_eq!(*world.resource::<Counter>(), Counter(1));
let id = world.register_system(doubling);
world.run_system(id).expect("system runs successfully");
assert_eq!(*world.resource::<Counter>(), Counter(1));
world.run_system(id).expect("system runs successfully");
assert_eq!(*world.resource::<Counter>(), Counter(2));
world.run_system(id).expect("system runs successfully");
assert_eq!(*world.resource::<Counter>(), Counter(4));
world.run_system(id).expect("system runs successfully");
assert_eq!(*world.resource::<Counter>(), Counter(8));
}
#[test]
fn input_values() {
struct NonCopy(u8);
fn increment_sys(In(NonCopy(increment_by)): In<NonCopy>, mut counter: ResMut<Counter>) {
counter.0 += increment_by;
}
let mut world = World::new();
let id = world.register_system(increment_sys);
world.insert_resource(Counter(1));
assert_eq!(*world.resource::<Counter>(), Counter(1));
world
.run_system_with(id, NonCopy(1))
.expect("system runs successfully");
assert_eq!(*world.resource::<Counter>(), Counter(2));
world
.run_system_with(id, NonCopy(1))
.expect("system runs successfully");
assert_eq!(*world.resource::<Counter>(), Counter(3));
world
.run_system_with(id, NonCopy(20))
.expect("system runs successfully");
assert_eq!(*world.resource::<Counter>(), Counter(23));
world
.run_system_with(id, NonCopy(1))
.expect("system runs successfully");
assert_eq!(*world.resource::<Counter>(), Counter(24));
}
#[test]
fn output_values() {
#[derive(Eq, PartialEq, Debug)]
struct NonCopy(u8);
fn increment_sys(mut counter: ResMut<Counter>) -> NonCopy {
counter.0 += 1;
NonCopy(counter.0)
}
let mut world = World::new();
let id = world.register_system(increment_sys);
world.insert_resource(Counter(1));
assert_eq!(*world.resource::<Counter>(), Counter(1));
let output = world.run_system(id).expect("system runs successfully");
assert_eq!(*world.resource::<Counter>(), Counter(2));
assert_eq!(output, NonCopy(2));
let output = world.run_system(id).expect("system runs successfully");
assert_eq!(*world.resource::<Counter>(), Counter(3));
assert_eq!(output, NonCopy(3));
}
#[test]
fn fallible_system() {
fn sys() -> Result<()> {
Err("error")?;
Ok(())
}
let mut world = World::new();
let fallible_system_id = world.register_system(sys);
let output = world.run_system(fallible_system_id);
assert!(matches!(output, Ok(Err(_))));
}
#[test]
fn exclusive_system() {
let mut world = World::new();
let exclusive_system_id = world.register_system(|world: &mut World| {
world.spawn_empty();
});
let entity_count = world.entities.count_spawned();
let _ = world.run_system(exclusive_system_id);
assert_eq!(world.entities.count_spawned(), entity_count + 1);
}
#[test]
fn nested_systems() {
use crate::system::SystemId;
#[derive(Component)]
struct Callback(SystemId);
fn nested(query: Query<&Callback>, mut commands: Commands) {
for callback in query.iter() {
commands.run_system(callback.0);
}
}
let mut world = World::new();
world.insert_resource(Counter(0));
let increment_two = world.register_system(|mut counter: ResMut<Counter>| {
counter.0 += 2;
});
let increment_three = world.register_system(|mut counter: ResMut<Counter>| {
counter.0 += 3;
});
let nested_id = world.register_system(nested);
world.spawn(Callback(increment_two));
world.spawn(Callback(increment_three));
let _ = world.run_system(nested_id);
assert_eq!(*world.resource::<Counter>(), Counter(5));
}
#[test]
fn nested_systems_with_inputs() {
use crate::system::SystemId;
#[derive(Component)]
struct Callback(SystemId<In<u8>>, u8);
fn nested(query: Query<&Callback>, mut commands: Commands) {
for callback in query.iter() {
commands.run_system_with(callback.0, callback.1);
}
}
let mut world = World::new();
world.insert_resource(Counter(0));
let increment_by =
world.register_system(|In(amt): In<u8>, mut counter: ResMut<Counter>| {
counter.0 += amt;
});
let nested_id = world.register_system(nested);
world.spawn(Callback(increment_by, 2));
world.spawn(Callback(increment_by, 3));
let _ = world.run_system(nested_id);
assert_eq!(*world.resource::<Counter>(), Counter(5));
}
#[test]
fn cached_system() {
use crate::system::RegisteredSystemError;
fn four() -> i32 {
4
}
let mut world = World::new();
let old = world.register_system_cached(four);
let new = world.register_system_cached(four);
assert_eq!(old, new);
let result = world.unregister_system_cached(four);
assert!(result.is_ok());
let new = world.register_system_cached(four);
assert_ne!(old, new);
let output = world.run_system(old);
assert!(matches!(
output,
Err(RegisteredSystemError::SystemIdNotRegistered(x)) if x == old,
));
let output = world.run_system(new);
assert!(matches!(output, Ok(x) if x == four()));
let output = world.run_system_cached(four);
assert!(matches!(output, Ok(x) if x == four()));
let output = world.run_system_cached_with(four, ());
assert!(matches!(output, Ok(x) if x == four()));
}
#[test]
fn cached_fallible_system() {
fn sys() -> Result<()> {
Err("error")?;
Ok(())
}
let mut world = World::new();
let fallible_system_id = world.register_system_cached(sys);
let output = world.run_system(fallible_system_id);
assert!(matches!(output, Ok(Err(_))));
let output = world.run_system_cached(sys);
assert!(matches!(output, Ok(Err(_))));
let output = world.run_system_cached_with(sys, ());
assert!(matches!(output, Ok(Err(_))));
}
#[test]
fn cached_system_commands() {
fn sys(mut counter: ResMut<Counter>) {
counter.0 += 1;
}
let mut world = World::new();
world.insert_resource(Counter(0));
world.commands().run_system_cached(sys);
world.flush_commands();
assert_eq!(world.resource::<Counter>().0, 1);
world.commands().run_system_cached_with(sys, ());
world.flush_commands();
assert_eq!(world.resource::<Counter>().0, 2);
}
#[test]
fn cached_fallible_system_commands() {
fn sys(mut counter: ResMut<Counter>) -> Result {
counter.0 += 1;
Ok(())
}
let mut world = World::new();
world.insert_resource(Counter(0));
world.commands().run_system_cached(sys);
world.flush_commands();
assert_eq!(world.resource::<Counter>().0, 1);
world.commands().run_system_cached_with(sys, ());
world.flush_commands();
assert_eq!(world.resource::<Counter>().0, 2);
}
#[test]
#[should_panic(expected = "This system always fails")]
fn cached_fallible_system_commands_can_fail() {
use crate::system::command;
fn sys() -> Result {
Err("This system always fails".into())
}
let mut world = World::new();
world.commands().queue(command::run_system_cached(sys));
world.flush_commands();
}
#[test]
fn cached_system_adapters() {
fn four() -> i32 {
4
}
fn double(In(i): In<i32>) -> i32 {
i * 2
}
let mut world = World::new();
let output = world.run_system_cached(four.pipe(double));
assert!(matches!(output, Ok(8)));
let output = world.run_system_cached(four.map(|i| i * 2));
assert!(matches!(output, Ok(8)));
}
#[test]
fn cached_system_into_same_system_type() {
struct Foo;
impl IntoSystem<(), (), ()> for Foo {
type System = ApplyDeferred;
fn into_system(_: Self) -> Self::System {
ApplyDeferred
}
}
struct Bar;
impl IntoSystem<(), (), ()> for Bar {
type System = ApplyDeferred;
fn into_system(_: Self) -> Self::System {
ApplyDeferred
}
}
let mut world = World::new();
let foo1 = world.register_system_cached(Foo);
let foo2 = world.register_system_cached(Foo);
let bar1 = world.register_system_cached(Bar);
let bar2 = world.register_system_cached(Bar);
assert_ne!(foo1, bar1);
assert_eq!(foo1, foo2);
assert_eq!(bar1, bar2);
}
#[test]
fn system_with_input_ref() {
fn with_ref(InRef(input): InRef<u8>, mut counter: ResMut<Counter>) {
counter.0 += *input;
}
let mut world = World::new();
world.insert_resource(Counter(0));
let id = world.register_system(with_ref);
world.run_system_with(id, &2).unwrap();
assert_eq!(*world.resource::<Counter>(), Counter(2));
}
#[test]
fn system_with_input_mut() {
#[derive(Event)]
struct MyEvent {
cancelled: bool,
}
fn post(InMut(event): InMut<MyEvent>, counter: ResMut<Counter>) {
if counter.0 > 0 {
event.cancelled = true;
}
}
let mut world = World::new();
world.insert_resource(Counter(0));
let post_system = world.register_system(post);
let mut event = MyEvent { cancelled: false };
world.run_system_with(post_system, &mut event).unwrap();
assert!(!event.cancelled);
world.resource_mut::<Counter>().0 = 1;
world.run_system_with(post_system, &mut event).unwrap();
assert!(event.cancelled);
}
#[test]
fn run_system_invalid_params() {
use crate::system::RegisteredSystemError;
use alloc::string::ToString;
#[derive(Resource)]
struct T;
fn system(_: Res<T>) {}
let mut world = World::new();
let id = world.register_system(system);
let result = world.run_system(id);
assert!(matches!(result, Err(RegisteredSystemError::Failed { .. })));
let expected = "Resource does not exist";
let actual = result.unwrap_err().to_string();
assert!(
actual.contains(expected),
"Expected error message to contain `{}` but got `{}`",
expected,
actual
);
}
#[test]
fn run_system_recursive() {
std::thread_local! {
static INVOCATIONS_LEFT: Cell<i32> = const { Cell::new(3) };
static SYSTEM_ID: Cell<Option<SystemId>> = default();
}
fn system(mut commands: Commands) {
let count = INVOCATIONS_LEFT.get() - 1;
INVOCATIONS_LEFT.set(count);
if count > 0 {
commands.run_system(SYSTEM_ID.get().unwrap());
}
}
let mut world = World::new();
let id = world.register_system(system);
SYSTEM_ID.set(Some(id));
world.run_system(id).unwrap();
assert_eq!(INVOCATIONS_LEFT.get(), 0);
}
#[test]
fn run_system_exclusive_adapters() {
let mut world = World::new();
fn system(_: &mut World) {}
world.run_system_cached(system).unwrap();
world.run_system_cached(system.pipe(system)).unwrap();
world.run_system_cached(system.map(|()| {})).unwrap();
}
#[test]
fn wrong_system_type() {
fn test() -> Result<(), u8> {
Ok(())
}
let mut world = World::new();
let entity = world.register_system_cached(test).entity();
match world.run_system::<u8>(SystemId::from_entity(entity)) {
Ok(_) => panic!("Should fail since called `run_system` with wrong SystemId type."),
Err(RegisteredSystemError::IncorrectType(_, _)) => (),
Err(err) => panic!("Failed with wrong error. `{:?}`", err),
}
}
}