use std::time::{Duration, Instant};
use pyo3::prelude::*;
use pyo3::{PyTraverseError, PyVisit};
use crate::python::collator::PyCollator;
use crate::python::dataloader::PyDataloader;
use crate::python::dataset::{Fetcher, PyDataset};
use crate::python::into_py_err;
type CorePyDataloaderIter = crate::loader::DataLoaderIter<PyDataset, PyCollator>;
const SIGNAL_CHECK_INTERVAL: Duration = Duration::from_millis(100);
pub(crate) enum PyIterInner {
Direct {
chunks: std::vec::IntoIter<Vec<usize>>,
fetcher: Fetcher,
collator: PyCollator,
},
Threaded(CorePyDataloaderIter),
}
#[pyclass(name = "PyDataloaderIter", module = "dataloader_rs")]
pub struct PyDataloaderIter {
pub(crate) _owner: Py<PyDataloader>,
pub(crate) inner: PyIterInner,
}
#[pymethods]
impl PyDataloaderIter {
fn __iter__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> {
slf
}
fn __next__(&mut self, py: Python<'_>) -> PyResult<Option<Py<PyAny>>> {
let batch = match &mut self.inner {
PyIterInner::Direct {
chunks,
fetcher,
collator,
} => {
let Some(chunk) = chunks.next() else {
return Ok(None);
};
let items = fetcher.fetch(py, &chunk)?;
collator.collate_with_py(py, items).map_err(into_py_err)?
}
PyIterInner::Threaded(inner) => {
let next = match inner.next_until(None) {
Some(next) => next,
None => loop {
let deadline = Instant::now() + SIGNAL_CHECK_INTERVAL;
if let Some(next) = py.detach(|| inner.next_until(Some(deadline))) {
break next;
}
py.check_signals()?;
},
};
let Some(batch) = next else {
return Ok(None);
};
batch.map_err(into_py_err)?
}
};
batch.into_py(py).map(Some)
}
fn __traverse__(&self, visit: PyVisit<'_>) -> Result<(), PyTraverseError> {
visit.call(&self._owner)?;
if let PyIterInner::Direct {
fetcher, collator, ..
} = &self.inner
{
visit.call(fetcher.method())?;
visit.call(collator.collate_fn())?;
}
Ok(())
}
fn __len__(&self) -> usize {
match &self.inner {
PyIterInner::Direct { chunks, .. } => chunks.len(),
PyIterInner::Threaded(inner) => inner.len(),
}
}
}