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::lane::Lane;
22use khora_core::{ecs::entity::EntityId, graph::topological_sort};
23use khora_data::{
24    ecs::{Component, Parent, SerializableParent, SerializableTransform, Transform, World},
25    scene::{SceneCommand, SceneRecipe},
26};
27use std::{any, collections::HashMap};
28
29// --- Deserialization Registry ---
30
31// A function pointer that takes bytes, deserializes them into a stable representation,
32// converts that into a live component, and adds it to the world.
33type ComponentDeserializer =
34    Box<dyn Fn(&mut World, EntityId, &[u8]) -> Result<(), DeserializationError> + Send + Sync>;
35
36/// A registry to map component type names to their deserialization functions.
37///
38/// This provides a form of reflection, allowing the `Lane` to deserialize different
39/// component types dynamically based on a string name.
40#[derive(Default)]
41struct DeserializerRegistry {
42    map: HashMap<String, ComponentDeserializer>,
43}
44
45impl DeserializerRegistry {
46    /// Creates a new registry and registers all supported component types.
47    fn new() -> Self {
48        let mut registry = Self::default();
49
50        // For each component, we register how to convert its `Serializable` form
51        // back into the live `Component` form.
52        registry.register::<Transform, SerializableTransform>(|st| Transform {
53            translation: st.translation,
54            rotation: st.rotation,
55            scale: st.scale,
56        });
57        registry.register::<Parent, SerializableParent>(|sp| Parent(sp.0));
58
59        registry
60    }
61
62    /// Registers a component type for deserialization.
63    ///
64    /// # Type Parameters
65    /// * `C`: The live component type (e.g., `khora_data::ecs::Transform`).
66    /// * `S`: The stable, serializable representation (e.g., `SerializableTransform`).
67    ///
68    /// # Arguments
69    /// * `from_serializable`: A function that converts the stable representation into the live one.
70    fn register<C, S>(&mut self, from_serializable: fn(S) -> C)
71    where
72        C: Component,
73        S: Decode<()> + 'static,
74    {
75        let type_name = any::type_name::<C>().to_string();
76        self.map.insert(
77            type_name,
78            Box::new(move |world, entity_id, data| {
79                let (serializable_component, _): (S, _) =
80                    bincode::decode_from_slice_with_context(data, config::standard(), ())
81                        .map_err(|e| DeserializationError::InvalidFormat(e.to_string()))?;
82                let live_component = from_serializable(serializable_component);
83                world.add_component(entity_id, live_component).ok();
84                Ok(())
85            }),
86        );
87    }
88
89    /// Retrieves the deserializer function for a given component type name.
90    fn get(&self, type_name: &str) -> Option<&ComponentDeserializer> {
91        self.map.get(type_name)
92    }
93}
94
95// --- The Lane ---
96
97/// A serialization strategy that uses a sequence of commands (`SceneRecipe`).
98///
99/// This lane is designed for flexibility. The command-based format is a good
100/// foundation for editor tools, scene patching, and potential streaming in the future.
101pub struct RecipeSerializationLane {
102    deserializers: DeserializerRegistry,
103}
104
105impl Default for RecipeSerializationLane {
106    fn default() -> Self {
107        Self {
108            deserializers: DeserializerRegistry::new(),
109        }
110    }
111}
112
113impl RecipeSerializationLane {
114    /// Creates a new instance of the `RecipeSerializationLane`.
115    pub fn new() -> Self {
116        Self::default()
117    }
118}
119
120impl khora_core::lane::Lane for RecipeSerializationLane {
121    fn strategy_name(&self) -> &'static str {
122        "KH_RECIPE_V1"
123    }
124
125    fn lane_kind(&self) -> khora_core::lane::LaneKind {
126        khora_core::lane::LaneKind::Scene
127    }
128
129    fn as_any(&self) -> &dyn std::any::Any {
130        self
131    }
132
133    fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
134        self
135    }
136}
137
138impl SerializationStrategy for RecipeSerializationLane {
139    fn get_strategy_id(&self) -> &'static str {
140        self.strategy_name()
141    }
142
143    // --- SERIALIZATION ---
144    fn serialize(&self, world: &World) -> Result<Vec<u8>, SerializationError> {
145        let mut commands = Vec::new();
146
147        // 1. Collect nodes (all living entities) and edges (Parent->Child relationships).
148        let nodes: Vec<EntityId> = world.iter_entities().collect();
149        let mut edges: Vec<(EntityId, EntityId)> = Vec::new();
150        for &entity_id in &nodes {
151            if let Some(parent) = world.get::<Parent>(entity_id) {
152                // The edge goes from the parent (source) to the child (destination).
153                edges.push((parent.0, entity_id));
154            }
155        }
156
157        // 2. Call our generic topological sort utility from khora-core.
158        let sorted_entities = topological_sort(nodes, edges).map_err(|_| {
159            SerializationError::ProcessingFailed("Cycle detected in scene hierarchy.".to_string())
160        })?;
161
162        // 3. Generate the commands in the guaranteed topological order.
163        for entity_id in sorted_entities {
164            // Spawn command
165            commands.push(SceneCommand::Spawn { id: entity_id });
166
167            // AddComponent commands for each serializable component.
168            if let Some(transform) = world.get::<Transform>(entity_id) {
169                // First, convert the live component to its stable, serializable form.
170                let serializable = SerializableTransform {
171                    translation: transform.translation,
172                    rotation: transform.rotation,
173                    scale: transform.scale,
174                };
175                commands.push(SceneCommand::AddComponent {
176                    entity_id,
177                    component_type: any::type_name::<Transform>().to_string(),
178                    // Then, serialize the stable form.
179                    component_data: bincode::encode_to_vec(serializable, config::standard())
180                        .unwrap(),
181                });
182            }
183            if let Some(parent) = world.get::<Parent>(entity_id) {
184                let serializable = SerializableParent(parent.0);
185                commands.push(SceneCommand::AddComponent {
186                    entity_id,
187                    component_type: any::type_name::<Parent>().to_string(),
188                    component_data: bincode::encode_to_vec(serializable, config::standard())
189                        .unwrap(),
190                });
191            }
192        }
193
194        let scene_recipe = SceneRecipe { commands };
195        bincode::encode_to_vec(&scene_recipe, config::standard())
196            .map_err(|e| SerializationError::ProcessingFailed(e.to_string()))
197    }
198
199    // --- DESERIALIZATION ---
200    fn deserialize(&self, data: &[u8], world: &mut World) -> Result<(), DeserializationError> {
201        let (recipe, _): (SceneRecipe, _) = bincode::decode_from_slice(data, config::standard())
202            .map_err(|e| DeserializationError::InvalidFormat(e.to_string()))?;
203
204        let mut id_map = HashMap::<EntityId, EntityId>::new();
205
206        // Execute each command sequentially.
207        for command in recipe.commands {
208            match command {
209                SceneCommand::Spawn { id } => {
210                    let new_id = world.spawn(());
211                    id_map.insert(id, new_id);
212                }
213                SceneCommand::AddComponent {
214                    entity_id,
215                    component_type,
216                    component_data,
217                } => {
218                    // Use the id_map to find the entity's ID in the new world.
219                    if let Some(new_id) = id_map.get(&entity_id) {
220                        // Use the registry to find the correct deserialization function.
221                        if let Some(deserializer) = self.deserializers.get(&component_type) {
222                            deserializer(world, *new_id, &component_data)?;
223                        }
224                    }
225                }
226                SceneCommand::SetParent { .. } => {
227                    // This variant is not used in our current serialization implementation.
228                }
229            }
230        }
231
232        Ok(())
233    }
234}