use bevy::ecs::entity::Entity;
use pyo3::{
exceptions::{PyTypeError, PyValueError},
prelude::*,
types::PyTuple,
};
use super::{PyChildOf, PyEntity, commands::PyCommands, helpers::validity_guard::ValidityFlag};
use crate::ecs::observer_registry::ObserverRegistry;
#[pyclass(name = "EntityCommands")]
#[derive(Debug, Clone)]
pub struct PyEntityCommands {
pub(crate) id: Entity,
commands_ptr: Option<usize>,
world_ptr: Option<usize>,
validity: Option<ValidityFlag>,
}
unsafe impl Send for PyEntityCommands {}
unsafe impl Sync for PyEntityCommands {}
impl PyEntityCommands {
pub(crate) fn with_commands(entity: Entity, commands: &PyCommands) -> Self {
Self {
id: entity,
commands_ptr: Some(commands as *const PyCommands as usize),
world_ptr: None,
validity: Some(commands.validity()),
}
}
pub(crate) fn with_world(entity: Entity, world: &super::world::PyWorld) -> Self {
Self {
id: entity,
commands_ptr: None,
world_ptr: Some(world as *const super::world::PyWorld as usize),
validity: world.validity(),
}
}
fn check_valid(&self) -> PyResult<()> {
if let Some(ref validity) = self.validity {
Ok(validity.check()?)
} else {
Ok(()) }
}
fn get_commands(&self) -> PyResult<Option<&PyCommands>> {
self.check_valid()?;
Ok(self
.commands_ptr
.map(|ptr| unsafe { &*(ptr as *const PyCommands) }))
}
fn get_world(&self) -> PyResult<Option<&super::world::PyWorld>> {
self.check_valid()?;
Ok(self
.world_ptr
.map(|ptr| unsafe { &*(ptr as *const super::world::PyWorld) }))
}
fn temp_commands_from_world(&self) -> PyResult<Option<PyCommands>> {
if let Some(world) = self.get_world()? {
let world_ptr = world.world_ptr();
let validity = world.validity().unwrap_or_else(ValidityFlag::new);
let temp_commands = unsafe { PyCommands::from_world_temporary(world_ptr, validity) };
Ok(Some(temp_commands))
} else {
Ok(None)
}
}
fn get_commands_or_world(&self) -> PyResult<Option<CommandsSource<'_>>> {
if let Some(commands) = self.get_commands()? {
Ok(Some(CommandsSource::Commands(commands)))
} else if let Some(temp) = self.temp_commands_from_world()? {
Ok(Some(CommandsSource::TempFromWorld(temp)))
} else {
Ok(None)
}
}
}
enum CommandsSource<'a> {
Commands(&'a PyCommands),
TempFromWorld(PyCommands),
}
impl<'a> CommandsSource<'a> {
fn as_ref(&self) -> &PyCommands {
match self {
CommandsSource::Commands(c) => c,
CommandsSource::TempFromWorld(c) => c,
}
}
}
#[pymethods]
impl PyEntityCommands {
pub fn id(&self) -> PyEntity {
PyEntity(self.id)
}
#[pyo3(signature = (*components))]
pub fn insert(
&self,
py: Python,
components: &Bound<'_, PyTuple>,
) -> PyResult<PyEntityCommands> {
if let Some(source) = self.get_commands_or_world()? {
crate::ecs::commands::insert_components_to_entity_helper(
source.as_ref(),
py,
self.id,
components,
)?;
Ok(self.clone())
} else {
Err(PyValueError::new_err(
"Cannot insert components: EntityCommands not associated with a Commands or World object.",
))
}
}
#[pyo3(signature = (*components))]
pub fn remove(
&self,
py: Python,
components: &Bound<'_, PyTuple>,
) -> PyResult<PyEntityCommands> {
if let Some(source) = self.get_commands_or_world()? {
crate::ecs::commands::remove_components_from_entity_helper(
source.as_ref(),
py,
self.id,
components,
)?;
Ok(self.clone())
} else {
Err(PyValueError::new_err(
"Cannot remove components: EntityCommands not associated with a Commands or World object.",
))
}
}
pub fn despawn(&self) -> PyResult<()> {
if let Some(source) = self.get_commands_or_world()? {
source.as_ref().despawn(&PyEntity(self.id))
} else {
Err(PyValueError::new_err(
"Cannot despawn: EntityCommands not associated with a Commands or World object.",
))
}
}
pub fn add_child(&self, child: &PyEntity) -> PyResult<PyEntityCommands> {
if let Some(source) = self.get_commands_or_world()? {
crate::ecs::commands::add_child_helper(source.as_ref(), self.id, child.0)?;
Ok(self.clone())
} else {
Err(PyValueError::new_err(
"Cannot add child: EntityCommands not associated with a Commands or World object.",
))
}
}
pub fn set_parent(&self, parent: &PyEntity) -> PyResult<PyEntityCommands> {
if let Some(source) = self.get_commands_or_world()? {
crate::ecs::commands::set_parent_helper(source.as_ref(), self.id, parent.0)?;
Ok(self.clone())
} else {
Err(PyValueError::new_err(
"Cannot set parent: EntityCommands not associated with a Commands or World object.",
))
}
}
pub fn remove_parent(&self) -> PyResult<PyEntityCommands> {
if let Some(source) = self.get_commands_or_world()? {
crate::ecs::commands::remove_parent_helper(source.as_ref(), self.id)?;
Ok(self.clone())
} else {
Err(PyValueError::new_err(
"Cannot remove parent: EntityCommands not associated with a Commands or World object.",
))
}
}
#[pyo3(signature = (*children))]
pub fn remove_children(
&self,
children: &Bound<'_, pyo3::types::PyTuple>,
) -> PyResult<PyEntityCommands> {
if let Some(source) = self.get_commands_or_world()? {
let child_ids: Vec<Entity> = children
.iter()
.map(|item| {
item.extract::<PyEntity>()
.map(|e| e.0)
.map_err(|_| PyTypeError::new_err("Expected Entity objects"))
})
.collect::<PyResult<Vec<_>>>()?;
crate::ecs::commands::remove_children_helper(source.as_ref(), self.id, &child_ids)?;
Ok(self.clone())
} else {
Err(PyValueError::new_err(
"Cannot remove children: EntityCommands not associated with a Commands or World object.",
))
}
}
pub fn clear_children(&self) -> PyResult<PyEntityCommands> {
if let Some(source) = self.get_commands_or_world()? {
crate::ecs::commands::clear_children_helper(source.as_ref(), self.id)?;
Ok(self.clone())
} else {
Err(PyValueError::new_err(
"Cannot clear children: EntityCommands not associated with a Commands or World object.",
))
}
}
pub fn with_children(&self, py: Python, func: Bound<'_, PyAny>) -> PyResult<PyEntityCommands> {
let ty = func.get_type();
if !ty.is_callable() {
return Err(PyValueError::new_err("Parameter must be callable"));
}
if let Some(source) = self.get_commands_or_world()? {
let related_spawner = Py::new(
py,
PyRelatedSpawnerCommands::with_commands(self.id, source.as_ref()),
)?;
func.call1((related_spawner,))?;
Ok(self.clone())
} else {
Err(PyValueError::new_err(
"Cannot spawn children: EntityCommands not associated with a Commands or World object.",
))
}
}
pub fn observe(&self, py: Python, observer: Bound<'_, PyAny>) -> PyResult<PyEntityCommands> {
let world_mut = if let Some(commands) = self.get_commands()? {
commands.try_world_mut()?
} else if let Some(world) = self.get_world()? {
Some(world.world_mut()?)
} else {
None
};
if let Some(world) = world_mut {
let _observer_entity =
ObserverRegistry::register_observer_for_entity(py, &observer, self.id, world)?;
Ok(self.clone())
} else if let Some(commands) = self.get_commands()? {
let entity_id = self.id;
let observer_py: Py<PyAny> = observer.unbind();
commands.execute_or_queue(move |world| {
Python::attach(|py| {
let observer_bound = observer_py.bind(py);
if let Err(e) = ObserverRegistry::register_observer_for_entity(
py,
observer_bound,
entity_id,
world,
) {
eprintln!(
"Error: Failed to register observer via deferred command: {:?}",
e
);
}
});
})?;
Ok(self.clone())
} else {
Err(PyValueError::new_err(
"EntityCommands.observe() requires either World or Commands access.",
))
}
}
}
#[pyclass(name = "RelatedSpawnerCommands")]
pub struct PyRelatedSpawnerCommands {
target: Entity,
commands_ptr: usize,
validity: ValidityFlag,
}
unsafe impl Send for PyRelatedSpawnerCommands {}
unsafe impl Sync for PyRelatedSpawnerCommands {}
impl PyRelatedSpawnerCommands {
fn with_commands(target: Entity, commands: &PyCommands) -> Self {
Self {
target,
commands_ptr: commands as *const PyCommands as usize,
validity: commands.validity(),
}
}
fn get_commands(&self) -> PyResult<&PyCommands> {
self.validity.check()?;
Ok(unsafe { &*(self.commands_ptr as *const PyCommands) })
}
fn create_child_of_component(py: Python, target: Entity) -> PyResult<Py<PyAny>> {
let child_of = Py::new(py, PyChildOf::new(PyEntity(target)))?;
Ok(child_of.into_any())
}
}
#[pymethods]
impl PyRelatedSpawnerCommands {
#[new]
pub fn new(py: Python, commands: Py<PyCommands>, target: PyEntity) -> PyResult<Self> {
let commands_ref = commands.bind(py).borrow();
let commands_ptr = &*commands_ref as *const PyCommands as usize;
let validity = commands_ref.validity();
Ok(Self {
target: target.0,
commands_ptr,
validity,
})
}
pub fn spawn_empty(&self, py: Python) -> PyResult<PyEntityCommands> {
if self.commands_ptr == 0 {
return Err(PyValueError::new_err(
"RelatedSpawnerCommands not properly initialized",
));
}
let commands = self.get_commands()?;
let mut entity_cmd = commands.spawn_empty(py)?;
let child_of = Self::create_child_of_component(py, self.target)?;
let child_of_tuple = PyTuple::new(py, vec![child_of])?;
entity_cmd.insert(py, &child_of_tuple)?;
entity_cmd.commands_ptr = Some(self.commands_ptr);
entity_cmd.world_ptr = None;
entity_cmd.validity = Some(self.validity.clone());
Ok(entity_cmd)
}
#[pyo3(signature = (*components))]
pub fn spawn(&self, py: Python, components: &Bound<'_, PyTuple>) -> PyResult<PyEntityCommands> {
if self.commands_ptr == 0 {
return Err(PyValueError::new_err(
"RelatedSpawnerCommands not properly initialized",
));
}
let commands = self.get_commands()?;
let mut entity_cmd = commands.spawn(py, components)?;
let child_of = Self::create_child_of_component(py, self.target)?;
let child_of_tuple = PyTuple::new(py, vec![child_of])?;
entity_cmd.insert(py, &child_of_tuple)?;
entity_cmd.commands_ptr = Some(self.commands_ptr);
entity_cmd.world_ptr = None;
entity_cmd.validity = Some(self.validity.clone());
Ok(entity_cmd)
}
pub fn target_entity(&self) -> PyEntity {
PyEntity(self.target)
}
}