use std::ffi::c_void;
use bevy::math::{Vec2, Vec3};
use pybevy_bytecodevm::bytecode::FieldType;
use pybevy_math::{vec2::PyVec2, vec3::PyVec3};
use pyo3::{
exceptions::PyTypeError,
prelude::*,
types::{PyDict, PyType},
};
use super::component_wrapper::WrapperSize;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum PrimitiveType {
F32,
F64,
I32,
I64,
U32,
U64,
Bool,
Vec3,
Vec2,
}
impl PrimitiveType {
pub const fn size_bytes(&self) -> usize {
match self {
PrimitiveType::F32 => 4,
PrimitiveType::F64 => 8,
PrimitiveType::I32 => 4,
PrimitiveType::I64 => 8,
PrimitiveType::U32 => 4,
PrimitiveType::U64 => 8,
PrimitiveType::Bool => 1,
PrimitiveType::Vec3 => 12, PrimitiveType::Vec2 => 8, }
}
pub const fn alignment(&self) -> usize {
match self {
PrimitiveType::F32 => 4,
PrimitiveType::F64 => 8,
PrimitiveType::I32 => 4,
PrimitiveType::I64 => 8,
PrimitiveType::U32 => 4,
PrimitiveType::U64 => 8,
PrimitiveType::Bool => 1,
PrimitiveType::Vec3 => 4, PrimitiveType::Vec2 => 4, }
}
pub fn from_python_type(ty: &Bound<'_, PyAny>) -> PyResult<Option<Self>> {
let ty_str_bound = ty.str()?;
let ty_str = ty_str_bound.to_str()?;
let ty_str = if ty_str.starts_with("<class '") && ty_str.ends_with("'>") {
&ty_str[8..ty_str.len() - 2]
} else {
ty_str
};
match ty_str {
"float" => Ok(Some(PrimitiveType::F64)), "int" => Ok(Some(PrimitiveType::I64)), "bool" => Ok(Some(PrimitiveType::Bool)),
"Vec3" | "builtins.Vec3" => Ok(Some(PrimitiveType::Vec3)),
"Vec2" | "builtins.Vec2" => Ok(Some(PrimitiveType::Vec2)),
_ => {
if ty_str.contains("typing.") || ty_str.contains("list") || ty_str.contains("dict")
{
Ok(None)
} else {
Ok(None)
}
}
}
}
pub fn to_numpy_dtype(&self) -> &'static str {
match self {
PrimitiveType::F32 => "f4",
PrimitiveType::F64 => "f8",
PrimitiveType::I32 => "i4",
PrimitiveType::I64 => "i8",
PrimitiveType::U32 => "u4",
PrimitiveType::U64 => "u8",
PrimitiveType::Bool => "u1",
PrimitiveType::Vec3 => "f4", PrimitiveType::Vec2 => "f4",
}
}
pub const fn is_composite(&self) -> bool {
matches!(self, PrimitiveType::Vec3 | PrimitiveType::Vec2)
}
pub const fn element_count(&self) -> usize {
match self {
PrimitiveType::Vec3 => 3,
PrimitiveType::Vec2 => 2,
_ => 1,
}
}
pub fn to_field_type(&self) -> FieldType {
match self {
PrimitiveType::F32 => FieldType::F32,
PrimitiveType::F64 => FieldType::F64,
PrimitiveType::I32 => FieldType::I32,
PrimitiveType::I64 => FieldType::I64,
PrimitiveType::U32 => FieldType::U32,
PrimitiveType::U64 => FieldType::U64,
PrimitiveType::Bool => FieldType::Bool,
PrimitiveType::Vec3 => FieldType::Vec3,
PrimitiveType::Vec2 => FieldType::Vec2,
}
}
}
#[derive(Debug, Clone)]
pub struct FieldInfo {
pub name: String,
pub offset: usize,
pub field_type: PrimitiveType,
}
#[derive(Debug, Clone)]
pub struct ComponentLayout {
pub py_type_ptr: *const c_void,
pub name: String,
pub fields: Vec<FieldInfo>,
pub data_size: usize,
pub wrapper_size: WrapperSize,
}
unsafe impl Send for ComponentLayout {}
unsafe impl Sync for ComponentLayout {}
impl ComponentLayout {
pub fn from_annotations(cls: &Bound<'_, PyType>) -> PyResult<Self> {
let name = cls.name()?.to_string();
let annotations_bound = cls.getattr("__annotations__").map_err(|_| {
PyTypeError::new_err(format!("Component class '{}' has no __annotations__", name))
})?;
let annotations = annotations_bound.cast::<PyDict>()?;
if annotations.is_empty() {
return Err(PyTypeError::new_err(format!(
"Component class '{}' has no fields",
name
)));
}
let mut fields = Vec::new();
let mut current_offset = 0usize;
for (field_name, field_type) in annotations.iter() {
let field_name_bound = field_name.str()?;
let field_name_str = field_name_bound.to_str()?.to_string();
let primitive_type = PrimitiveType::from_python_type(&field_type)?;
match primitive_type {
Some(prim_type) => {
let alignment = prim_type.alignment();
current_offset = (current_offset + alignment - 1) / alignment * alignment;
fields.push(FieldInfo {
name: field_name_str.clone(),
offset: current_offset,
field_type: prim_type,
});
current_offset += prim_type.size_bytes();
}
None => {
let type_str_bound = field_type.str()?;
let type_str = type_str_bound.to_str()?;
return Err(PyTypeError::new_err(format!(
"Component '{}' field '{}' has non-primitive type '{}'. \
Only float, int, and bool are supported for wrapper storage.",
name, field_name_str, type_str
)));
}
}
}
let data_size = current_offset;
let wrapper_size = WrapperSize::for_size(data_size).ok_or_else(|| {
PyTypeError::new_err(format!(
"Component '{}' is too large ({} bytes). \
Maximum size for wrapper storage is 1024 bytes.",
name, data_size
))
})?;
Ok(ComponentLayout {
py_type_ptr: cls.as_ptr() as *const c_void,
name,
fields,
data_size,
wrapper_size,
})
}
pub fn get_field(&self, name: &str) -> Option<&FieldInfo> {
self.fields.iter().find(|f| f.name == name)
}
pub fn field_names(&self) -> Vec<&str> {
self.fields.iter().map(|f| f.name.as_str()).collect()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ComponentStorageType {
Wrapper(WrapperSize),
PyObject,
}
impl ComponentStorageType {
pub fn from_python_class(cls: &Bound<'_, PyType>) -> PyResult<Self> {
if let Ok(storage_attr) = cls.getattr("__pybevy_storage__") {
let storage_str_bound = storage_attr.str()?;
let storage_str = storage_str_bound.to_str()?;
match storage_str {
"pyobject" => return Ok(ComponentStorageType::PyObject),
"wrapper" => {
}
_ => {
return Err(PyTypeError::new_err(format!(
"Invalid storage type '{}'. Use 'wrapper' or 'pyobject'.",
storage_str
)));
}
}
}
match ComponentLayout::from_annotations(cls) {
Ok(layout) => Ok(ComponentStorageType::Wrapper(layout.wrapper_size)),
Err(_) => {
Ok(ComponentStorageType::PyObject)
}
}
}
}
pub fn serialize_to_wrapper(obj: &Bound<'_, PyAny>, layout: &ComponentLayout) -> PyResult<Vec<u8>> {
let mut buffer = vec![0u8; layout.wrapper_size.size_bytes()];
for field in &layout.fields {
let value = obj.getattr(field.name.as_str())?;
match field.field_type {
PrimitiveType::F32 => {
let val: f32 = value.extract()?;
let bytes = val.to_le_bytes();
buffer[field.offset..field.offset + 4].copy_from_slice(&bytes);
}
PrimitiveType::F64 => {
let val: f64 = value.extract()?;
let bytes = val.to_le_bytes();
buffer[field.offset..field.offset + 8].copy_from_slice(&bytes);
}
PrimitiveType::I32 => {
let val: i32 = value.extract()?;
let bytes = val.to_le_bytes();
buffer[field.offset..field.offset + 4].copy_from_slice(&bytes);
}
PrimitiveType::I64 => {
let val: i64 = value.extract()?;
let bytes = val.to_le_bytes();
buffer[field.offset..field.offset + 8].copy_from_slice(&bytes);
}
PrimitiveType::U32 => {
let val: u32 = value.extract()?;
let bytes = val.to_le_bytes();
buffer[field.offset..field.offset + 4].copy_from_slice(&bytes);
}
PrimitiveType::U64 => {
let val: u64 = value.extract()?;
let bytes = val.to_le_bytes();
buffer[field.offset..field.offset + 8].copy_from_slice(&bytes);
}
PrimitiveType::Bool => {
let val: bool = value.extract()?;
buffer[field.offset] = if val { 1 } else { 0 };
}
PrimitiveType::Vec3 => {
let py_vec3: PyRef<PyVec3> = value.extract()?;
let v: Vec3 = (&*py_vec3).into();
buffer[field.offset..field.offset + 4].copy_from_slice(&v.x.to_le_bytes());
buffer[field.offset + 4..field.offset + 8].copy_from_slice(&v.y.to_le_bytes());
buffer[field.offset + 8..field.offset + 12].copy_from_slice(&v.z.to_le_bytes());
}
PrimitiveType::Vec2 => {
let py_vec2: PyRef<PyVec2> = value.extract()?;
let v: Vec2 = (&*py_vec2).into();
buffer[field.offset..field.offset + 4].copy_from_slice(&v.x.to_le_bytes());
buffer[field.offset + 4..field.offset + 8].copy_from_slice(&v.y.to_le_bytes());
}
}
}
Ok(buffer)
}