use pyo3::{PyTypeInfo, exceptions::PyTypeError, prelude::*, types::PyType};
use super::{
PyEntity,
component_type::{ComponentRegistry, PyComponentType},
};
#[pyclass(name = "Event", subclass)]
#[derive(Debug, Clone)]
pub struct PyEvent;
#[pymethods]
impl PyEvent {
#[new]
#[pyo3(signature = (*_args, **_kwargs))]
pub fn new(
_args: &Bound<'_, pyo3::types::PyTuple>,
_kwargs: Option<&Bound<'_, pyo3::types::PyDict>>,
) -> Self {
PyEvent
}
}
#[pyclass(name = "OnAdd")]
#[derive(Debug, Clone)]
pub struct PyOnAdd;
#[pyclass(name = "OnInsert")]
#[derive(Debug, Clone)]
pub struct PyOnInsert;
#[pyclass(name = "OnRemove")]
#[derive(Debug, Clone)]
pub struct PyOnRemove;
#[pyclass(name = "OnReplace")]
#[derive(Debug, Clone)]
pub struct PyOnReplace;
#[pyclass(name = "OnDespawn")]
#[derive(Debug, Clone)]
pub struct PyOnDespawn;
#[pyclass(name = "On", frozen)]
pub struct PyOn {
pub(crate) event_data: Py<PyAny>,
pub(crate) entity: Option<bevy::ecs::entity::Entity>,
}
#[pymethods]
impl PyOn {
#[classmethod]
#[pyo3(signature = (key, /))]
pub fn __class_getitem__(
_cls: &Bound<'_, PyType>,
key: &Bound<'_, PyAny>,
) -> PyResult<Py<PyAny>> {
let py = key.py();
if let Ok(tuple) = key.cast_exact::<pyo3::types::PyTuple>() {
if tuple.len() != 2 {
return Err(PyTypeError::new_err(
"On expects 1 or 2 type parameters: On[EventType] or On[EventType, BundleType] or On[LifecycleEvent, ComponentType]",
));
}
let first_key = tuple.get_item(0)?;
let second_key = tuple.get_item(1)?;
let first_type_obj = first_key.cast_exact::<PyType>()?;
if first_type_obj.is(&PyOnAdd::type_object(py)) {
let comp_type = if let Ok(comp_type_obj) = second_key.cast_exact::<PyType>() {
PyComponentType::try_from((comp_type_obj, py))?
} else {
return Err(PyTypeError::new_err(
"Second parameter to On[OnAdd, ...] must be a Component type",
));
};
return Py::new(
py,
PyOnTypeParam {
event_type: EventType::Add(comp_type),
bundle_filter: None,
},
)
.map(|obj| obj.into_any());
} else if first_type_obj.is(&PyOnInsert::type_object(py)) {
let comp_type = if let Ok(comp_type_obj) = second_key.cast_exact::<PyType>() {
PyComponentType::try_from((comp_type_obj, py))?
} else {
return Err(PyTypeError::new_err(
"Second parameter to On[OnInsert, ...] must be a Component type",
));
};
return Py::new(
py,
PyOnTypeParam {
event_type: EventType::Insert(comp_type),
bundle_filter: None,
},
)
.map(|obj| obj.into_any());
} else if first_type_obj.is(&PyOnRemove::type_object(py)) {
let comp_type = if let Ok(comp_type_obj) = second_key.cast_exact::<PyType>() {
PyComponentType::try_from((comp_type_obj, py))?
} else {
return Err(PyTypeError::new_err(
"Second parameter to On[OnRemove, ...] must be a Component type",
));
};
return Py::new(
py,
PyOnTypeParam {
event_type: EventType::Remove(comp_type),
bundle_filter: None,
},
)
.map(|obj| obj.into_any());
} else if first_type_obj.is(&PyOnReplace::type_object(py)) {
let comp_type = if let Ok(comp_type_obj) = second_key.cast_exact::<PyType>() {
PyComponentType::try_from((comp_type_obj, py))?
} else {
return Err(PyTypeError::new_err(
"Second parameter to On[OnReplace, ...] must be a Component type",
));
};
return Py::new(
py,
PyOnTypeParam {
event_type: EventType::Replace(comp_type),
bundle_filter: None,
},
)
.map(|obj| obj.into_any());
} else if first_type_obj.is(&PyOnDespawn::type_object(py)) {
let comp_type = if let Ok(comp_type_obj) = second_key.cast_exact::<PyType>() {
PyComponentType::try_from((comp_type_obj, py))?
} else {
return Err(PyTypeError::new_err(
"Second parameter to On[OnDespawn, ...] must be a Component type",
));
};
return Py::new(
py,
PyOnTypeParam {
event_type: EventType::Despawn(comp_type),
bundle_filter: None,
},
)
.map(|obj| obj.into_any());
}
let event_type = EventType::from_py_type(py, first_type_obj)?;
let bundle_filter = if let Ok(bundle_type_obj) = second_key.cast_exact::<PyType>() {
let comp_type = PyComponentType::try_from((bundle_type_obj, py))?;
Some(vec![comp_type])
} else if let Ok(origin) = second_key.getattr("__origin__") {
if origin.is_instance_of::<pyo3::types::PyType>()
&& origin
.cast::<pyo3::types::PyType>()
.map(|t| t.name().map(|n| n == "tuple").unwrap_or(false))
.unwrap_or(false)
{
let args = second_key.getattr("__args__")?;
let mut components = Vec::new();
for item in args.try_iter()? {
let item = item?;
let comp_type_obj = item.cast_exact::<PyType>().map_err(|_| {
PyTypeError::new_err(format!(
"All items in bundle filter tuple must be Component types, got {:?}",
item
))
})?;
let comp_type = PyComponentType::try_from((comp_type_obj, py))?;
components.push(comp_type);
}
if components.is_empty() {
return Err(PyTypeError::new_err("Bundle filter tuple cannot be empty"));
}
Some(components)
} else {
return Err(PyTypeError::new_err(format!(
"Second parameter to On must be a Component type or tuple[...], got {:?}",
second_key
)));
}
} else if let Ok(iter) = second_key.try_iter() {
let mut components = Vec::new();
for item in iter {
let item = item?;
let comp_type_obj = item.cast_exact::<PyType>().map_err(|_| {
PyTypeError::new_err(format!(
"All items in bundle filter tuple must be Component types, got {:?}",
item
))
})?;
let comp_type = PyComponentType::try_from((comp_type_obj, py))?;
components.push(comp_type);
}
if components.is_empty() {
return Err(PyTypeError::new_err("Bundle filter tuple cannot be empty"));
}
Some(components)
} else {
return Err(PyTypeError::new_err(format!(
"Second parameter to On must be a Component type or tuple of Component types, got {:?}",
second_key
)));
};
Py::new(
py,
PyOnTypeParam {
event_type,
bundle_filter,
},
)
.map(|obj| obj.into_any())
} else {
let event_type = if let Ok(event_type_obj) = key.cast_exact::<PyType>() {
EventType::from_py_type(py, event_type_obj)?
} else {
return Err(PyTypeError::new_err(format!(
"Parameter to On must be an Event type, got {:?}",
key
)));
};
Py::new(
py,
PyOnTypeParam {
event_type,
bundle_filter: None,
},
)
.map(|obj| obj.into_any())
}
}
pub fn event(&self, py: Python) -> Py<PyAny> {
self.event_data.clone_ref(py)
}
pub fn entity(&self) -> Option<PyEntity> {
self.entity.map(PyEntity)
}
}
#[derive(Debug)]
pub enum EventType {
Add(PyComponentType),
Insert(PyComponentType),
Remove(PyComponentType),
Replace(PyComponentType),
Despawn(PyComponentType),
Custom(Py<PyType>),
}
impl Clone for EventType {
fn clone(&self) -> Self {
match self {
EventType::Add(c) => EventType::Add(c.clone()),
EventType::Insert(c) => EventType::Insert(c.clone()),
EventType::Remove(c) => EventType::Remove(c.clone()),
EventType::Replace(c) => EventType::Replace(c.clone()),
EventType::Despawn(c) => EventType::Despawn(c.clone()),
EventType::Custom(ty) => Python::attach(|py| EventType::Custom(ty.clone_ref(py))),
}
}
}
impl PartialEq for EventType {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(EventType::Add(a), EventType::Add(b)) => a == b,
(EventType::Insert(a), EventType::Insert(b)) => a == b,
(EventType::Remove(a), EventType::Remove(b)) => a == b,
(EventType::Replace(a), EventType::Replace(b)) => a == b,
(EventType::Despawn(a), EventType::Despawn(b)) => a == b,
(EventType::Custom(a), EventType::Custom(b)) => a.is(b),
_ => false,
}
}
}
impl EventType {
pub(crate) fn from_py_type(_py: Python, ty: &Bound<'_, PyType>) -> PyResult<Self> {
if ty.is_subclass_of::<PyEvent>()? {
Ok(EventType::Custom(ty.clone().unbind()))
} else {
Err(PyTypeError::new_err(format!(
"Expected Event subclass, got {}",
ty.name()?
)))
}
}
#[allow(dead_code)] pub(crate) fn matches(&self, py: Python, event: &Bound<'_, PyAny>) -> bool {
match self {
EventType::Custom(expected_type) => {
let event_type = event.get_type();
event_type.is(&expected_type.bind(py))
}
_ => false,
}
}
}
#[derive(Debug, Clone)]
pub struct BundleFilter {
pub(crate) components: Vec<PyComponentType>,
}
impl BundleFilter {
pub(crate) fn matches(
&self,
world: &bevy::ecs::world::World,
entity: bevy::ecs::entity::Entity,
) -> bool {
let entity_ref = match world.get_entity(entity) {
Ok(e) => e,
Err(_) => return false, };
self.components
.iter()
.any(|comp_type| entity_has_component(world, &entity_ref, comp_type))
}
}
fn entity_has_component(
world: &bevy::ecs::world::World,
entity: &bevy::ecs::world::EntityRef,
comp_type: &PyComponentType,
) -> bool {
match comp_type {
PyComponentType::Custom(type_ptr) => {
if let Some(registry) = world.get_resource::<ComponentRegistry>() {
if let Some(component_id) = registry.get(*type_ptr) {
return entity.contains_id(component_id);
}
}
false
}
_ => comp_type.entity_contains(entity),
}
}
pub(crate) fn entity_has_component_type(
world: &bevy::ecs::world::World,
entity: bevy::ecs::entity::Entity,
comp_type: &PyComponentType,
) -> bool {
match world.get_entity(entity) {
Ok(entity_ref) => entity_has_component(world, &entity_ref, comp_type),
Err(_) => false,
}
}
#[pyclass(name = "OnTypeParam", frozen)]
#[derive(Debug, Clone)]
pub struct PyOnTypeParam {
pub(crate) event_type: EventType,
pub(crate) bundle_filter: Option<Vec<PyComponentType>>,
}