#[cfg(feature = "hotpatching")]
use crate::{change_detection::DetectChanges, HotPatchChanges};
use crate::{
change_detection::Mut,
entity::Entity,
error::BevyError,
prelude::{FromTemplate, Template},
system::{
input::SystemInput, BoxedSystem, Commands, If, IntoSystem, Res, 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 concurrent_queue::ConcurrentQueue;
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 fn despawn_unused_registered_systems(
despawner: If<Res<RegisteredSystemDespawner>>,
mut commands: Commands,
) {
for entity in despawner.queue.try_iter() {
commands.entity(entity).try_despawn();
}
}
#[derive(Resource)]
pub struct RegisteredSystemDespawner {
queue: Arc<ConcurrentQueue<Entity>>,
}
impl Default for RegisteredSystemDespawner {
fn default() -> Self {
Self {
queue: Arc::new(ConcurrentQueue::unbounded()),
}
}
}
pub enum SystemHandle<I: SystemInput = (), O = ()> {
Strong(Arc<StrongSystemHandle>),
Weak(SystemId<I, O>),
}
impl<I: SystemInput, O> SystemHandle<I, O> {
pub fn entity(&self) -> Entity {
match self {
SystemHandle::Strong(strong) => strong.entity,
SystemHandle::Weak(weak) => weak.entity,
}
}
}
impl<I: SystemInput, O> Eq for SystemHandle<I, O> {}
impl<I: SystemInput, O> Clone for SystemHandle<I, O> {
fn clone(&self) -> Self {
match self {
SystemHandle::Strong(strong) => SystemHandle::Strong(Arc::clone(strong)),
SystemHandle::Weak(weak) => SystemHandle::Weak(*weak),
}
}
}
impl<I: SystemInput, O> PartialEq for SystemHandle<I, O> {
fn eq(&self, other: &Self) -> bool {
self.entity() == other.entity()
}
}
impl<I: SystemInput, O> PartialEq<SystemId<I, O>> for SystemHandle<I, O> {
fn eq(&self, other: &SystemId<I, O>) -> bool {
self.entity() == other.entity
}
}
impl<I: SystemInput, O> core::hash::Hash for SystemHandle<I, O> {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.entity().hash(state);
}
}
impl<I: SystemInput, O> core::fmt::Debug for SystemHandle<I, O> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let name = if matches!(self, SystemHandle::Strong(_)) {
"StrongSystemHandle"
} else {
"WeakSystemHandle"
};
f.debug_tuple(name).field(&self.entity()).finish()
}
}
impl<I: SystemInput, O> From<SystemId<I, O>> for SystemHandle<I, O> {
fn from(id: SystemId<I, O>) -> Self {
SystemHandle::Weak(id)
}
}
pub struct StrongSystemHandle {
entity: Entity,
drop_queue: Arc<ConcurrentQueue<Entity>>,
}
impl Drop for StrongSystemHandle {
fn drop(&mut self) {
let _ = self.drop_queue.push(self.entity);
}
}
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()
}
}
impl<I: SystemInput, O> From<&SystemHandle<I, O>> for SystemId<I, O> {
fn from(handle: &SystemHandle<I, O>) -> Self {
Self::from_entity(handle.entity())
}
}
impl<I: SystemInput, O> From<SystemHandle<I, O>> for SystemId<I, O> {
fn from(handle: SystemHandle<I, O>) -> Self {
(&handle).into()
}
}
impl<I: SystemInput + 'static, O: 'static> FromTemplate for SystemHandle<I, O> {
type Template = SystemHandleTemplate<I, O>;
}
pub enum SystemHandleTemplate<I: SystemInput + 'static = (), O: 'static = ()> {
Handle(SystemHandle<I, O>),
Value(SystemHandleValue<I, O>),
}
pub struct SystemHandleValue<I: SystemInput + 'static = (), O: 'static = ()>(
Arc<Mutex<SystemHandleOrValue<I, O>>>,
);
impl<I: SystemInput + 'static, O: 'static> Clone for SystemHandleValue<I, O> {
fn clone(&self) -> Self {
Self(Arc::clone(&self.0))
}
}
enum SystemHandleOrValue<I: SystemInput + 'static = (), O: 'static = ()> {
Handle(SystemHandle<I, O>),
Value(Option<BoxedSystem<I, O>>),
}
impl<I: SystemInput + 'static, O: 'static> SystemHandleTemplate<I, O> {
pub fn value<M>(system: impl IntoSystem<I, O, M>) -> Self {
Self::Value(SystemHandleValue(Arc::new(Mutex::new(
SystemHandleOrValue::Value(Some(Box::new(IntoSystem::into_system(system)))),
))))
}
}
impl<I: SystemInput + 'static, O: 'static> Template for SystemHandleTemplate<I, O> {
type Output = SystemHandle<I, O>;
fn build_template(
&self,
context: &mut TemplateContext,
) -> crate::prelude::Result<Self::Output> {
match self {
Self::Handle(handle) => Ok(handle.clone()),
Self::Value(value) => {
let mut value_or_id = value.0.lock().unwrap();
match &mut *value_or_id {
SystemHandleOrValue::Handle(handle) => Ok(handle.clone()),
SystemHandleOrValue::Value(system) => {
let system = system.take().unwrap();
let id = context
.entity
.world_scope(|world| world.register_tracked_boxed_system(system));
*value_or_id = SystemHandleOrValue::Handle(id.clone());
Ok(id)
}
}
}
}
}
fn clone_template(&self) -> Self {
match self {
Self::Handle(handle) => Self::Handle(handle.clone()),
Self::Value(value) => Self::Value(value.clone()),
}
}
}
impl<I: SystemInput + 'static, O: 'static> Default for SystemHandleTemplate<I, O> {
fn default() -> Self {
Self::Handle(SystemHandle::Weak(SystemId::from_entity(
Entity::PLACEHOLDER,
)))
}
}
impl<I: SystemInput + 'static, O: 'static> From<SystemHandle<I, O>> for SystemHandleTemplate<I, O> {
fn from(handle: SystemHandle<I, O>) -> Self {
Self::Handle(handle)
}
}
impl<I: SystemInput + 'static, O: 'static> From<BoxedSystem<I, O>> for SystemHandleTemplate<I, O> {
fn from(system: BoxedSystem<I, O>) -> Self {
Self::Value(SystemHandleValue(Arc::new(Mutex::new(
SystemHandleOrValue::Value(Some(system)),
))))
}
}
impl<I: SystemInput + 'static, O: 'static> From<SystemId<I, O>> for SystemHandleTemplate<I, O> {
fn from(id: SystemId<I, O>) -> Self {
Self::Handle(SystemHandle::Weak(id))
}
}
pub fn system_value<I: SystemInput + 'static, O: 'static, M>(
system: impl IntoSystem<I, O, M>,
) -> SystemHandleTemplate<I, O> {
SystemHandleTemplate::value(system)
}
#[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 register_tracked_system<I, O, M>(
&mut self,
system: impl IntoSystem<I, O, M> + 'static,
) -> SystemHandle<I, O>
where
I: SystemInput + 'static,
O: 'static,
{
self.register_tracked_boxed_system(Box::new(IntoSystem::into_system(system)))
}
pub fn register_tracked_boxed_system<I, O>(
&mut self,
system: BoxedSystem<I, O>,
) -> SystemHandle<I, O>
where
I: SystemInput + 'static,
O: 'static,
{
let entity = self.spawn(RegisteredSystem::new(system)).id();
let despawner = self.get_resource_or_init::<RegisteredSystemDespawner>();
SystemHandle::Strong(Arc::new(StrongSystemHandle {
entity,
drop_queue: despawner.queue.clone(),
}))
}
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: impl Into<SystemId<(), O>>,
) -> Result<O, RegisteredSystemError<(), O>> {
self.run_system_with(id, ())
}
pub fn run_system_with<I, O>(
&mut self,
id: impl Into<SystemId<I, O>>,
input: I::Inner<'_>,
) -> Result<O, RegisteredSystemError<I, O>>
where
I: SystemInput + 'static,
O: 'static,
{
let id = id.into();
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>()
.is_none_or(|r| r.is_changed_after(system.get_last_run()))
{
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::{
despawn_unused_registered_systems, system_value, RegisteredSystemError, SystemHandle,
SystemHandleTemplate, 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 = "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),
}
}
#[test]
fn despawn_unused() {
let mut world = World::new();
fn system() {}
let handle = world.register_tracked_system(system);
let entity = handle.entity();
drop(handle);
assert!(world.get_entity(entity).is_ok());
world
.run_system_cached(despawn_unused_registered_systems)
.unwrap();
assert!(world.get_entity(entity).is_err());
}
#[test]
fn system_handle_template() {
fn my_system() {}
let mut world = World::new();
{
let my_system_handle = world.register_tracked_system(my_system);
let system_handle = world
.spawn_empty()
.build_template(&SystemHandleTemplate::Handle(my_system_handle.clone()))
.unwrap();
assert_eq!(system_handle, my_system_handle);
}
{
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!(matches!(a, SystemHandle::Strong(_)));
assert!(matches!(b, SystemHandle::Strong(_)));
assert_eq!(a, b);
}
}
#[test]
fn run_system_with_owned_system_handle() {
fn increment(mut counter: ResMut<Counter>) {
counter.0 += 1;
}
let mut world = World::new();
world.insert_resource(Counter(0));
let handle = world.register_tracked_system(increment);
world.run_system(handle).expect("system runs successfully");
assert_eq!(*world.resource::<Counter>(), Counter(1));
}
}