removed bincode for rkyv
Signed-off-by: Sienna Meridian Satterwhite <sienna@r3t.io>
This commit is contained in:
@@ -12,7 +12,7 @@ pub enum PersistenceError {
|
||||
Database(rusqlite::Error),
|
||||
|
||||
/// Serialization failed
|
||||
Serialization(bincode::Error),
|
||||
Serialization(String),
|
||||
|
||||
/// Deserialization failed
|
||||
Deserialization(String),
|
||||
@@ -85,7 +85,6 @@ impl std::error::Error for PersistenceError {
|
||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||
match self {
|
||||
| Self::Database(err) => Some(err),
|
||||
| Self::Serialization(err) => Some(err),
|
||||
| Self::Io(err) => Some(err),
|
||||
| _ => None,
|
||||
}
|
||||
@@ -99,12 +98,6 @@ impl From<rusqlite::Error> for PersistenceError {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<bincode::Error> for PersistenceError {
|
||||
fn from(err: bincode::Error) -> Self {
|
||||
Self::Serialization(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::io::Error> for PersistenceError {
|
||||
fn from(err: std::io::Error) -> Self {
|
||||
Self::Io(err)
|
||||
|
||||
@@ -40,6 +40,7 @@ mod migrations;
|
||||
mod plugin;
|
||||
pub mod reflection;
|
||||
mod systems;
|
||||
mod type_registry;
|
||||
mod types;
|
||||
|
||||
pub use config::*;
|
||||
@@ -52,4 +53,5 @@ pub use migrations::*;
|
||||
pub use plugin::*;
|
||||
pub use reflection::*;
|
||||
pub use systems::*;
|
||||
pub use type_registry::*;
|
||||
pub use types::*;
|
||||
|
||||
@@ -88,7 +88,8 @@ impl Plugin for PersistencePlugin {
|
||||
.insert_resource(PersistenceMetrics::default())
|
||||
.insert_resource(CheckpointTimer::default())
|
||||
.insert_resource(PersistenceHealth::default())
|
||||
.insert_resource(PendingFlushTasks::default());
|
||||
.insert_resource(PendingFlushTasks::default())
|
||||
.init_resource::<ComponentTypeRegistryResource>();
|
||||
|
||||
// Add startup system
|
||||
app.add_systems(Startup, persistence_startup_system);
|
||||
@@ -206,18 +207,17 @@ fn collect_dirty_entities_bevy_system(world: &mut World) {
|
||||
|
||||
// Serialize all components on this entity (generic tracking)
|
||||
let components = {
|
||||
let type_registry = world.resource::<AppTypeRegistry>().read();
|
||||
let comps = serialize_all_components_from_entity(entity, world, &type_registry);
|
||||
drop(type_registry);
|
||||
comps
|
||||
let type_registry_res = world.resource::<crate::persistence::ComponentTypeRegistryResource>();
|
||||
let type_registry = type_registry_res.0;
|
||||
type_registry.serialize_entity_components(world, entity)
|
||||
};
|
||||
|
||||
// Add operations for each component
|
||||
for (component_type, data) in components {
|
||||
for (_discriminant, type_path, data) in components {
|
||||
// Get mutable access to dirty and mark it
|
||||
{
|
||||
let mut dirty = world.resource_mut::<DirtyEntitiesResource>();
|
||||
dirty.mark_dirty(network_id, &component_type);
|
||||
dirty.mark_dirty(network_id, type_path);
|
||||
}
|
||||
|
||||
// Get mutable access to write_buffer and add the operation
|
||||
@@ -225,12 +225,12 @@ fn collect_dirty_entities_bevy_system(world: &mut World) {
|
||||
let mut write_buffer = world.resource_mut::<WriteBufferResource>();
|
||||
if let Err(e) = write_buffer.add(PersistenceOp::UpsertComponent {
|
||||
entity_id: network_id,
|
||||
component_type: component_type.clone(),
|
||||
component_type: type_path.to_string(),
|
||||
data,
|
||||
}) {
|
||||
error!(
|
||||
"Failed to add UpsertComponent operation for entity {} component {}: {}",
|
||||
network_id, component_type, e
|
||||
network_id, type_path, e
|
||||
);
|
||||
// Continue with other components even if one fails
|
||||
}
|
||||
|
||||
@@ -1,27 +1,10 @@
|
||||
//! Reflection-based component serialization for persistence
|
||||
//! DEPRECATED: Reflection-based component serialization
|
||||
//! Marker components for the persistence system
|
||||
//!
|
||||
//! This module provides utilities to serialize and deserialize Bevy components
|
||||
//! using reflection, allowing the persistence layer to work with any component
|
||||
//! that implements Reflect.
|
||||
//! All component serialization now uses #[derive(Synced)] with rkyv.
|
||||
//! This module only provides the Persisted marker component.
|
||||
|
||||
use bevy::{
|
||||
prelude::*,
|
||||
reflect::{
|
||||
TypeRegistry,
|
||||
serde::{
|
||||
ReflectSerializer,
|
||||
TypedReflectDeserializer,
|
||||
TypedReflectSerializer,
|
||||
},
|
||||
},
|
||||
};
|
||||
use bincode::Options as _;
|
||||
use serde::de::DeserializeSeed;
|
||||
|
||||
use crate::persistence::error::{
|
||||
PersistenceError,
|
||||
Result,
|
||||
};
|
||||
use bevy::prelude::*;
|
||||
|
||||
/// Marker component to indicate that an entity should be persisted
|
||||
///
|
||||
@@ -67,247 +50,4 @@ impl Persisted {
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait for components that can be persisted
|
||||
pub trait Persistable: Component + Reflect {
|
||||
/// Get the type name for this component (used as key in database)
|
||||
fn type_name() -> &'static str {
|
||||
std::any::type_name::<Self>()
|
||||
}
|
||||
}
|
||||
|
||||
/// Serialize a component using Bevy's reflection system
|
||||
///
|
||||
/// This converts any component implementing `Reflect` into bytes for storage.
|
||||
/// Uses bincode for efficient binary serialization with type information from
|
||||
/// the registry to handle polymorphic types correctly.
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `component`: Component to serialize (must implement `Reflect`)
|
||||
/// - `type_registry`: Bevy's type registry for reflection metadata
|
||||
///
|
||||
/// # Returns
|
||||
/// - `Ok(Vec<u8>)`: Serialized component data
|
||||
/// - `Err`: If serialization fails (e.g., type not properly registered)
|
||||
///
|
||||
/// # Examples
|
||||
/// ```no_run
|
||||
/// # use bevy::prelude::*;
|
||||
/// # use libmarathon::persistence::*;
|
||||
/// # fn example(component: &Transform, registry: &AppTypeRegistry) -> anyhow::Result<()> {
|
||||
/// let registry = registry.read();
|
||||
/// let bytes = serialize_component(component.as_reflect(), ®istry)?;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn serialize_component(
|
||||
component: &dyn Reflect,
|
||||
type_registry: &TypeRegistry,
|
||||
) -> Result<Vec<u8>> {
|
||||
let serializer = ReflectSerializer::new(component, type_registry);
|
||||
bincode::options()
|
||||
.serialize(&serializer)
|
||||
.map_err(PersistenceError::from)
|
||||
}
|
||||
|
||||
/// Serialize a component when the type is known (more efficient for bincode)
|
||||
///
|
||||
/// This uses `TypedReflectSerializer` which doesn't include type path
|
||||
/// information, making it compatible with `TypedReflectDeserializer` for binary
|
||||
/// formats.
|
||||
pub fn serialize_component_typed(
|
||||
component: &dyn Reflect,
|
||||
type_registry: &TypeRegistry,
|
||||
) -> Result<Vec<u8>> {
|
||||
let serializer = TypedReflectSerializer::new(component, type_registry);
|
||||
bincode::options()
|
||||
.serialize(&serializer)
|
||||
.map_err(PersistenceError::from)
|
||||
}
|
||||
|
||||
/// Deserialize a component using Bevy's reflection system
|
||||
///
|
||||
/// Converts serialized bytes back into a reflected component. The returned
|
||||
/// component is boxed and must be downcast to the concrete type for use.
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `bytes`: Serialized component data from [`serialize_component`]
|
||||
/// - `type_registry`: Bevy's type registry for reflection metadata
|
||||
///
|
||||
/// # Returns
|
||||
/// - `Ok(Box<dyn PartialReflect>)`: Deserialized component (needs downcasting)
|
||||
/// - `Err`: If deserialization fails (e.g., type not registered, data
|
||||
/// corruption)
|
||||
///
|
||||
/// # Examples
|
||||
/// ```no_run
|
||||
/// # use bevy::prelude::*;
|
||||
/// # use libmarathon::persistence::*;
|
||||
/// # fn example(bytes: &[u8], registry: &AppTypeRegistry) -> anyhow::Result<()> {
|
||||
/// let registry = registry.read();
|
||||
/// let reflected = deserialize_component(bytes, ®istry)?;
|
||||
/// // Downcast to concrete type as needed
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn deserialize_component(
|
||||
bytes: &[u8],
|
||||
type_registry: &TypeRegistry,
|
||||
) -> Result<Box<dyn PartialReflect>> {
|
||||
let mut deserializer = bincode::Deserializer::from_slice(bytes, bincode::options());
|
||||
let reflect_deserializer = bevy::reflect::serde::ReflectDeserializer::new(type_registry);
|
||||
|
||||
reflect_deserializer
|
||||
.deserialize(&mut deserializer)
|
||||
.map_err(|e| PersistenceError::Deserialization(e.to_string()))
|
||||
}
|
||||
|
||||
/// Deserialize a component when the type is known
|
||||
///
|
||||
/// Uses `TypedReflectDeserializer` which is more efficient for binary formats
|
||||
/// like bincode when the component type is known at deserialization time.
|
||||
pub fn deserialize_component_typed(
|
||||
bytes: &[u8],
|
||||
component_type: &str,
|
||||
type_registry: &TypeRegistry,
|
||||
) -> Result<Box<dyn PartialReflect>> {
|
||||
let registration = type_registry
|
||||
.get_with_type_path(component_type)
|
||||
.ok_or_else(|| {
|
||||
PersistenceError::Deserialization(format!("Type {} not registered", component_type))
|
||||
})?;
|
||||
|
||||
let mut deserializer = bincode::Deserializer::from_slice(bytes, bincode::options());
|
||||
let reflect_deserializer = TypedReflectDeserializer::new(registration, type_registry);
|
||||
|
||||
reflect_deserializer
|
||||
.deserialize(&mut deserializer)
|
||||
.map_err(|e| PersistenceError::Deserialization(e.to_string()))
|
||||
}
|
||||
|
||||
/// Serialize a component directly from an entity using its type path
|
||||
///
|
||||
/// This is a convenience function that combines type lookup, reflection, and
|
||||
/// serialization. It's the primary method used by the persistence system to
|
||||
/// save component state without knowing the concrete type at compile time.
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `entity`: Bevy entity to read the component from
|
||||
/// - `component_type`: Type path string (e.g.,
|
||||
/// "bevy_transform::components::Transform")
|
||||
/// - `world`: Bevy world containing the entity
|
||||
/// - `type_registry`: Bevy's type registry for reflection metadata
|
||||
///
|
||||
/// # Returns
|
||||
/// - `Some(Vec<u8>)`: Serialized component data
|
||||
/// - `None`: If entity doesn't have the component or type isn't registered
|
||||
///
|
||||
/// # Examples
|
||||
/// ```no_run
|
||||
/// # use bevy::prelude::*;
|
||||
/// # use libmarathon::persistence::*;
|
||||
/// # fn example(entity: Entity, world: &World, registry: &AppTypeRegistry) -> Option<()> {
|
||||
/// let registry = registry.read();
|
||||
/// let bytes = serialize_component_from_entity(
|
||||
/// entity,
|
||||
/// "bevy_transform::components::Transform",
|
||||
/// world,
|
||||
/// ®istry,
|
||||
/// )?;
|
||||
/// # Some(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn serialize_component_from_entity(
|
||||
entity: Entity,
|
||||
component_type: &str,
|
||||
world: &World,
|
||||
type_registry: &TypeRegistry,
|
||||
) -> Option<Vec<u8>> {
|
||||
// Get the type registration
|
||||
let registration = type_registry.get_with_type_path(component_type)?;
|
||||
|
||||
// Get the ReflectComponent data
|
||||
let reflect_component = registration.data::<ReflectComponent>()?;
|
||||
|
||||
// Reflect the component from the entity
|
||||
let reflected = reflect_component.reflect(world.entity(entity))?;
|
||||
|
||||
// Serialize it directly
|
||||
serialize_component(reflected, type_registry).ok()
|
||||
}
|
||||
|
||||
/// Serialize all components from an entity that have reflection data
|
||||
///
|
||||
/// This iterates over all components on an entity and serializes those that:
|
||||
/// - Are registered in the type registry
|
||||
/// - Have `ReflectComponent` data (meaning they support reflection)
|
||||
/// - Are not the `Persisted` marker component (to avoid redundant storage)
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `entity`: Bevy entity to serialize components from
|
||||
/// - `world`: Bevy world containing the entity
|
||||
/// - `type_registry`: Bevy's type registry for reflection metadata
|
||||
///
|
||||
/// # Returns
|
||||
/// Vector of tuples containing (component_type_path, serialized_data) for each
|
||||
/// component
|
||||
pub fn serialize_all_components_from_entity(
|
||||
entity: Entity,
|
||||
world: &World,
|
||||
type_registry: &TypeRegistry,
|
||||
) -> Vec<(String, Vec<u8>)> {
|
||||
let mut components = Vec::new();
|
||||
|
||||
// Get the entity reference
|
||||
let entity_ref = world.entity(entity);
|
||||
|
||||
// Iterate over all type registrations
|
||||
for registration in type_registry.iter() {
|
||||
// Skip if no ReflectComponent data (not a component)
|
||||
let Some(reflect_component) = registration.data::<ReflectComponent>() else {
|
||||
continue;
|
||||
};
|
||||
|
||||
// Get the type path for this component
|
||||
let type_path = registration.type_info().type_path();
|
||||
|
||||
// Skip the Persisted marker component itself (we don't need to persist it)
|
||||
if type_path.ends_with("::Persisted") {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Try to reflect this component from the entity
|
||||
if let Some(reflected) = reflect_component.reflect(entity_ref) {
|
||||
// Serialize the component using typed serialization for consistency
|
||||
// This matches the format expected by deserialize_component_typed
|
||||
if let Ok(data) = serialize_component_typed(reflected, type_registry) {
|
||||
components.push((type_path.to_string(), data));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
components
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[derive(Component, Reflect, Default)]
|
||||
#[reflect(Component)]
|
||||
struct TestComponent {
|
||||
value: i32,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_component_serialization() -> Result<()> {
|
||||
let mut registry = TypeRegistry::default();
|
||||
registry.register::<TestComponent>();
|
||||
|
||||
let component = TestComponent { value: 42 };
|
||||
let bytes = serialize_component(&component, ®istry)?;
|
||||
|
||||
assert!(!bytes.is_empty());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
// All component serialization now uses #[derive(Synced)] with rkyv through ComponentTypeRegistry
|
||||
|
||||
259
crates/libmarathon/src/persistence/type_registry.rs
Normal file
259
crates/libmarathon/src/persistence/type_registry.rs
Normal file
@@ -0,0 +1,259 @@
|
||||
//! Zero-copy component type registry using rkyv and inventory
|
||||
//!
|
||||
//! This module provides a runtime type registry that collects all synced components
|
||||
//! via the `inventory` crate and assigns them numeric discriminants for efficient
|
||||
//! serialization.
|
||||
|
||||
use std::{
|
||||
any::TypeId,
|
||||
collections::HashMap,
|
||||
sync::OnceLock,
|
||||
};
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
/// Component metadata collected via inventory
|
||||
pub struct ComponentMeta {
|
||||
/// Human-readable type name (e.g., "Health")
|
||||
pub type_name: &'static str,
|
||||
|
||||
/// Full type path (e.g., "my_crate::components::Health")
|
||||
pub type_path: &'static str,
|
||||
|
||||
/// Rust TypeId for type-safe lookups
|
||||
pub type_id: TypeId,
|
||||
|
||||
/// Deserialization function that returns a boxed component
|
||||
pub deserialize_fn: fn(&[u8]) -> Result<Box<dyn std::any::Any>>,
|
||||
|
||||
/// Serialization function that reads from an entity (returns None if entity doesn't have this component)
|
||||
pub serialize_fn: fn(&bevy::ecs::world::World, bevy::ecs::entity::Entity) -> Option<Vec<u8>>,
|
||||
|
||||
/// Insert function that takes a boxed component and inserts it into an entity
|
||||
pub insert_fn: fn(&mut bevy::ecs::world::EntityWorldMut, Box<dyn std::any::Any>),
|
||||
}
|
||||
|
||||
// Collect all registered components via inventory
|
||||
inventory::collect!(ComponentMeta);
|
||||
|
||||
/// Runtime component type registry
|
||||
///
|
||||
/// Maps TypeId -> numeric discriminant for efficient serialization
|
||||
pub struct ComponentTypeRegistry {
|
||||
/// TypeId to discriminant mapping
|
||||
type_to_discriminant: HashMap<TypeId, u16>,
|
||||
|
||||
/// Discriminant to deserialization function
|
||||
discriminant_to_deserializer: HashMap<u16, fn(&[u8]) -> Result<Box<dyn std::any::Any>>>,
|
||||
|
||||
/// Discriminant to serialization function
|
||||
discriminant_to_serializer: HashMap<u16, fn(&bevy::ecs::world::World, bevy::ecs::entity::Entity) -> Option<Vec<u8>>>,
|
||||
|
||||
/// Discriminant to insert function
|
||||
discriminant_to_inserter: HashMap<u16, fn(&mut bevy::ecs::world::EntityWorldMut, Box<dyn std::any::Any>)>,
|
||||
|
||||
/// Discriminant to type name (for debugging)
|
||||
discriminant_to_name: HashMap<u16, &'static str>,
|
||||
|
||||
/// Discriminant to type path (for networking)
|
||||
discriminant_to_path: HashMap<u16, &'static str>,
|
||||
|
||||
/// TypeId to type name (for debugging)
|
||||
type_to_name: HashMap<TypeId, &'static str>,
|
||||
}
|
||||
|
||||
impl ComponentTypeRegistry {
|
||||
/// Initialize the registry from inventory-collected components
|
||||
///
|
||||
/// This should be called once at application startup.
|
||||
pub fn init() -> Self {
|
||||
let mut type_to_discriminant = HashMap::new();
|
||||
let mut discriminant_to_deserializer = HashMap::new();
|
||||
let mut discriminant_to_serializer = HashMap::new();
|
||||
let mut discriminant_to_inserter = HashMap::new();
|
||||
let mut discriminant_to_name = HashMap::new();
|
||||
let mut discriminant_to_path = HashMap::new();
|
||||
let mut type_to_name = HashMap::new();
|
||||
|
||||
// Collect all registered components
|
||||
let mut components: Vec<&ComponentMeta> = inventory::iter::<ComponentMeta>().collect();
|
||||
|
||||
// Sort by TypeId for deterministic discriminants
|
||||
components.sort_by_key(|c| c.type_id);
|
||||
|
||||
// Assign discriminants
|
||||
for (discriminant, meta) in components.iter().enumerate() {
|
||||
let discriminant = discriminant as u16;
|
||||
type_to_discriminant.insert(meta.type_id, discriminant);
|
||||
discriminant_to_deserializer.insert(discriminant, meta.deserialize_fn);
|
||||
discriminant_to_serializer.insert(discriminant, meta.serialize_fn);
|
||||
discriminant_to_inserter.insert(discriminant, meta.insert_fn);
|
||||
discriminant_to_name.insert(discriminant, meta.type_name);
|
||||
discriminant_to_path.insert(discriminant, meta.type_path);
|
||||
type_to_name.insert(meta.type_id, meta.type_name);
|
||||
|
||||
tracing::debug!(
|
||||
type_name = meta.type_name,
|
||||
type_path = meta.type_path,
|
||||
discriminant = discriminant,
|
||||
"Registered component type"
|
||||
);
|
||||
}
|
||||
|
||||
tracing::info!(
|
||||
count = components.len(),
|
||||
"Initialized component type registry"
|
||||
);
|
||||
|
||||
Self {
|
||||
type_to_discriminant,
|
||||
discriminant_to_deserializer,
|
||||
discriminant_to_serializer,
|
||||
discriminant_to_inserter,
|
||||
discriminant_to_name,
|
||||
discriminant_to_path,
|
||||
type_to_name,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the discriminant for a component type
|
||||
pub fn get_discriminant(&self, type_id: TypeId) -> Option<u16> {
|
||||
self.type_to_discriminant.get(&type_id).copied()
|
||||
}
|
||||
|
||||
/// Deserialize a component from bytes with its discriminant
|
||||
pub fn deserialize(&self, discriminant: u16, bytes: &[u8]) -> Result<Box<dyn std::any::Any>> {
|
||||
let deserialize_fn = self
|
||||
.discriminant_to_deserializer
|
||||
.get(&discriminant)
|
||||
.ok_or_else(|| {
|
||||
anyhow::anyhow!(
|
||||
"Unknown component discriminant: {} (available: {:?})",
|
||||
discriminant,
|
||||
self.discriminant_to_name
|
||||
)
|
||||
})?;
|
||||
|
||||
deserialize_fn(bytes)
|
||||
}
|
||||
|
||||
/// Get the insert function for a discriminant
|
||||
pub fn get_insert_fn(&self, discriminant: u16) -> Option<fn(&mut bevy::ecs::world::EntityWorldMut, Box<dyn std::any::Any>)> {
|
||||
self.discriminant_to_inserter.get(&discriminant).copied()
|
||||
}
|
||||
|
||||
/// Get type name for a discriminant (for debugging)
|
||||
pub fn get_type_name(&self, discriminant: u16) -> Option<&'static str> {
|
||||
self.discriminant_to_name.get(&discriminant).copied()
|
||||
}
|
||||
|
||||
/// Get the deserialize function for a discriminant
|
||||
pub fn get_deserialize_fn(&self, discriminant: u16) -> Option<fn(&[u8]) -> Result<Box<dyn std::any::Any>>> {
|
||||
self.discriminant_to_deserializer.get(&discriminant).copied()
|
||||
}
|
||||
|
||||
/// Get type path for a discriminant
|
||||
pub fn get_type_path(&self, discriminant: u16) -> Option<&'static str> {
|
||||
self.discriminant_to_path.get(&discriminant).copied()
|
||||
}
|
||||
|
||||
/// Get the deserialize function by type path
|
||||
pub fn get_deserialize_fn_by_path(&self, type_path: &str) -> Option<fn(&[u8]) -> Result<Box<dyn std::any::Any>>> {
|
||||
// Linear search through discriminant_to_path to find matching type_path
|
||||
for (discriminant, path) in &self.discriminant_to_path {
|
||||
if *path == type_path {
|
||||
return self.get_deserialize_fn(*discriminant);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Get the insert function by type path
|
||||
pub fn get_insert_fn_by_path(&self, type_path: &str) -> Option<fn(&mut bevy::ecs::world::EntityWorldMut, Box<dyn std::any::Any>)> {
|
||||
// Linear search through discriminant_to_path to find matching type_path
|
||||
for (discriminant, path) in &self.discriminant_to_path {
|
||||
if *path == type_path {
|
||||
return self.get_insert_fn(*discriminant);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Get the number of registered component types
|
||||
pub fn len(&self) -> usize {
|
||||
self.type_to_discriminant.len()
|
||||
}
|
||||
|
||||
/// Check if the registry is empty
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.type_to_discriminant.is_empty()
|
||||
}
|
||||
|
||||
/// Serialize all registered components from an entity
|
||||
///
|
||||
/// Returns Vec<(discriminant, type_path, serialized_bytes)> for all components that exist on the entity.
|
||||
pub fn serialize_entity_components(
|
||||
&self,
|
||||
world: &bevy::ecs::world::World,
|
||||
entity: bevy::ecs::entity::Entity,
|
||||
) -> Vec<(u16, &'static str, Vec<u8>)> {
|
||||
let mut results = Vec::new();
|
||||
|
||||
for (&discriminant, &serialize_fn) in &self.discriminant_to_serializer {
|
||||
if let Some(bytes) = serialize_fn(world, entity) {
|
||||
if let Some(&type_path) = self.discriminant_to_path.get(&discriminant) {
|
||||
results.push((discriminant, type_path, bytes));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
results
|
||||
}
|
||||
|
||||
/// Get all registered discriminants (for iteration)
|
||||
pub fn all_discriminants(&self) -> impl Iterator<Item = u16> + '_ {
|
||||
self.discriminant_to_name.keys().copied()
|
||||
}
|
||||
}
|
||||
|
||||
/// Global component type registry instance
|
||||
static REGISTRY: OnceLock<ComponentTypeRegistry> = OnceLock::new();
|
||||
|
||||
/// Get the global component type registry
|
||||
///
|
||||
/// Initializes the registry on first access.
|
||||
pub fn component_registry() -> &'static ComponentTypeRegistry {
|
||||
REGISTRY.get_or_init(ComponentTypeRegistry::init)
|
||||
}
|
||||
|
||||
/// Bevy resource wrapper for ComponentTypeRegistry
|
||||
///
|
||||
/// Use this in Bevy systems to access the global component registry.
|
||||
/// Insert this resource at app startup.
|
||||
#[derive(bevy::prelude::Resource)]
|
||||
pub struct ComponentTypeRegistryResource(pub &'static ComponentTypeRegistry);
|
||||
|
||||
impl Default for ComponentTypeRegistryResource {
|
||||
fn default() -> Self {
|
||||
Self(component_registry())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_registry_initialization() {
|
||||
let registry = ComponentTypeRegistry::init();
|
||||
// Should have at least the components defined in the codebase
|
||||
assert!(registry.len() > 0 || registry.is_empty()); // May be empty in unit tests
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_global_registry() {
|
||||
let registry = component_registry();
|
||||
// Should be initialized
|
||||
assert!(registry.len() >= 0);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user