using Ghost.Engine.Core; using Ghost.Entities; using System.Collections.ObjectModel; namespace Ghost.Editor.Core.SceneGraph; /// /// Manages the editor world and scene graph hierarchy. /// Provides functionality to load/unload scenes and maintain the editor-side scene graph. /// public class EditorWorldManager { private readonly World _editorWorld; private readonly SceneManager _sceneManager; private readonly ObservableCollection _loadedScenes; private readonly Dictionary _sceneIdToNode; private readonly Dictionary _entityToNode; /// /// Gets the editor world instance. /// public World EditorWorld => _editorWorld; /// /// Gets the runtime scene manager. /// public SceneManager SceneManager => _sceneManager; /// /// Gets the collection of loaded scenes in the editor. /// public ReadOnlyObservableCollection LoadedScenes { get; } /// /// Event raised when a scene is loaded. /// public event Action? OnSceneLoaded; /// /// Event raised when a scene is unloaded. /// public event Action? OnSceneUnloaded; /// /// Event raised when an entity node is created. /// public event Action? OnEntityNodeCreated; /// /// Event raised when an entity node is destroyed. /// public event Action? OnEntityNodeDestroyed; public EditorWorldManager(World editorWorld, SceneManager sceneManager) { _editorWorld = editorWorld; _sceneManager = sceneManager; _loadedScenes = []; _sceneIdToNode = []; _entityToNode = []; LoadedScenes = new ReadOnlyObservableCollection(_loadedScenes); } /// /// Creates a new empty scene in the editor. /// /// The name of the scene. /// The created scene node. public SceneNode CreateNewScene(string name = "New Scene") { var runtimeScene = _sceneManager.CreateScene(); var sceneNode = new SceneNode(runtimeScene, name) { IsLoaded = true }; _loadedScenes.Add(sceneNode); _sceneIdToNode[runtimeScene.ID] = sceneNode; OnSceneLoaded?.Invoke(sceneNode); return sceneNode; } /// /// Unloads a scene from the editor. /// /// The scene to unload. public void UnloadScene(SceneNode sceneNode) { if (!_loadedScenes.Contains(sceneNode)) { return; } // Remove all entity nodes from tracking foreach (var entityNode in sceneNode.GetAllEntities().ToList()) { _entityToNode.Remove(entityNode.Entity); OnEntityNodeDestroyed?.Invoke(entityNode); } // Unload runtime scene _sceneManager.UnloadScene(sceneNode.Scene); // Remove from loaded scenes _loadedScenes.Remove(sceneNode); _sceneIdToNode.Remove(sceneNode.Scene.ID); sceneNode.IsLoaded = false; OnSceneUnloaded?.Invoke(sceneNode); } /// /// Creates an entity in the specified scene. /// /// The scene to create the entity in. /// The display name of the entity. /// Optional parent entity node. /// The created entity node. public EntityNode CreateEntity(SceneNode sceneNode, string name = "Entity", EntityNode? parent = null) { // Create runtime entity with SceneID component var entity = _editorWorld.EntityManager.CreateEntity(); _editorWorld.EntityManager.AddComponent(entity, new Components.SceneID { id = sceneNode.Scene.ID }); // Create entity node var entityNode = new EntityNode(entity, name); // Add to scene graph if (parent != null) { parent.AddChild(entityNode); // Add Hierarchy component _editorWorld.EntityManager.AddComponent(entity, new Components.Hierarchy { parent = parent.Entity, firstChild = Entity.Invalid, nextSibling = Entity.Invalid }); } else { sceneNode.AddRootEntity(entityNode); // Add root hierarchy component _editorWorld.EntityManager.AddComponent(entity, Components.Hierarchy.Root); } // Track entity node _entityToNode[entity] = entityNode; OnEntityNodeCreated?.Invoke(entityNode); return entityNode; } /// /// Destroys an entity and its node from the scene. /// /// The entity node to destroy. public void DestroyEntity(EntityNode entityNode) { // Remove from parent or scene root if (entityNode.Parent != null) { entityNode.Parent.RemoveChild(entityNode); } else if (entityNode.OwnerScene != null) { entityNode.OwnerScene.RemoveRootEntity(entityNode); } // Destroy all children recursively var childrenCopy = entityNode.Children.ToList(); foreach (var child in childrenCopy) { DestroyEntity(child); } // Destroy runtime entity _editorWorld.EntityManager.DestroyEntity(entityNode.Entity); // Remove from tracking _entityToNode.Remove(entityNode.Entity); OnEntityNodeDestroyed?.Invoke(entityNode); } /// /// Gets the scene node for a runtime scene ID. /// /// The scene ID. /// The scene node if found, null otherwise. public SceneNode? GetSceneNode(short sceneId) { _sceneIdToNode.TryGetValue(sceneId, out var sceneNode); return sceneNode; } /// /// Gets the entity node for a runtime entity. /// /// The entity. /// The entity node if found, null otherwise. public EntityNode? GetEntityNode(Entity entity) { _entityToNode.TryGetValue(entity, out var entityNode); return entityNode; } /// /// Rebuilds the scene graph from the current world state. /// Useful after loading a scene from disk. /// /// The scene node to rebuild. public void RebuildSceneGraph(SceneNode sceneNode) { // Clear existing nodes sceneNode.RootEntities.Clear(); // Build query for entities in this scene var builder = new QueryBuilder(); builder.WithAll([ComponentTypeID.Value, ComponentTypeID.Value]); var queryID = builder.Build(_editorWorld); ref var query = ref _editorWorld.ComponentManager.GetEntityQueryReference(queryID); // First pass: Create all entity nodes var entityNodes = new Dictionary(); foreach (var chunk in query.GetChunkIterator()) { var entities = chunk.GetEntities(); var sceneIDs = chunk.GetComponentData(); for (int i = 0; i < chunk.Count; i++) { if (sceneIDs[i].id == sceneNode.Scene.ID) { var entity = entities[i]; // Try to get existing node name or use default var name = _entityToNode.TryGetValue(entity, out var existing) ? existing.Name : "Entity"; var entityNode = new EntityNode(entity, name); entityNodes[entity] = entityNode; _entityToNode[entity] = entityNode; } } } // Second pass: Build hierarchy foreach (var chunk in query.GetChunkIterator()) { var entities = chunk.GetEntities(); var sceneIDs = chunk.GetComponentData(); var hierarchies = chunk.GetComponentData(); for (int i = 0; i < chunk.Count; i++) { if (sceneIDs[i].id == sceneNode.Scene.ID) { var entity = entities[i]; var hierarchy = hierarchies[i]; var entityNode = entityNodes[entity]; if (hierarchy.parent.IsValid && entityNodes.TryGetValue(hierarchy.parent, out var parentNode)) { parentNode.AddChild(entityNode); } else { sceneNode.AddRootEntity(entityNode); } } } } } }