Skip to main content

khora_data/scene/
definition_strategy.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//! A serialization strategy that uses a stable, intermediate representation.
16//!
17//! Uses inventory-based component registration to handle all component types
18//! automatically. Each component is serialized as a base64-encoded blob keyed
19//! by type name in a human-readable RON structure.
20
21use super::{DeserializationError, SerializationError, SerializationStrategy};
22use crate::ecs::World;
23use crate::scene::registry::ComponentRegistration;
24use khora_core::ecs::entity::EntityId;
25use serde::{Deserialize, Serialize};
26use std::collections::HashMap;
27
28/// A single component in the stable intermediate representation.
29#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct ComponentDefinition {
31    /// The component type name (e.g., "Transform", "Camera").
32    pub type_name: String,
33    /// Base64-encoded binary data (bincode-encoded SerializableX).
34    pub data_base64: String,
35}
36
37/// An entity definition in the stable intermediate representation.
38#[derive(Debug, Clone, Serialize, Deserialize)]
39pub struct EntityDefinition {
40    /// The original entity ID (for reference).
41    pub id: EntityId,
42    /// The components attached to this entity.
43    pub components: Vec<ComponentDefinition>,
44}
45
46/// The full scene definition in stable intermediate representation.
47#[derive(Debug, Clone, Serialize, Deserialize)]
48pub struct SceneDefinition {
49    /// All entities present in the scene, in stable definition form.
50    pub entities: Vec<EntityDefinition>,
51}
52
53/// A serialization strategy that uses a stable, intermediate representation.
54///
55/// Iterates all registered components via inventory to serialize every
56/// component on every entity. The output is human-readable RON with
57/// base64-encoded binary component data.
58#[derive(Default)]
59pub struct DefinitionSerializationStrategy;
60
61impl DefinitionSerializationStrategy {
62    /// Creates a new `DefinitionSerializationStrategy`.
63    pub fn new() -> Self {
64        Self
65    }
66}
67
68impl SerializationStrategy for DefinitionSerializationStrategy {
69    fn get_strategy_id(&self) -> &'static str {
70        "KH_DEFINITION_RON_V1"
71    }
72
73    fn serialize(&self, world: &World) -> Result<Vec<u8>, SerializationError> {
74        let mut entity_defs = Vec::new();
75
76        for entity_id in world.iter_entities() {
77            let mut component_defs = Vec::new();
78
79            // Every author-owned component; engine-written ones are rebuilt on
80            // load, so persisting them would only bake in stale entity ids.
81            for (type_name, data) in crate::scene::serialize_all_components(world, entity_id) {
82                component_defs.push(ComponentDefinition {
83                    type_name,
84                    data_base64: base64_encode(&data),
85                });
86            }
87
88            if !component_defs.is_empty() {
89                entity_defs.push(EntityDefinition {
90                    id: entity_id,
91                    components: component_defs,
92                });
93            }
94        }
95
96        let scene_definition = SceneDefinition {
97            entities: entity_defs,
98        };
99
100        let pretty_config = ron::ser::PrettyConfig::default().indentor("  ".to_string());
101        ron::ser::to_string_pretty(&scene_definition, pretty_config)
102            .map(|s| s.into_bytes())
103            .map_err(|e| SerializationError::ProcessingFailed(e.to_string()))
104    }
105
106    fn deserialize(&self, data: &[u8], world: &mut World) -> Result<(), DeserializationError> {
107        let scene_def: SceneDefinition = ron::de::from_bytes(data)
108            .map_err(|e| DeserializationError::InvalidFormat(e.to_string()))?;
109
110        let mut id_map = HashMap::<EntityId, EntityId>::new();
111
112        // First pass: spawn all entities.
113        for entity_def in &scene_def.entities {
114            let new_id = world.spawn(());
115            id_map.insert(entity_def.id, new_id);
116        }
117
118        // Second pass: add components.
119        for entity_def in &scene_def.entities {
120            let new_id = id_map[&entity_def.id];
121
122            for comp_def in &entity_def.components {
123                let data = base64_decode(&comp_def.data_base64)
124                    .map_err(DeserializationError::InvalidFormat)?;
125
126                // Find the registration by type_name.
127                for reg in inventory::iter::<ComponentRegistration> {
128                    if reg.type_name == comp_def.type_name {
129                        if let Err(e) = (reg.deserialize_recipe)(world, new_id, &data) {
130                            log::warn!("Failed to deserialize {}: {}", comp_def.type_name, e);
131                        }
132                        break;
133                    }
134                }
135            }
136        }
137
138        Ok(())
139    }
140}
141
142// Simple base64 encoding/decoding (no external dependency needed for small blobs)
143fn base64_encode(data: &[u8]) -> String {
144    use std::fmt::Write;
145    const CHARS: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
146    let mut out = String::with_capacity(data.len().div_ceil(3) * 4);
147    let mut chunks = data.chunks_exact(3);
148    for chunk in chunks.by_ref() {
149        let n = ((chunk[0] as u32) << 16) | ((chunk[1] as u32) << 8) | (chunk[2] as u32);
150        write!(
151            out,
152            "{}{}{}{}",
153            CHARS[((n >> 18) & 0x3F) as usize] as char,
154            CHARS[((n >> 12) & 0x3F) as usize] as char,
155            CHARS[((n >> 6) & 0x3F) as usize] as char,
156            CHARS[(n & 0x3F) as usize] as char,
157        )
158        .unwrap();
159    }
160    let rem = chunks.remainder();
161    if rem.len() == 1 {
162        let n = (rem[0] as u32) << 16;
163        write!(
164            out,
165            "{}{}==",
166            CHARS[((n >> 18) & 0x3F) as usize] as char,
167            CHARS[((n >> 12) & 0x3F) as usize] as char,
168        )
169        .unwrap();
170    } else if rem.len() == 2 {
171        let n = ((rem[0] as u32) << 16) | ((rem[1] as u32) << 8);
172        write!(
173            out,
174            "{}{}{}=",
175            CHARS[((n >> 18) & 0x3F) as usize] as char,
176            CHARS[((n >> 12) & 0x3F) as usize] as char,
177            CHARS[((n >> 6) & 0x3F) as usize] as char,
178        )
179        .unwrap();
180    }
181    out
182}
183
184fn base64_decode(input: &str) -> Result<Vec<u8>, String> {
185    let input = input.trim_end_matches('=');
186    let mut out = Vec::with_capacity(input.len() * 3 / 4);
187    let mut buf = 0u32;
188    let mut bits = 0u8;
189    for c in input.chars() {
190        let val = match c {
191            'A'..='Z' => c as u32 - 'A' as u32,
192            'a'..='z' => c as u32 - 'a' as u32 + 26,
193            '0'..='9' => c as u32 - '0' as u32 + 52,
194            '+' => 62,
195            '/' => 63,
196            _ => return Err(format!("Invalid base64 character: {}", c)),
197        };
198        buf = (buf << 6) | val;
199        bits += 6;
200        if bits >= 8 {
201            bits -= 8;
202            out.push((buf >> bits) as u8);
203        }
204    }
205    Ok(out)
206}