Files
marathon/crates/lib/src/persistence/reflection.rs
Sienna Meridian Satterwhite 4965d13070 code review results
Signed-off-by: Sienna Meridian Satterwhite <sienna@r3t.io>
2026-02-07 14:11:03 +00:00

314 lines
10 KiB
Rust

//! Reflection-based component serialization for persistence
//!
//! This module provides utilities to serialize and deserialize Bevy components
//! using reflection, allowing the persistence layer to work with any component
//! that implements Reflect.
use bevy::{
prelude::*,
reflect::{
TypeRegistry,
serde::{
ReflectSerializer,
TypedReflectDeserializer,
TypedReflectSerializer,
},
},
};
use bincode::Options as _;
use serde::de::DeserializeSeed;
use crate::persistence::error::{
PersistenceError,
Result,
};
/// Marker component to indicate that an entity should be persisted
///
/// Add this component to any entity that should have its state persisted to
/// disk. The persistence system will automatically serialize all components on
/// entities with this marker when they change.
///
/// # Triggering Persistence
///
/// To trigger persistence after modifying components on an entity, access
/// `Persisted` mutably through a query. Bevy's change detection will
/// automatically mark it as changed:
///
/// ```no_run
/// # use bevy::prelude::*;
/// # use lib::persistence::*;
/// fn update_position(mut query: Query<(&mut Transform, &mut Persisted)>) {
/// for (mut transform, mut persisted) in query.iter_mut() {
/// transform.translation.x += 1.0;
/// // Accessing &mut Persisted triggers change detection automatically
/// }
/// }
/// ```
///
/// Alternatively, use `auto_track_transform_changes_system` for automatic
/// persistence of Transform changes without manual queries.
#[derive(Component, Reflect, Default)]
#[reflect(Component)]
pub struct Persisted {
/// Unique network ID for this entity
pub network_id: uuid::Uuid,
}
impl Persisted {
pub fn new() -> Self {
Self {
network_id: uuid::Uuid::new_v4(),
}
}
pub fn with_id(network_id: uuid::Uuid) -> Self {
Self { network_id }
}
}
/// 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 lib::persistence::*;
/// # fn example(component: &Transform, registry: &AppTypeRegistry) -> anyhow::Result<()> {
/// let registry = registry.read();
/// let bytes = serialize_component(component.as_reflect(), &registry)?;
/// # 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 lib::persistence::*;
/// # fn example(bytes: &[u8], registry: &AppTypeRegistry) -> anyhow::Result<()> {
/// let registry = registry.read();
/// let reflected = deserialize_component(bytes, &registry)?;
/// // 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 lib::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,
/// &registry,
/// )?;
/// # 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, &registry)?;
assert!(!bytes.is_empty());
Ok(())
}
}