using System.Reflection; using Ghost.Entities; namespace Ghost.Editor.Core.SceneGraph.Serialization; /// /// Handles serialization and deserialization of scenes to/from JSON format. /// This is editor-only and uses reflection for flexibility. /// public class SceneSerializer { /// /// Saves a scene to JSON-serializable format. /// Queries all entities with the given sceneId and converts them to SceneAssetData. /// public SceneAssetData SaveScene(SceneGraph sceneGraph, short sceneId, World editorWorld) { var sceneNode = sceneGraph.GetSceneNode(sceneId); if (sceneNode == null) { throw new InvalidOperationException($"Scene {sceneId} not found in scene graph."); } var sceneData = new SceneAssetData { SceneGuid = sceneNode.SceneGuid, Name = sceneNode.Name, SceneId = sceneId, Version = 1 }; // Get all entities in this scene var entitiesInScene = sceneGraph.GetEntitiesInScene(sceneId).ToList(); // Create entity data in order for (int i = 0; i < entitiesInScene.Count; i++) { var entityNode = entitiesInScene[i]; var entityData = new EntityData { FileLocalId = i, Name = entityNode.Name, ParentFileLocalId = entityNode.ParentNode != null ? GetFileLocalId(entitiesInScene, entityNode.ParentNode) : -1, Components = SerializeEntityComponents(editorWorld, entityNode.EntityId) }; sceneData.Entities.Add(entityData); } // Validate for cross-scene references ValidateNoInvalidReferences(sceneData); return sceneData; } /// /// Loads a scene from JSON-serializable format. /// Creates entities in the editor world and sets up all relationships. /// public void LoadScene(SceneGraph sceneGraph, SceneAssetData sceneData, World editorWorld, SceneSerializationContext? context = null) { context ??= new SceneSerializationContext(sceneData.SceneId, editorWorld); // Add scene node to graph var sceneNode = sceneGraph.AddScene(sceneData.Name, sceneData.SceneId, sceneData.SceneGuid); // Create all entities first (without relationships) var createdEntities = new List(); foreach (var entityData in sceneData.Entities) { var entity = editorWorld.CreateEntity(); createdEntities.Add(entity); context.EntityOrder.Add(entity); context.IdRemap.Register(entityData.FileLocalId, entity); // Add SceneID component // TODO: Add SceneID component to entity // Deserialize components DeserializeEntityComponents(editorWorld, entity, entityData.Components); } // Now establish parent-child relationships for (int i = 0; i < sceneData.Entities.Count; i++) { var entityData = sceneData.Entities[i]; var entityNode = sceneGraph.GetEntityNode(createdEntities[i]); if (entityNode == null) { context.AddValidationError($"Entity node for {createdEntities[i]} not found."); continue; } // Add to scene or to parent entity if (entityData.ParentFileLocalId == -1) { // Root entity in scene - should already be added } else if (entityData.ParentFileLocalId < 0 || entityData.ParentFileLocalId >= createdEntities.Count) { context.AddValidationError($"Invalid parent file-local ID {entityData.ParentFileLocalId} for entity {entityData.Name}."); } else { var parentEntity = createdEntities[entityData.ParentFileLocalId]; var parentNode = sceneGraph.GetEntityNode(parentEntity); if (parentNode != null) { sceneGraph.SetEntityParent(createdEntities[i], parentEntity); } } } // Report any validation errors if (context.HasErrors) { // Log or handle errors System.Diagnostics.Debug.WriteLine($"Scene load validation errors:\n{context.GetErrorsSummary()}"); } } /// /// Serializes all components on an entity. /// private List SerializeEntityComponents(World editorWorld, Entity entity) { var components = new List(); // TODO: Query entity components and serialize them // This requires integration with the ECS world return components; } /// /// Deserializes components onto an entity. /// private void DeserializeEntityComponents(World editorWorld, Entity entity, List componentDataList) { // TODO: Deserialize component data and add to entity // This requires integration with the ECS world and reflection } /// /// Validates that no entity references entities in other scenes. /// private void ValidateNoInvalidReferences(SceneAssetData sceneData) { var validFileLocalIds = sceneData.Entities.Select(e => e.FileLocalId).ToHashSet(); foreach (var entity in sceneData.Entities) { // TODO: Check component data for cross-scene entity references } } /// /// Gets the file-local ID for an entity node within a scene. /// private int GetFileLocalId(List entities, EntityNode node) { return entities.IndexOf(node); } }