removed bincode for rkyv

Signed-off-by: Sienna Meridian Satterwhite <sienna@r3t.io>
This commit is contained in:
2025-12-17 19:20:34 +00:00
parent 56f0f0d40b
commit 99e31b1157
47 changed files with 2728 additions and 1697 deletions

View File

@@ -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)

View File

@@ -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::*;

View File

@@ -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
}

View File

@@ -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(), &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 libmarathon::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 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,
/// &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(())
}
}
// All component serialization now uses #[derive(Synced)] with rkyv through ComponentTypeRegistry

View 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);
}
}