finished zero-copy migration!

now the entire networking and persistence stack is zero-copy with
single-allocation, single-copy reads.

Closes #128

Signed-off-by: Sienna Meridian Satterwhite <sienna@r3t.io>
This commit is contained in:
2025-12-17 21:45:46 +00:00
parent 7d24abf113
commit 99db2c90b4
14 changed files with 106 additions and 81 deletions

View File

@@ -485,7 +485,7 @@ pub fn save_session(conn: &mut Connection, session: &crate::networking::Session)
session.last_active,
session.entity_count as i64,
session.state.to_string(),
session.secret,
session.secret.as_ref().map(|b| b.as_ref()),
],
)?;
Ok(())
@@ -517,7 +517,8 @@ pub fn load_session(
last_active: row.get(3)?,
entity_count: row.get::<_, i64>(4)? as usize,
state,
secret: row.get(6)?,
secret: row.get::<_, Option<std::borrow::Cow<'_, [u8]>>>(6)?
.map(|cow| bytes::Bytes::copy_from_slice(&cow)),
})
},
)
@@ -548,7 +549,8 @@ pub fn get_last_active_session(conn: &Connection) -> Result<Option<crate::networ
last_active: row.get(3)?,
entity_count: row.get::<_, i64>(4)? as usize,
state,
secret: row.get(6)?,
secret: row.get::<_, Option<std::borrow::Cow<'_, [u8]>>>(6)?
.map(|cow| bytes::Bytes::copy_from_slice(&cow)),
})
},
)
@@ -643,10 +645,10 @@ pub fn load_entity_components(
let components: Vec<LoadedComponent> = stmt
.query_map([entity_id.as_bytes()], |row| {
let data_vec: Vec<u8> = row.get(1)?;
let data_cow: std::borrow::Cow<'_, [u8]> = row.get(1)?;
Ok(LoadedComponent {
component_type: row.get(0)?,
data: bytes::Bytes::from(data_vec),
data: bytes::Bytes::copy_from_slice(&data_cow),
})
})?
.collect::<std::result::Result<Vec<_>, _>>()?;
@@ -669,7 +671,7 @@ pub fn load_entity_by_network_id(
WHERE id = ?1",
[network_id.as_bytes()],
|row| {
let id_bytes: Vec<u8> = row.get(0)?;
let id_bytes: std::borrow::Cow<'_, [u8]> = row.get(0)?;
let mut id_array = [0u8; 16];
id_array.copy_from_slice(&id_bytes);
let id = uuid::Uuid::from_bytes(id_array);
@@ -714,7 +716,7 @@ pub fn load_all_entities(conn: &Connection) -> Result<Vec<LoadedEntity>> {
)?;
let entity_rows = stmt.query_map([], |row| {
let id_bytes: Vec<u8> = row.get(0)?;
let id_bytes: std::borrow::Cow<'_, [u8]> = row.get(0)?;
let mut id_array = [0u8; 16];
id_array.copy_from_slice(&id_bytes);
let id = uuid::Uuid::from_bytes(id_array);
@@ -761,7 +763,7 @@ pub fn load_entities_by_type(conn: &Connection, entity_type: &str) -> Result<Vec
)?;
let entity_rows = stmt.query_map([entity_type], |row| {
let id_bytes: Vec<u8> = row.get(0)?;
let id_bytes: std::borrow::Cow<'_, [u8]> = row.get(0)?;
let mut id_array = [0u8; 16];
id_array.copy_from_slice(&id_bytes);
let id = uuid::Uuid::from_bytes(id_array);

View File

@@ -1,8 +1,8 @@
//! Zero-copy component type registry using rkyv and inventory
//! 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.
//! 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,
@@ -26,10 +26,13 @@ pub struct ComponentMeta {
/// 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<bytes::Bytes>,
/// 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<bytes::Bytes>,
/// Insert function that takes a boxed component and inserts it into an entity
/// 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>),
}
@@ -47,10 +50,14 @@ pub struct ComponentTypeRegistry {
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<bytes::Bytes>>,
discriminant_to_serializer: HashMap<
u16,
fn(&bevy::ecs::world::World, bevy::ecs::entity::Entity) -> Option<bytes::Bytes>,
>,
/// Discriminant to insert function
discriminant_to_inserter: HashMap<u16, fn(&mut bevy::ecs::world::EntityWorldMut, Box<dyn std::any::Any>)>,
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>,
@@ -138,7 +145,10 @@ impl ComponentTypeRegistry {
}
/// 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>)> {
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()
}
@@ -148,8 +158,13 @@ impl ComponentTypeRegistry {
}
/// 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()
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
@@ -158,7 +173,10 @@ impl ComponentTypeRegistry {
}
/// 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>>> {
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 {
@@ -169,7 +187,10 @@ impl ComponentTypeRegistry {
}
/// 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>)> {
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 {
@@ -191,7 +212,8 @@ impl ComponentTypeRegistry {
/// Serialize all registered components from an entity
///
/// Returns Vec<(discriminant, type_path, serialized_bytes)> for all components that exist on the 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,