use alloc::{string::ToString, vec::Vec};
#[cfg(not(feature = "trace"))]
use log::info;
#[cfg(feature = "trace")]
use tracing::info;
use crate::{
bundle::{Bundle, InsertMode},
change_detection::MaybeLocation,
component::{Component, ComponentId},
entity::{Entity, EntityClonerBuilder, OptIn, OptOut},
event::EntityEvent,
name::Name,
relationship::RelationshipHookMode,
system::IntoObserverSystem,
world::{error::EntityMutableFetchError, EntityWorldMut, FromWorld},
};
use bevy_ptr::{move_as_ptr, OwningPtr};
pub trait EntityCommand<Out = ()>: Send + 'static {
fn apply(self, entity: EntityWorldMut) -> Out;
}
#[derive(thiserror::Error, Debug)]
pub enum EntityCommandError<E> {
#[error(transparent)]
EntityFetchError(#[from] EntityMutableFetchError),
#[error("{0}")]
CommandFailed(E),
}
impl<Out, F> EntityCommand<Out> for F
where
F: FnOnce(EntityWorldMut) -> Out + Send + 'static,
{
fn apply(self, entity: EntityWorldMut) -> Out {
self(entity)
}
}
#[track_caller]
pub fn insert(bundle: impl Bundle, mode: InsertMode) -> impl EntityCommand {
let caller = MaybeLocation::caller();
move |mut entity: EntityWorldMut| {
move_as_ptr!(bundle);
entity.insert_with_caller(bundle, mode, caller, RelationshipHookMode::Run);
}
}
#[track_caller]
pub unsafe fn insert_by_id<T: Send + 'static>(
component_id: ComponentId,
value: T,
mode: InsertMode,
) -> impl EntityCommand {
let caller = MaybeLocation::caller();
move |mut entity: EntityWorldMut| {
OwningPtr::make(value, |ptr| unsafe {
entity.insert_by_id_with_caller(
component_id,
ptr,
mode,
caller,
RelationshipHookMode::Run,
);
});
}
}
#[track_caller]
pub fn insert_from_world<T: Component + FromWorld>(mode: InsertMode) -> impl EntityCommand {
let caller = MaybeLocation::caller();
move |mut entity: EntityWorldMut| {
if !(mode == InsertMode::Keep && entity.contains::<T>()) {
let value = entity.world_scope(|world| T::from_world(world));
move_as_ptr!(value);
entity.insert_with_caller(value, mode, caller, RelationshipHookMode::Run);
}
}
}
#[track_caller]
pub fn insert_with<T: Component, F>(component_fn: F, mode: InsertMode) -> impl EntityCommand
where
F: FnOnce() -> T + Send + 'static,
{
let caller = MaybeLocation::caller();
move |mut entity: EntityWorldMut| {
if !(mode == InsertMode::Keep && entity.contains::<T>()) {
let bundle = component_fn();
move_as_ptr!(bundle);
entity.insert_with_caller(bundle, mode, caller, RelationshipHookMode::Run);
}
}
}
#[track_caller]
pub fn remove<T: Bundle>() -> impl EntityCommand {
let caller = MaybeLocation::caller();
move |mut entity: EntityWorldMut| {
entity.remove_with_caller::<T>(caller);
}
}
#[track_caller]
pub fn remove_with_requires<T: Bundle>() -> impl EntityCommand {
let caller = MaybeLocation::caller();
move |mut entity: EntityWorldMut| {
entity.remove_with_requires_with_caller::<T>(caller);
}
}
#[track_caller]
pub fn remove_by_id(component_id: ComponentId) -> impl EntityCommand {
let caller = MaybeLocation::caller();
move |mut entity: EntityWorldMut| {
entity.remove_by_id_with_caller(component_id, caller);
}
}
#[track_caller]
pub fn clear() -> impl EntityCommand {
let caller = MaybeLocation::caller();
move |mut entity: EntityWorldMut| {
entity.clear_with_caller(caller);
}
}
#[track_caller]
pub fn retain<T: Bundle>() -> impl EntityCommand {
let caller = MaybeLocation::caller();
move |mut entity: EntityWorldMut| {
entity.retain_with_caller::<T>(caller);
}
}
#[track_caller]
pub fn despawn() -> impl EntityCommand {
let caller = MaybeLocation::caller();
move |entity: EntityWorldMut| {
entity.despawn_with_caller(caller);
}
}
#[track_caller]
pub fn observe<E: EntityEvent, B: Bundle, M>(
observer: impl IntoObserverSystem<E, B, M>,
) -> impl EntityCommand {
let caller = MaybeLocation::caller();
move |mut entity: EntityWorldMut| {
entity.observe_with_caller(observer, caller);
}
}
pub fn clone_with_opt_out(
target: Entity,
config: impl FnOnce(&mut EntityClonerBuilder<OptOut>) + Send + Sync + 'static,
) -> impl EntityCommand {
move |mut entity: EntityWorldMut| {
entity.clone_with_opt_out(target, config);
}
}
pub fn clone_with_opt_in(
target: Entity,
config: impl FnOnce(&mut EntityClonerBuilder<OptIn>) + Send + Sync + 'static,
) -> impl EntityCommand {
move |mut entity: EntityWorldMut| {
entity.clone_with_opt_in(target, config);
}
}
pub fn clone_components<B: Bundle>(target: Entity) -> impl EntityCommand {
move |mut entity: EntityWorldMut| {
entity.clone_components::<B>(target);
}
}
pub fn move_components<B: Bundle>(target: Entity) -> impl EntityCommand {
move |mut entity: EntityWorldMut| {
entity.move_components::<B>(target);
}
}
pub fn log_components() -> impl EntityCommand {
move |entity: EntityWorldMut| {
let name = entity.get::<Name>().map(ToString::to_string);
let id = entity.id();
let mut components: Vec<_> = entity
.world()
.inspect_entity(id)
.expect("Entity existence is verified before an EntityCommand is executed")
.map(|info| info.name().to_string())
.collect();
components.sort();
#[cfg(not(feature = "debug"))]
{
let component_count = components.len();
#[cfg(feature = "trace")]
{
if let Some(name) = name {
info!(id=?id, name=?name, ?component_count, "log_components. Enable the `debug` feature to log component names.");
} else {
info!(id=?id, ?component_count, "log_components. Enable the `debug` feature to log component names.");
}
}
#[cfg(not(feature = "trace"))]
{
let name = name
.map(|name| alloc::format!(" ({name})"))
.unwrap_or_default();
info!("Entity {id}{name}: {component_count} components. Enable the `debug` feature to log component names.");
}
}
#[cfg(feature = "debug")]
{
#[cfg(feature = "trace")]
{
if let Some(name) = name {
info!(id=?id, name=?name, ?components, "log_components");
} else {
info!(id=?id, ?components, "log_components");
}
}
#[cfg(not(feature = "trace"))]
{
let name = name
.map(|name| alloc::format!(" ({name})"))
.unwrap_or_default();
info!("Entity {id}{name}: {components:?}");
}
}
}
}