using Ghost.Entities; using Ghost.Engine.Components; using System.Reflection; using System.Runtime.InteropServices; namespace Ghost.Editor.Core.Validation; /// /// Validation result for scene integrity checks. /// public class ValidationResult { public bool IsValid => Errors.Count == 0; public List Errors { get; } = []; public List Warnings { get; } = []; public void AddError(string error) => Errors.Add(error); public void AddWarning(string warning) => Warnings.Add(warning); } /// /// Provides validation for scenes, checking for cross-scene references and other integrity issues. /// public class SceneValidator { private readonly World _world; public SceneValidator(World world) { _world = world; } /// /// Validates that all entity references within a scene point to entities in the same scene. /// /// The ID of the scene to validate. /// The list of entities in the scene. /// A validation result with any errors or warnings found. public ValidationResult ValidateSceneReferences(short sceneId, IEnumerable entities) { var result = new ValidationResult(); foreach (var entity in entities) { // Get entity location var location = _world.EntityManager.GetEntityLocation(entity); if (!location.IsSuccess) { result.AddError($"Entity {entity} not found in world."); continue; } ref var archetype = ref _world.ComponentManager.GetArchetypeReference(location.Value.archetypeID); // Check each component for entity references for (int i = 0; i < archetype._layouts.Count; i++) { var layout = archetype._layouts[i]; if (layout.enableBitsOffset == -1) continue; var componentTypeId = layout.componentID; // Get component type if (!ComponentRegistry.s_runtimeIDToType.TryGetValue(componentTypeId.Value, out var componentType)) { continue; } // Get component data var pComponent = _world.EntityManager.GetComponent(entity, componentTypeId); if (pComponent == null) { continue; } // Check fields for entity references ValidateComponentReferences(entity, componentType, pComponent, sceneId, result); } } return result; } /// /// Validates that a scene has no circular hierarchy references. /// /// The list of entities in the scene. /// A validation result with any errors or warnings found. public ValidationResult ValidateHierarchy(IEnumerable entities) { var result = new ValidationResult(); var entitySet = new HashSet(entities); foreach (var entity in entities) { if (!_world.EntityManager.HasComponent(entity)) { continue; } var visited = new HashSet(); var current = entity; // Traverse up the hierarchy while (current.IsValid) { if (visited.Contains(current)) { result.AddError($"Circular hierarchy detected involving entity {entity}"); break; } visited.Add(current); ref var hierarchy = ref _world.EntityManager.GetComponent(current); current = hierarchy.parent; // Check that parent is in the same scene if it exists if (current.IsValid && !entitySet.Contains(current)) { result.AddWarning($"Entity {entity} has parent {current} outside of the scene."); } } } return result; } private unsafe void ValidateComponentReferences( Entity entity, Type componentType, void* pComponent, short sceneId, ValidationResult result) { var componentInstance = Marshal.PtrToStructure((IntPtr)pComponent, componentType); if (componentInstance == null) { return; } var fields = componentType.GetFields(BindingFlags.Public | BindingFlags.Instance); foreach (var field in fields) { if (field.FieldType == typeof(Entity)) { var entityRef = (Entity)field.GetValue(componentInstance)!; if (!entityRef.IsValid) { continue; } // Check if the referenced entity exists if (!_world.EntityManager.Exists(entityRef)) { result.AddError($"Entity {entity} in component {componentType.Name} references invalid entity {entityRef} in field {field.Name}"); continue; } // Check if the referenced entity has a SceneID if (!_world.EntityManager.HasComponent(entityRef)) { result.AddWarning($"Entity {entity} in component {componentType.Name} references entity {entityRef} without a SceneID in field {field.Name}"); continue; } // Check if the referenced entity is in the same scene var refSceneId = _world.EntityManager.GetComponent(entityRef); if (refSceneId.id != sceneId) { result.AddError($"Cross-scene reference detected! Entity {entity} in scene {sceneId} references entity {entityRef} in scene {refSceneId.id} via component {componentType.Name}.{field.Name}"); } } } } }