khora_lanes/scene_lane/strategies/
recipe_lane.rs

1// Copyright 2025 eraflo
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Implements the "Recipe" serialization strategy for scenes.
16
17use 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
28// --- Deserialization Registry ---
29
30// A function pointer that takes bytes, deserializes them into a stable representation,
31// converts that into a live component, and adds it to the world.
32type ComponentDeserializer =
33    Box<dyn Fn(&mut World, EntityId, &[u8]) -> Result<(), DeserializationError> + Send + Sync>;
34
35/// A registry to map component type names to their deserialization functions.
36///
37/// This provides a form of reflection, allowing the `Lane` to deserialize different
38/// component types dynamically based on a string name.
39#[derive(Default)]
40struct DeserializerRegistry {
41    map: HashMap<String, ComponentDeserializer>,
42}
43
44impl DeserializerRegistry {
45    /// Creates a new registry and registers all supported component types.
46    fn new() -> Self {
47        let mut registry = Self::default();
48
49        // For each component, we register how to convert its `Serializable` form
50        // back into the live `Component` form.
51        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    /// Registers a component type for deserialization.
62    ///
63    /// # Type Parameters
64    /// * `C`: The live component type (e.g., `khora_data::ecs::Transform`).
65    /// * `S`: The stable, serializable representation (e.g., `SerializableTransform`).
66    ///
67    /// # Arguments
68    /// * `from_serializable`: A function that converts the stable representation into the live one.
69    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    /// Retrieves the deserializer function for a given component type name.
89    fn get(&self, type_name: &str) -> Option<&ComponentDeserializer> {
90        self.map.get(type_name)
91    }
92}
93
94// --- The Lane ---
95
96/// A serialization strategy that uses a sequence of commands (`SceneRecipe`).
97///
98/// This lane is designed for flexibility. The command-based format is a good
99/// foundation for editor tools, scene patching, and potential streaming in the future.
100pub 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    /// Creates a new instance of the `RecipeSerializationLane`.
114    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    // --- SERIALIZATION ---
125    fn serialize(&self, world: &World) -> Result<Vec<u8>, SerializationError> {
126        let mut commands = Vec::new();
127
128        // 1. Collect nodes (all living entities) and edges (Parent->Child relationships).
129        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                // The edge goes from the parent (source) to the child (destination).
134                edges.push((parent.0, entity_id));
135            }
136        }
137
138        // 2. Call our generic topological sort utility from khora-core.
139        let sorted_entities = topological_sort(nodes, edges).map_err(|_| {
140            SerializationError::ProcessingFailed("Cycle detected in scene hierarchy.".to_string())
141        })?;
142
143        // 3. Generate the commands in the guaranteed topological order.
144        for entity_id in sorted_entities {
145            // Spawn command
146            commands.push(SceneCommand::Spawn { id: entity_id });
147
148            // AddComponent commands for each serializable component.
149            if let Some(transform) = world.get::<Transform>(entity_id) {
150                // First, convert the live component to its stable, serializable form.
151                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                    // Then, serialize the stable form.
160                    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    // --- DESERIALIZATION ---
181    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        // Execute each command sequentially.
188        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                    // Use the id_map to find the entity's ID in the new world.
200                    if let Some(new_id) = id_map.get(&entity_id) {
201                        // Use the registry to find the correct deserialization function.
202                        if let Some(deserializer) = self.deserializers.get(&component_type) {
203                            deserializer(world, *new_id, &component_data)?;
204                        }
205                    }
206                }
207                SceneCommand::SetParent { .. } => {
208                    // This variant is not used in our current serialization implementation.
209                }
210            }
211        }
212
213        Ok(())
214    }
215}