khora_lanes/scene_lane/strategies/
recipe_lane.rs1use crate::scene_lane::strategies::{
18 DeserializationError, SerializationError, SerializationStrategy,
19};
20use bincode::{config, Decode};
21use khora_core::{ecs::entity::EntityId, graph::topological_sort};
22use khora_data::{
23 ecs::{Component, Parent, SerializableParent, SerializableTransform, Transform, World},
24 scene::{SceneCommand, SceneRecipe},
25};
26use std::{any, collections::HashMap};
27
28type ComponentDeserializer =
33 Box<dyn Fn(&mut World, EntityId, &[u8]) -> Result<(), DeserializationError> + Send + Sync>;
34
35#[derive(Default)]
40struct DeserializerRegistry {
41 map: HashMap<String, ComponentDeserializer>,
42}
43
44impl DeserializerRegistry {
45 fn new() -> Self {
47 let mut registry = Self::default();
48
49 registry.register::<Transform, SerializableTransform>(|st| Transform {
52 translation: st.translation,
53 rotation: st.rotation,
54 scale: st.scale,
55 });
56 registry.register::<Parent, SerializableParent>(|sp| Parent(sp.0));
57
58 registry
59 }
60
61 fn register<C, S>(&mut self, from_serializable: fn(S) -> C)
70 where
71 C: Component,
72 S: Decode<()> + 'static,
73 {
74 let type_name = any::type_name::<C>().to_string();
75 self.map.insert(
76 type_name,
77 Box::new(move |world, entity_id, data| {
78 let (serializable_component, _): (S, _) =
79 bincode::decode_from_slice_with_context(data, config::standard(), ())
80 .map_err(|e| DeserializationError::InvalidFormat(e.to_string()))?;
81 let live_component = from_serializable(serializable_component);
82 world.add_component(entity_id, live_component).ok();
83 Ok(())
84 }),
85 );
86 }
87
88 fn get(&self, type_name: &str) -> Option<&ComponentDeserializer> {
90 self.map.get(type_name)
91 }
92}
93
94pub struct RecipeSerializationLane {
101 deserializers: DeserializerRegistry,
102}
103
104impl Default for RecipeSerializationLane {
105 fn default() -> Self {
106 Self {
107 deserializers: DeserializerRegistry::new(),
108 }
109 }
110}
111
112impl RecipeSerializationLane {
113 pub fn new() -> Self {
115 Self::default()
116 }
117}
118
119impl SerializationStrategy for RecipeSerializationLane {
120 fn get_strategy_id(&self) -> &'static str {
121 "KH_RECIPE_V1"
122 }
123
124 fn serialize(&self, world: &World) -> Result<Vec<u8>, SerializationError> {
126 let mut commands = Vec::new();
127
128 let nodes: Vec<EntityId> = world.iter_entities().collect();
130 let mut edges: Vec<(EntityId, EntityId)> = Vec::new();
131 for &entity_id in &nodes {
132 if let Some(parent) = world.get::<Parent>(entity_id) {
133 edges.push((parent.0, entity_id));
135 }
136 }
137
138 let sorted_entities = topological_sort(nodes, edges).map_err(|_| {
140 SerializationError::ProcessingFailed("Cycle detected in scene hierarchy.".to_string())
141 })?;
142
143 for entity_id in sorted_entities {
145 commands.push(SceneCommand::Spawn { id: entity_id });
147
148 if let Some(transform) = world.get::<Transform>(entity_id) {
150 let serializable = SerializableTransform {
152 translation: transform.translation,
153 rotation: transform.rotation,
154 scale: transform.scale,
155 };
156 commands.push(SceneCommand::AddComponent {
157 entity_id,
158 component_type: any::type_name::<Transform>().to_string(),
159 component_data: bincode::encode_to_vec(serializable, config::standard())
161 .unwrap(),
162 });
163 }
164 if let Some(parent) = world.get::<Parent>(entity_id) {
165 let serializable = SerializableParent(parent.0);
166 commands.push(SceneCommand::AddComponent {
167 entity_id,
168 component_type: any::type_name::<Parent>().to_string(),
169 component_data: bincode::encode_to_vec(serializable, config::standard())
170 .unwrap(),
171 });
172 }
173 }
174
175 let scene_recipe = SceneRecipe { commands };
176 bincode::encode_to_vec(&scene_recipe, config::standard())
177 .map_err(|e| SerializationError::ProcessingFailed(e.to_string()))
178 }
179
180 fn deserialize(&self, data: &[u8], world: &mut World) -> Result<(), DeserializationError> {
182 let (recipe, _): (SceneRecipe, _) = bincode::decode_from_slice(data, config::standard())
183 .map_err(|e| DeserializationError::InvalidFormat(e.to_string()))?;
184
185 let mut id_map = HashMap::<EntityId, EntityId>::new();
186
187 for command in recipe.commands {
189 match command {
190 SceneCommand::Spawn { id } => {
191 let new_id = world.spawn(());
192 id_map.insert(id, new_id);
193 }
194 SceneCommand::AddComponent {
195 entity_id,
196 component_type,
197 component_data,
198 } => {
199 if let Some(new_id) = id_map.get(&entity_id) {
201 if let Some(deserializer) = self.deserializers.get(&component_type) {
203 deserializer(world, *new_id, &component_data)?;
204 }
205 }
206 }
207 SceneCommand::SetParent { .. } => {
208 }
210 }
211 }
212
213 Ok(())
214 }
215}