khora_data/scene/
messagepack_strategy.rs1use super::{DeserializationError, SerializationError, SerializationStrategy};
18use crate::{
19 ecs::World,
20 scene::{registry::ComponentRegistration, SceneCommand, SceneRecipe},
21};
22use khora_core::{ecs::entity::EntityId, graph::topological_sort};
23use std::collections::HashMap;
24
25pub struct MessagePackSerializationStrategy;
27
28impl Default for MessagePackSerializationStrategy {
29 fn default() -> Self {
30 Self
31 }
32}
33
34impl MessagePackSerializationStrategy {
35 pub fn new() -> Self {
39 Self
40 }
41}
42
43impl SerializationStrategy for MessagePackSerializationStrategy {
44 fn get_strategy_id(&self) -> &'static str {
45 "KH_MESSAGEPACK_V1"
46 }
47
48 fn serialize(&self, world: &World) -> Result<Vec<u8>, SerializationError> {
49 let mut commands = Vec::new();
50
51 let nodes: Vec<EntityId> = world.iter_entities().collect();
54 let mut edges: Vec<(EntityId, EntityId)> = Vec::new();
55 for &entity_id in &nodes {
56 if let Some(parent) = world.get::<crate::ecs::Parent>(entity_id) {
57 edges.push((parent.0, entity_id));
58 }
59 }
60 let sorted_entities = topological_sort(nodes, edges).map_err(|_| {
61 SerializationError::ProcessingFailed("Cycle detected in scene hierarchy.".to_string())
62 })?;
63
64 for entity_id in sorted_entities {
65 commands.push(SceneCommand::Spawn { id: entity_id });
66 for (component_type, component_data) in
67 crate::scene::serialize_all_components(world, entity_id)
68 {
69 commands.push(SceneCommand::AddComponent {
70 entity_id,
71 component_type,
72 component_data,
73 });
74 }
75 if let Some(parent) = world.get::<crate::ecs::Parent>(entity_id) {
76 commands.push(SceneCommand::SetParent {
77 child_id: entity_id,
78 parent_id: parent.0,
79 });
80 }
81 }
82
83 let scene_recipe = SceneRecipe { commands };
84 rmp_serde::to_vec_named(&scene_recipe)
85 .map_err(|e| SerializationError::ProcessingFailed(e.to_string()))
86 }
87
88 fn deserialize(&self, data: &[u8], world: &mut World) -> Result<(), DeserializationError> {
89 let recipe: SceneRecipe = rmp_serde::from_slice(data)
90 .map_err(|e| DeserializationError::InvalidFormat(e.to_string()))?;
91
92 let mut id_map = HashMap::<EntityId, EntityId>::new();
93
94 for command in recipe.commands {
95 match command {
96 SceneCommand::Spawn { id } => {
97 let new_id = world.spawn(());
98 id_map.insert(id, new_id);
99 }
100 SceneCommand::AddComponent {
101 entity_id,
102 component_type,
103 component_data,
104 } => {
105 let live_id = id_map.get(&entity_id).copied().ok_or_else(|| {
106 DeserializationError::WorldPopulationFailed(format!(
107 "Recipe AddComponent for unspawned entity {:?}",
108 entity_id
109 ))
110 })?;
111 let reg = inventory::iter::<ComponentRegistration>
112 .into_iter()
113 .find(|r| r.type_name == component_type.as_str())
114 .ok_or_else(|| {
115 DeserializationError::WorldPopulationFailed(format!(
116 "Unknown component type '{}' in recipe",
117 component_type
118 ))
119 })?;
120 (reg.deserialize_recipe)(world, live_id, &component_data).map_err(|e| {
121 DeserializationError::WorldPopulationFailed(format!(
122 "Failed to apply component '{}': {:?}",
123 component_type, e
124 ))
125 })?;
126 }
127 SceneCommand::SetParent {
128 child_id,
129 parent_id,
130 } => {
131 let child = id_map.get(&child_id).copied().ok_or_else(|| {
132 DeserializationError::WorldPopulationFailed(format!(
133 "Recipe SetParent unknown child {:?}",
134 child_id
135 ))
136 })?;
137 let parent = id_map.get(&parent_id).copied().ok_or_else(|| {
138 DeserializationError::WorldPopulationFailed(format!(
139 "Recipe SetParent unknown parent {:?}",
140 parent_id
141 ))
142 })?;
143 crate::scene::link_parent_child(world, child, parent);
144 }
145 }
146 }
147 Ok(())
148 }
149}
150
151#[cfg(test)]
152mod tests {
153 use super::*;
154 use crate::ecs::{GlobalTransform, Transform, World};
155 use khora_core::math::Vec3;
156
157 #[test]
158 fn round_trip_preserves_transform() {
159 let mut src = World::new();
160 let t = Transform {
161 translation: Vec3::new(7.0, 0.0, -3.0),
162 ..Default::default()
163 };
164 src.spawn((t, GlobalTransform::identity()));
165
166 let strat = MessagePackSerializationStrategy::new();
167 let bytes = strat.serialize(&src).unwrap();
168 assert!(!bytes.is_empty());
169
170 let mut dst = World::new();
171 strat.deserialize(&bytes, &mut dst).unwrap();
172
173 let mut q = dst.query::<&Transform>();
174 let got = q.next().expect("at least one entity restored");
175 assert_eq!(*got, t);
176 }
177}