gizmo-scene 0.1.7

A custom ECS and physics engine aimed for realistic simulations.
Documentation
//! SceneSnapshot — Play/Stop Modu için in-memory sahne yedeği
//!
//! Editörde "Play" butonuna basıldığında tüm sahne durumu (entity'ler, fizik
//! bileşenleri, transform'lar) belleğe snapshot olarak alınır. "Stop"
//! butonuna basıldığında bu snapshot'tan geri yükleme yapılır.
//!
//! Avantajlar:
//! - Disk I/O yok → anlık snapshot/restore (~mikrosaniye)
//! - Fizik state korunur (velocity, angular_velocity, sleep state)
//! - Serileştirme gerektirmeyen bileşenler de yedeklenir

use gizmo_core::World;

#[cfg(target_arch = "wasm32")]
use web_time::Instant;
#[cfg(not(target_arch = "wasm32"))]
use std::time::Instant;

/// Tek bir entity'nin in-memory yedeği
#[derive(Clone)]
pub struct EntitySnapshot {
    pub entity_id: u32,
    pub name: Option<String>,
    pub components: std::collections::BTreeMap<String, String>,
}

/// Tüm sahnenin in-memory snapshot'ı
#[derive(Clone)]
pub struct SceneSnapshot {
    pub entities: Vec<EntitySnapshot>,
    pub timestamp: Instant,
}

impl SceneSnapshot {
    /// Mevcut World durumunu in-memory snapshot olarak yakalar.
    /// `protected_ids`: Editor kamerası, highlight box gibi korunacak entity'ler
    pub fn capture(
        world: &World,
        registry: &crate::registry::SceneRegistry,
        protected_ids: &std::collections::HashSet<u32>,
    ) -> Self {
        let mut entities = Vec::new();
        let names = world.borrow::<gizmo_core::EntityName>();

        for ent in world.iter_alive_entities() {
            let id = ent.id();
            if protected_ids.contains(&id) {
                continue;
            }

            // Editor internal entity'lerini atla
            if let Some(name) = names.get(id) {
                if name.0.starts_with("Editor ") || name.0 == "Highlight Box" {
                    continue;
                }
            }

            let name = names.get(id).map(|n| n.0.clone());

            // Registry üzerinden tüm dinamik bileşenleri RON AST'sine dönüştür
            let mut components = std::collections::BTreeMap::new();
            
            let entity = gizmo_core::entity::Entity::new(id, 0);
            let types = world.get_entity_component_types(entity);
            for type_id in types {
                if let Some(reg) = registry.get_registration(type_id) {
                    if let (Some(ptr), Some(get_reflect_ptr)) = (world.get_component_ptr(entity, type_id), reg.get_reflect_ptr_fn) {
                        let reflect_ptr = get_reflect_ptr(ptr);
                        let reflect_val = unsafe { &*reflect_ptr };
                        
                        let type_reg = registry.reflect_registry.get(type_id);
                        if let Some(_type_registration) = type_reg {
                            let serializer = bevy_reflect::serde::TypedReflectSerializer::new(reflect_val, &registry.reflect_registry);
                            if let Ok(string_repr) = ron::ser::to_string(&serializer) {
                                components.insert(reg.name.clone(), string_repr);
                            }
                        } else if let Some(ser_fn) = reg.serialize_fn {
                            if let Ok(string_repr) = ser_fn(ptr) {
                                components.insert(reg.name.clone(), string_repr);
                            }
                        }
                    }
                }
            }

            // Yalnızca bir bileşeni olan entity'leri kaydet
            if name.is_some() || !components.is_empty() {
                entities.push(EntitySnapshot {
                    entity_id: id,
                    name,
                    components,
                });
            }
        }

        Self {
            entities,
            timestamp: Instant::now(),
        }
    }

    /// Snapshot'tan geri yükleme yapar. Mevcut entity'leri siler (korunanlar hariç)
    /// ve snapshot'taki entity'leri yeniden oluşturur.
    ///
    /// **Not:** Mesh ve Material gibi GPU kaynaklarını restore etmek için
    /// tam sahne yüklemesi yapılmalıdır. Bu fonksiyon yalnızca fizik/transform
    /// bileşenlerini geri yükler.
    pub fn restore(
        &self,
        world: &mut World,
        registry: &crate::registry::SceneRegistry,
        protected_ids: &std::collections::HashSet<u32>,
    ) -> RestoreResult {
        let start = Instant::now();
        let mut restored_count = 0u32;
        let mut despawned_count = 0u32;

        let mut snap_ids = std::collections::HashSet::new();
        for snap_ent in &self.entities {
            snap_ids.insert(snap_ent.entity_id);
        }

        // 1. Korunmayan ve snapshot'ta OLMAYAN (Play sırasında oluşturulmuş) entity'leri sil
        let alive = world.iter_alive_entities();
        let mut to_despawn = Vec::new();

        {
            let names = world.borrow::<gizmo_core::EntityName>();
            for ent in &alive {
                let id = ent.id();
                if protected_ids.contains(&id) {
                    continue;
                }
                if let Some(name) = names.get(id) {
                    if name.0.starts_with("Editor ") || name.0 == "Highlight Box" {
                        continue;
                    }
                }
                
                if !snap_ids.contains(&id) {
                    to_despawn.push(*ent);
                }
            }
        }

        for ent in to_despawn {
            world.despawn(ent);
            despawned_count += 1;
        }

        // 2. Snapshot'taki entity'lerin bileşenlerini mevcutların üzerine yaz
        for snap_entity in &self.entities {
            // Eğer entity silinmişse yeni bir tane oluştur (Not: Mesh gibi GPU verileri kaybolur, ideal değil ama çökmez)
            let ent = if let Some(e) = world.get_entity(snap_entity.entity_id) {
                if world.is_alive(e) {
                    e
                } else {
                    world.spawn()
                }
            } else {
                world.spawn()
            };

            if let Some(ref name) = snap_entity.name {
                world.add_component(ent, gizmo_core::EntityName::new(name));
            }

            for (comp_name, comp_val) in &snap_entity.components {
                if let Some(type_id) = registry.get_type_id(comp_name) {
                    if let Some(reg) = registry.get_registration(type_id) {
                        if let Some(type_reg) = registry.reflect_registry.get(type_id) {
                            let deserializer = bevy_reflect::serde::TypedReflectDeserializer::new(type_reg, &registry.reflect_registry);
                            if let Ok(mut de) = ron::de::Deserializer::from_str(comp_val) {
                                if let Ok(reflect_val) = serde::de::DeserializeSeed::deserialize(deserializer, &mut de) {
                                    if let Some(insert_fn) = reg.insert_reflect_fn {
                                        let _ = insert_fn(world, ent, &*reflect_val);
                                    }
                                }
                            }
                        } else if let Some(deserialize_fn) = reg.deserialize_fn {
                            let _ = deserialize_fn(world, ent, comp_val);
                        }
                    }
                }
            }

            restored_count += 1;
        }

        RestoreResult {
            despawned: despawned_count,
            restored: restored_count,
            duration: start.elapsed(),
        }
    }

    /// Snapshot'taki entity sayısı
    pub fn entity_count(&self) -> usize {
        self.entities.len()
    }

    /// Snapshot'ın alındığı zamandan bu yana geçen süre
    pub fn age(&self) -> std::time::Duration {
        self.timestamp.elapsed()
    }
}

/// Geri yükleme sonucu istatistikleri
#[derive(Debug, Clone)]
pub struct RestoreResult {
    pub despawned: u32,
    pub restored: u32,
    pub duration: std::time::Duration,
}

impl std::fmt::Display for RestoreResult {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "Restore: {} entity silindi, {} entity geri yüklendi ({:.2}ms)",
            self.despawned,
            self.restored,
            self.duration.as_secs_f64() * 1000.0,
        )
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_scene_snapshot_capture_empty_world() {
        let world = World::new();
        let registry = crate::registry::SceneRegistry::new();
        let protected = std::collections::HashSet::new();

        let snapshot = SceneSnapshot::capture(&world, &registry, &protected);
        assert_eq!(snapshot.entity_count(), 0);
    }

    #[test]
    fn test_scene_snapshot_round_trip() {
        let mut world = World::new();
        let registry = crate::registry::default_scene_registry();
        let protected = std::collections::HashSet::new();

        // Entity oluştur
        let ent = world.spawn();
        world.add_component(ent, gizmo_core::EntityName::new("TestCube"));
        world.add_component(
            ent,
            gizmo_physics_core::Transform::new(gizmo_math::Vec3::new(1.0, 2.0, 3.0)),
        );

        // Snapshot al
        let snapshot = SceneSnapshot::capture(&world, &registry, &protected);
        assert_eq!(snapshot.entity_count(), 1);
        assert_eq!(snapshot.entities[0].name.as_deref(), Some("TestCube"));
        assert!(snapshot.entities[0].components.contains_key("Transform"));

        // Entity'yi sil
        world.despawn(ent);
        assert_eq!(world.iter_alive_entities().len(), 0);

        // Restore et
        let result = snapshot.restore(&mut world, &registry, &protected);
        assert_eq!(result.restored, 1);

        // İsmi kontrol et
        let names = world.borrow::<gizmo_core::EntityName>();
        let alive = world.iter_alive_entities();
        assert_eq!(alive.len(), 1);
        let restored_name = names.get(alive[0].id());
        assert!(restored_name.is_some());
        assert_eq!(restored_name.unwrap().0, "TestCube");
    }
}