use std::{collections::VecDeque, sync::Arc};
use bevy::prelude::World;
use pybevy_core::{
PyMaterializable,
handle::PyHandle,
registry::{AssetBridge, global_registry},
};
use pybevy_mesh::{PyMeshBuilder, PyMeshable, meshable_to_mesh};
use pyo3::{
IntoPyObjectExt,
exceptions::{PyRuntimeError, PyStopIteration, PyTypeError},
ffi::PyTypeObject,
prelude::*,
types::{PyTuple, PyType},
};
use super::asset_type::PyAssetTypeParam;
use crate::ecs::{
helpers::validity_guard::{AccessMode, ValidityFlag, ValidityFlagWithMode},
resource::PyResource,
};
#[pyclass(name = "Assets", extends = PyResource)]
#[derive(Debug)]
pub struct PyAssets {
type_ptr: *const PyTypeObject,
wrapper_class: Option<*const PyTypeObject>,
world: *mut World,
validity: ValidityFlagWithMode,
}
unsafe impl Send for PyAssets {}
unsafe impl Sync for PyAssets {}
impl PyAssets {
pub(crate) unsafe fn new(
type_ptr: *const PyTypeObject,
wrapper_class: Option<*const PyTypeObject>,
world: *mut World,
validity: ValidityFlag,
is_mutable: bool,
) -> Self {
let access_mode = if is_mutable {
AccessMode::Write
} else {
AccessMode::Read
};
Self {
type_ptr,
wrapper_class,
world,
validity: validity.with_access_mode(access_mode),
}
}
fn bridge(&self) -> PyResult<Arc<dyn AssetBridge>> {
global_registry::get_asset_bridge_by_py_type(self.type_ptr)
.ok_or_else(|| PyRuntimeError::new_err("Asset bridge not found for type"))
}
fn check_handle_type(&self, handle: &PyHandle) -> PyResult<()> {
if handle.type_ptr() != self.type_ptr {
let handle_name = handle.asset_type_name().unwrap_or("Unknown");
let self_name = self.bridge().map(|b| b.name()).unwrap_or("Unknown");
return Err(pyo3::exceptions::PyValueError::new_err(format!(
"Handle of type `{}` does not match expected type `{}`",
handle_name, self_name
)));
}
Ok(())
}
fn world_ref(&self) -> PyResult<&World> {
self.validity.check_read()?;
Ok(unsafe { &*self.world })
}
fn world_mut(&mut self) -> PyResult<&mut World> {
self.validity.check_read()?;
if self.validity.access_mode() != AccessMode::Write {
return Err(PyRuntimeError::new_err(
"Mutable access required. Use ResMut[Assets[T]] instead of Res[Assets[T]] for mutations.",
));
}
Ok(unsafe { &mut *self.world })
}
}
#[pymethods]
impl PyAssets {
#[classmethod]
#[pyo3(signature = (key, /))]
pub fn __class_getitem__(
cls: &Bound<'_, PyType>,
key: &Bound<'_, PyAny>,
) -> PyResult<Py<PyAny>> {
if key.hasattr("__pybevy_asset_type__").unwrap_or(false) {
let actual_type_obj = key.getattr("__pybevy_asset_type__")?;
let actual_type = actual_type_obj.cast::<PyType>()?;
let key_type = key.cast::<PyType>()?;
let param = PyAssetTypeParam::with_redirect(actual_type, key_type)?;
return param.into_py_any(cls.py());
}
let key_type = key.cast::<PyType>()?;
let param = PyAssetTypeParam::try_from_py_type(key_type)?;
param.into_py_any(cls.py())
}
pub fn add(&mut self, asset: Bound<'_, PyAny>) -> PyResult<PyHandle> {
let py = asset.py();
if asset.is_instance_of::<PyMeshBuilder>() {
let bridge = self.bridge()?;
if bridge.name() != "Mesh" {
return Err(PyTypeError::new_err(format!(
"Asset type mismatch: expected `{}` but got `Mesh`",
bridge.name()
)));
}
let py_mesh = asset.call_method0("build")?;
let world = self.world_mut()?;
let untyped_handle = bridge.add(world, &py_mesh, py)?;
return Ok(PyHandle::from_untyped(untyped_handle, self.type_ptr));
} else if asset.is_instance_of::<PyMeshable>() {
let bridge = self.bridge()?;
if bridge.name() != "Mesh" {
return Err(PyTypeError::new_err(format!(
"Asset type mismatch: expected `{}` but got `Mesh`",
bridge.name()
)));
}
if let Ok(py_mesh) = asset.call_method0("build") {
let world = self.world_mut()?;
let untyped_handle = bridge.add(world, &py_mesh, py)?;
return Ok(PyHandle::from_untyped(untyped_handle, self.type_ptr));
}
let mesh = meshable_to_mesh(&asset)?;
let world = self.world_mut()?;
let mut assets = world
.get_resource_mut::<bevy::asset::Assets<bevy::mesh::Mesh>>()
.ok_or_else(|| PyRuntimeError::new_err("Assets<Mesh> resource not found"))?;
let handle = assets.add(mesh);
return Ok(PyHandle::from_untyped(handle.untyped(), self.type_ptr));
} else if asset.is_instance_of::<PyMaterializable>() {
let bridge = self.bridge()?;
if bridge.name() != "StandardMaterial" {
return Err(PyTypeError::new_err(format!(
"Asset type mismatch: expected `{}` but got `StandardMaterial`",
bridge.name()
)));
}
let material = asset.call_method0("materialize")?;
let world = self.world_mut()?;
let untyped_handle = bridge.add(world, &material, py)?;
return Ok(PyHandle::from_untyped(untyped_handle, self.type_ptr));
}
let asset_type_ptr = asset.get_type().as_type_ptr() as *const PyTypeObject;
if asset_type_ptr != self.type_ptr {
if let Ok(converter) = asset.getattr("to_shader_material") {
let converted = converter.call0()?;
let converted_type_ptr = converted.get_type().as_type_ptr() as *const PyTypeObject;
if converted_type_ptr == self.type_ptr {
let bridge = self.bridge()?;
let world = self.world_mut()?;
let untyped_handle = bridge.add(world, &converted, py)?;
return Ok(PyHandle::from_untyped(untyped_handle, self.type_ptr));
}
}
let bridge = self.bridge()?;
let asset_bridge = global_registry::get_asset_bridge_by_py_type(asset_type_ptr);
let asset_name = asset_bridge.as_ref().map(|b| b.name()).unwrap_or("Unknown");
return Err(PyTypeError::new_err(format!(
"Asset type mismatch: expected `{}` but got `{}`",
bridge.name(),
asset_name
)));
}
let bridge = self.bridge()?;
let world = self.world_mut()?;
let untyped_handle = bridge.add(world, &asset, py)?;
Ok(PyHandle::from_untyped(untyped_handle, self.type_ptr))
}
pub fn len(&self) -> PyResult<usize> {
let world = self.world_ref()?;
let bridge = self.bridge()?;
bridge.len(world)
}
pub fn is_empty(&self) -> PyResult<bool> {
Ok(self.len()? == 0)
}
pub fn contains(&self, id: Bound<'_, PyHandle>) -> PyResult<bool> {
let handle = id.extract::<PyHandle>()?;
self.check_handle_type(&handle)?;
let bridge = self.bridge()?;
let world = self.world_ref()?;
bridge.contains(world, &handle.to_untyped_handle()?)
}
pub fn remove(&mut self, py: Python, id: Bound<'_, PyHandle>) -> PyResult<Option<Py<PyAny>>> {
let handle = id.extract::<PyHandle>()?;
self.check_handle_type(&handle)?;
let bridge = self.bridge()?;
let world = self.world_mut()?;
bridge.remove_and_return(world, &handle.to_untyped_handle()?, py)
}
pub fn get(&self, py: Python, id: Bound<'_, PyHandle>) -> PyResult<Option<Py<PyAny>>> {
let handle = id.extract::<PyHandle>()?;
self.check_handle_type(&handle)?;
let bridge = self.bridge()?;
let world = self.world_ref()?;
bridge.get(
world,
&handle.to_untyped_handle()?,
self.validity.clone(),
py,
)
}
pub fn get_mut(&mut self, py: Python, id: Bound<'_, PyHandle>) -> PyResult<Option<Py<PyAny>>> {
let handle = id.extract::<PyHandle>()?;
self.check_handle_type(&handle)?;
let bridge = self.bridge()?;
let validity = self.validity.clone();
let untyped_handle = handle.to_untyped_handle()?;
let world = self.world_mut()?;
let raw = bridge.get_mut(world, &untyped_handle, validity, py)?;
if let (Some(raw_obj), Some(wrapper_ptr)) = (&raw, self.wrapper_class) {
let wrapper_cls: pyo3::Bound<'_, PyAny> = unsafe {
pyo3::Bound::from_borrowed_ptr(py, wrapper_ptr as *mut pyo3::ffi::PyObject)
};
let wrapped = wrapper_cls.call_method1("from_mut", (raw_obj,))?;
return Ok(Some(wrapped.unbind()));
}
Ok(raw)
}
pub fn __iter__(&self, py: Python) -> PyResult<PyAssetIter> {
let bridge = self.bridge()?;
let world = self.world_ref()?;
let pairs = bridge.iter_pairs(world, self.validity.clone(), py)?;
let type_ptr = self.type_ptr;
let values = pairs
.into_iter()
.map(|(untyped_handle, obj)| {
let py_handle = PyHandle::from_untyped(untyped_handle, type_ptr);
(py_handle, obj)
})
.collect();
Ok(PyAssetIter { values })
}
}
#[pyclass(name = "AssetIter")]
#[derive(Debug)]
pub struct PyAssetIter {
values: VecDeque<(PyHandle, Py<PyAny>)>,
}
#[pymethods]
impl PyAssetIter {
fn __next__<'a>(&'a mut self, py: Python<'a>) -> PyResult<Py<PyAny>> {
if let Some((handle, value)) = self.values.pop_front() {
PyTuple::new(py, [Py::new(py, handle)?.into_any(), value])?.into_py_any(py)
} else {
Err(PyErr::new::<PyStopIteration, _>(""))
}
}
}