use std::{collections::HashMap, sync::Arc};
use bevy::ecs::component::ComponentId;
use pyo3::{exceptions::PyRuntimeError, ffi::PyTypeObject, prelude::*};
use crate::ecs::{
helpers::validity_guard::ValidityFlag,
query::{query_param::PyQueryParam, query_runtime::PyQueryIter},
};
#[pyclass(name = "SingleQuery")]
pub struct PySingleQuery {
query_iter: Py<PyQueryIter>,
returned: bool,
cached_item: Option<Py<PyAny>>,
}
impl PySingleQuery {
pub unsafe fn new(
param: Arc<PyQueryParam>,
world: &mut bevy::prelude::World,
custom_component_ids: Arc<HashMap<*const PyTypeObject, ComponentId>>,
validity: ValidityFlag,
) -> Self {
let query_iter = unsafe { PyQueryIter::new(param, world, custom_component_ids, validity) };
Python::attach(|py| {
let query_iter_py = Py::new(py, query_iter).expect("Failed to create PyQueryIter");
PySingleQuery {
query_iter: query_iter_py,
returned: false,
cached_item: None,
}
})
}
}
#[pymethods]
impl PySingleQuery {
fn __iter__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> {
slf
}
fn __next__(&mut self, py: Python) -> PyResult<Py<PyAny>> {
if self.returned {
return Err(pyo3::exceptions::PyStopIteration::new_err(""));
}
let first = self.query_iter.call_method0(py, "__next__")?;
match self.query_iter.call_method0(py, "__next__") {
Ok(_) => {
Err(PyRuntimeError::new_err(
"Single<T> query matched multiple entities. Expected exactly one entity.",
))
}
Err(e) if e.is_instance_of::<pyo3::exceptions::PyStopIteration>(py) => {
self.returned = true;
Ok(first)
}
Err(e) => {
Err(e)
}
}
}
fn __getattr__(&mut self, py: Python, name: &str) -> PyResult<Py<PyAny>> {
let item = self.get_or_fetch_item(py)?;
item.getattr(py, name)
}
fn __setattr__(&mut self, py: Python, name: &str, value: Py<PyAny>) -> PyResult<()> {
let item = self.get_or_fetch_item(py)?;
item.setattr(py, name, value)
}
}
impl PySingleQuery {
fn get_or_fetch_item(&mut self, py: Python) -> PyResult<Py<PyAny>> {
if let Some(ref item) = self.cached_item {
return Ok(item.clone_ref(py));
}
let first = self.query_iter.call_method0(py, "__next__")?;
match self.query_iter.call_method0(py, "__next__") {
Ok(_) => {
return Err(PyRuntimeError::new_err(
"Single<T> query matched multiple entities. Expected exactly one entity.",
));
}
Err(e) if e.is_instance_of::<pyo3::exceptions::PyStopIteration>(py) => {
}
Err(e) => return Err(e),
}
self.returned = true;
self.cached_item = Some(first.clone_ref(py));
Ok(first)
}
}