Skip to main content

pybevy_core/
lib.rs

1//! Core storage primitives and runtime registries for PyBevy
2//!
3//! This crate provides the foundational types for PyBevy's owned/borrowed pattern
4//! and the runtime type registry system that enables crate splitting.
5//!
6//! ## Storage Primitives
7//!
8//! - `ValidityFlag` / `ValidityFlagWithMode` - Runtime validity tracking
9//! - `ValidityGuard` - RAII guard for system execution scope
10//! - `ValueStorage<T>` - Generic storage for Copy types (Vec3, Quat, etc.)
11//! - `FieldStorage<T>` - Generic storage for non-Copy types (TextureAtlas, etc.)
12//! - `BorrowableStorage` / `FromBorrowedStorage` - Traits for borrowed field access
13//!
14//! ## Runtime Registries
15//!
16//! - `ComponentBridge` - Trait for component type bridges
17//! - `AssetBridge` - Trait for asset type bridges
18//! - `PluginBridge` - Trait for plugin type bridges
19//! - `DynamicComponentRegistry` - Runtime registry for component bridges
20//! - `DynamicAssetRegistry` - Runtime registry for asset bridges
21//!
22//! The registry system allows feature crates (pybevy_audio, pybevy_light, etc.)
23//! to register their types without the core crate needing to import them at
24//! compile time, enabling independent compilation and faster incremental builds.
25
26pub mod asset;
27pub mod asset_path;
28pub mod component;
29pub mod debug_snapshot;
30pub mod entity;
31pub mod handle;
32pub mod hierarchy;
33pub mod materializable;
34pub mod message;
35pub mod plugin;
36pub mod registry;
37pub mod reload_request;
38pub mod resource;
39
40// Storage layer — re-exported from pybevy_storage
41pub use pybevy_storage::{
42    component_storage, field_storage, list_storage, resource_storage, storage, storage_error,
43    storage_traits, validity_guard, value_storage, view_bridge,
44};
45
46// PyF32List (was in list_storage.rs, needs pyo3)
47pybevy_storage::impl_py_list!(PyF32List, "F32List", f32);
48
49use bevy::ecs::{
50    component::ComponentId,
51    entity::Entity,
52    hierarchy::{ChildOf, Children},
53    world::{FilteredEntityMut, World},
54};
55use pyo3::prelude::*;
56
57// Manual implementation of ChildOfBridge because component_bridge! macro
58// uses pybevy_core:: paths which don't work inside pybevy_core itself.
59pub struct ChildOfBridge;
60
61impl ComponentBridge for ChildOfBridge {
62    fn bevy_type_id(&self) -> std::any::TypeId {
63        std::any::TypeId::of::<ChildOf>()
64    }
65
66    fn py_type_ptr(&self) -> *const pyo3::ffi::PyTypeObject {
67        Python::attach(|py| {
68            <hierarchy::PyChildOf as pyo3::PyTypeInfo>::type_object(py).as_type_ptr()
69        })
70    }
71
72    fn py_type<'py>(&self, py: Python<'py>) -> pyo3::Bound<'py, pyo3::types::PyType> {
73        <hierarchy::PyChildOf as pyo3::PyTypeInfo>::type_object(py)
74    }
75
76    fn name(&self) -> &'static str {
77        "ChildOf"
78    }
79
80    fn register(&self, world: &mut World) -> ComponentId {
81        world.register_component::<ChildOf>()
82    }
83
84    #[inline(always)]
85    fn extract(
86        &self,
87        entity: &mut FilteredEntityMut,
88        component_id: ComponentId,
89        _validity: ValidityFlagWithMode,
90        py: Python,
91    ) -> PyResult<Py<PyAny>> {
92        // ChildOf is an immutable component (relation), so we use get_by_id for read-only access
93        // and return an owned copy rather than a borrowed reference.
94        let ptr = entity
95            .get_by_id(component_id)
96            .ok_or_else(|| pyo3::exceptions::PyRuntimeError::new_err("ChildOf not found"))?;
97
98        let component = unsafe { ptr.deref::<ChildOf>() };
99        let py_component = hierarchy::PyChildOf::try_from(component)?;
100        let obj = Py::new(py, (py_component, PyComponent))?;
101        Ok(obj.into_any())
102    }
103
104    fn insert(
105        &self,
106        world: &mut World,
107        entity: Entity,
108        component: &pyo3::Bound<PyAny>,
109    ) -> PyResult<()> {
110        let py_component = component.extract::<pyo3::PyRef<hierarchy::PyChildOf>>()?;
111        let native: ChildOf = py_component.storage.as_ref()?.clone();
112
113        world.entity_mut(entity).insert(native);
114        Ok(())
115    }
116
117    fn insert_into_entity(
118        &self,
119        entity: &mut bevy::ecs::world::EntityWorldMut,
120        component: &pyo3::Bound<PyAny>,
121    ) -> PyResult<()> {
122        let py_component = component.extract::<pyo3::PyRef<hierarchy::PyChildOf>>()?;
123        let native: ChildOf = py_component.storage.as_ref()?.clone();
124
125        entity.insert(native);
126        Ok(())
127    }
128
129    fn extract_fn(&self) -> ExtractFn {
130        #[inline(always)]
131        fn extract_impl(
132            entity: &mut FilteredEntityMut,
133            component_id: ComponentId,
134            _validity: ValidityFlagWithMode,
135            py: Python,
136        ) -> PyResult<Py<PyAny>> {
137            // ChildOf is an immutable component (relation), so we use get_by_id for read-only access
138            // and return an owned copy rather than a borrowed reference.
139            let ptr = entity
140                .get_by_id(component_id)
141                .ok_or_else(|| pyo3::exceptions::PyRuntimeError::new_err("ChildOf not found"))?;
142
143            let component = unsafe { ptr.deref::<bevy::ecs::hierarchy::ChildOf>() };
144            let py_component = crate::hierarchy::PyChildOf::try_from(component)?;
145            let obj = Py::new(py, (py_component, crate::component::PyComponent))?;
146            Ok(obj.into_any())
147        }
148        extract_impl
149    }
150
151    fn entity_contains(&self, entity: &bevy::ecs::world::EntityRef) -> bool {
152        entity.contains::<ChildOf>()
153    }
154
155    fn extract_from_entity_ref(
156        &self,
157        entity: &bevy::ecs::world::EntityRef,
158        validity: ValidityFlagWithMode,
159        py: Python,
160    ) -> PyResult<Option<Py<PyAny>>> {
161        if let Some(component) = entity.get::<ChildOf>() {
162            let ptr = component as *const ChildOf as *mut ChildOf;
163            let storage = unsafe { ComponentStorage::borrowed(ptr, validity) };
164            let obj = Py::new(py, hierarchy::PyChildOf::from_borrowed(storage))?;
165            Ok(Some(obj.into_any()))
166        } else {
167            Ok(None)
168        }
169    }
170
171    fn extract_from_entity_mut(
172        &self,
173        entity: &mut bevy::ecs::world::EntityWorldMut,
174        _validity: ValidityFlagWithMode,
175        py: Python,
176    ) -> PyResult<Option<Py<PyAny>>> {
177        // ChildOf is an immutable component (relation), so we can only read it.
178        // Return an owned copy instead of a borrowed reference.
179        if let Some(component) = entity.get::<ChildOf>() {
180            let py_component = hierarchy::PyChildOf::try_from(component)?;
181            let obj = Py::new(py, (py_component, PyComponent))?;
182            Ok(Some(obj.into_any()))
183        } else {
184            Ok(None)
185        }
186    }
187}
188
189// ============================================================================
190// ChildrenBridge - Read-only component, auto-managed by Bevy
191// ============================================================================
192
193pub struct ChildrenBridge;
194
195impl ComponentBridge for ChildrenBridge {
196    fn bevy_type_id(&self) -> std::any::TypeId {
197        std::any::TypeId::of::<Children>()
198    }
199
200    fn py_type_ptr(&self) -> *const pyo3::ffi::PyTypeObject {
201        Python::attach(|py| {
202            <hierarchy::PyChildren as pyo3::PyTypeInfo>::type_object(py).as_type_ptr()
203        })
204    }
205
206    fn py_type<'py>(&self, py: Python<'py>) -> pyo3::Bound<'py, pyo3::types::PyType> {
207        <hierarchy::PyChildren as pyo3::PyTypeInfo>::type_object(py)
208    }
209
210    fn name(&self) -> &'static str {
211        "Children"
212    }
213
214    fn register(&self, world: &mut World) -> ComponentId {
215        world.register_component::<Children>()
216    }
217
218    #[inline(always)]
219    fn extract(
220        &self,
221        entity: &mut FilteredEntityMut,
222        component_id: ComponentId,
223        _validity: ValidityFlagWithMode,
224        py: Python,
225    ) -> PyResult<Py<PyAny>> {
226        // Children is read-only, use get_by_id and return an owned copy
227        let ptr = entity
228            .get_by_id(component_id)
229            .ok_or_else(|| pyo3::exceptions::PyRuntimeError::new_err("Children not found"))?;
230
231        let component = unsafe { ptr.deref::<Children>() };
232        let py_component = hierarchy::PyChildren::try_from(component)?;
233        let obj = Py::new(py, (py_component, PyComponent))?;
234        Ok(obj.into_any())
235    }
236
237    fn insert(
238        &self,
239        _world: &mut World,
240        _entity: Entity,
241        _component: &pyo3::Bound<PyAny>,
242    ) -> PyResult<()> {
243        Err(pyo3::exceptions::PyNotImplementedError::new_err(
244            "Children cannot be spawned from Python - it is auto-managed by Bevy",
245        ))
246    }
247
248    fn insert_into_entity(
249        &self,
250        _entity: &mut bevy::ecs::world::EntityWorldMut,
251        _component: &pyo3::Bound<PyAny>,
252    ) -> PyResult<()> {
253        Err(pyo3::exceptions::PyNotImplementedError::new_err(
254            "Children cannot be spawned from Python - it is auto-managed by Bevy",
255        ))
256    }
257
258    fn extract_fn(&self) -> ExtractFn {
259        #[inline(always)]
260        fn extract_impl(
261            entity: &mut FilteredEntityMut,
262            component_id: ComponentId,
263            _validity: ValidityFlagWithMode,
264            py: Python,
265        ) -> PyResult<Py<PyAny>> {
266            let ptr = entity
267                .get_by_id(component_id)
268                .ok_or_else(|| pyo3::exceptions::PyRuntimeError::new_err("Children not found"))?;
269
270            let component = unsafe { ptr.deref::<bevy::ecs::hierarchy::Children>() };
271            let py_component = crate::hierarchy::PyChildren::try_from(component)?;
272            let obj = Py::new(py, (py_component, crate::component::PyComponent))?;
273            Ok(obj.into_any())
274        }
275        extract_impl
276    }
277
278    fn entity_contains(&self, entity: &bevy::ecs::world::EntityRef) -> bool {
279        entity.contains::<Children>()
280    }
281
282    fn extract_from_entity_ref(
283        &self,
284        entity: &bevy::ecs::world::EntityRef,
285        _validity: ValidityFlagWithMode,
286        py: Python,
287    ) -> PyResult<Option<Py<PyAny>>> {
288        if let Some(component) = entity.get::<Children>() {
289            let py_component = hierarchy::PyChildren::try_from(component)?;
290            let obj = Py::new(py, (py_component, PyComponent))?;
291            Ok(Some(obj.into_any()))
292        } else {
293            Ok(None)
294        }
295    }
296
297    fn extract_from_entity_mut(
298        &self,
299        entity: &mut bevy::ecs::world::EntityWorldMut,
300        _validity: ValidityFlagWithMode,
301        py: Python,
302    ) -> PyResult<Option<Py<PyAny>>> {
303        // Children is read-only, return owned copy
304        if let Some(component) = entity.get::<Children>() {
305            let py_component = hierarchy::PyChildren::try_from(component)?;
306            let obj = Py::new(py, (py_component, PyComponent))?;
307            Ok(Some(obj.into_any()))
308        } else {
309            Ok(None)
310        }
311    }
312}
313
314// Re-export commonly used types at crate root
315pub use asset::{NativeAsset, PyAsset};
316pub use asset_path::PyAssetPath;
317pub use component::PyComponent;
318pub use debug_snapshot::{DebugSnapshot, ReloadMemorySnapshotInfo};
319pub use entity::PyEntity;
320pub use handle::{PyHandle, extract_handle_from_any};
321pub use hierarchy::{PyChildOf, PyChildren, PyChildrenIterator};
322pub use materializable::PyMaterializable;
323pub use message::{PyMessage, PyMessageId};
324pub use plugin::{PluginBridge, PyPlugin};
325// Storage types re-exported from pybevy_storage
326pub use pybevy_storage::{
327    AccessMode, AssetStorage, BorrowableStorage, ComponentStorage, ComponentStorageInner,
328    FieldOffset, FieldStorage, FieldStorageInner, FromBorrowedStorage, ListStorage,
329    ListStorageInner, ResourceStorage, ResourceStorageInner, StorageError, ValidityFlag,
330    ValidityFlagWithMode, ValidityGuard, ValueStorage, ValueStorageInner, ViewBridge,
331    ViewFieldAccess, normalize_index,
332};
333pub use registry::{
334    AssetBridge, BatchComponent, BatchFieldMeta, BatchableField, ComponentBatchInsertFn,
335    ComponentBatchMeta, ComponentBridge, DynamicAssetRegistry, DynamicComponentRegistry,
336    DynamicResourceRegistry, ExtractFn, MessageBridge, PluginConfigs, PyRustComponentBatch,
337    ResourceBridge, batch_field_meta_for, field_offset_view_meta_for, set_field_from_numpy,
338};
339pub use reload_request::{
340    CustomComponentEntry, CustomComponentInfo, CustomResourceEntry, CustomResourceInfo,
341    LastSystemError, PendingReloadRequest, PyResourceStorage, ReloadRequestMode, ReloadResult,
342};
343pub use resource::PyResource;
344// Re-export uuid for use in macros (asset_bridge! needs it for synthetic UUID construction)
345pub use uuid;
346
347/// Register core component bridges with global registry.
348/// Call this from main crate during initialization.
349pub fn register_core_bridges() {
350    registry::global_registry::register_component_bridge(ChildOfBridge);
351    registry::global_registry::register_component_bridge(ChildrenBridge);
352    registry::rust_batch::register_rust_batch_bridge();
353}