using System.Collections.ObjectModel;
using Ghost.Entities;
namespace Ghost.Editor.Core.SceneGraph;
///
/// SceneGraph is the editor's view-model over the ECS runtime data.
/// It provides a hierarchical representation of scenes and entities for UI rendering.
///
/// This is editor-only and does not exist at runtime.
///
public class SceneGraph
{
///
/// All scenes currently loaded in the editor world.
///
public ObservableCollection Scenes { get; }
///
/// Reference to the editor world containing ECS data.
///
private readonly World _editorWorld;
///
/// Cache: map from global entity ID to entity node for O(1) lookups.
///
private Dictionary _entityNodeMap;
///
/// Cache: map from scene ID to scene node for O(1) lookups.
///
private Dictionary _sceneNodeMap;
public SceneGraph(World editorWorld)
{
_editorWorld = editorWorld ?? throw new ArgumentNullException(nameof(editorWorld));
Scenes = new ObservableCollection();
_entityNodeMap = new Dictionary();
_sceneNodeMap = new Dictionary();
}
///
/// Adds a scene to the scene graph.
///
public SceneNode AddScene(string name, short sceneId, Guid? sceneGuid = null)
{
if (_sceneNodeMap.ContainsKey(sceneId))
{
throw new InvalidOperationException($"Scene with ID {sceneId} already exists in the graph.");
}
var sceneNode = new SceneNode(name, sceneId, sceneGuid);
Scenes.Add(sceneNode);
_sceneNodeMap[sceneId] = sceneNode;
return sceneNode;
}
///
/// Removes a scene from the scene graph.
///
public bool RemoveScene(short sceneId)
{
if (!_sceneNodeMap.TryGetValue(sceneId, out var sceneNode))
{
return false;
}
// Remove all entity nodes in this scene
foreach (var entityNode in sceneNode.GetAllDescendants())
{
_entityNodeMap.Remove(entityNode.EntityId);
}
Scenes.Remove(sceneNode);
_sceneNodeMap.Remove(sceneId);
return true;
}
///
/// Gets a scene node by its scene ID.
///
public SceneNode? GetSceneNode(short sceneId)
{
_sceneNodeMap.TryGetValue(sceneId, out var sceneNode);
return sceneNode;
}
///
/// Adds an entity node to a scene.
/// If parentEntityId is valid, adds it as a child of that entity.
/// Otherwise, adds it as a root entity in the scene.
///
public EntityNode AddEntity(short sceneId, string name, Entity entityId, Entity parentEntityId = default)
{
var sceneNode = GetSceneNode(sceneId);
if (sceneNode == null)
{
throw new InvalidOperationException($"Scene with ID {sceneId} not found.");
}
var entityNode = new EntityNode(name, entityId);
_entityNodeMap[entityId] = entityNode;
// Add as child of parent or as root in scene
if (parentEntityId.IsValid && _entityNodeMap.TryGetValue(parentEntityId, out var parentNode))
{
parentNode.Children.Add(entityNode);
entityNode.ParentNode = parentNode;
}
else
{
sceneNode.Children.Add(entityNode);
entityNode.ParentNode = null;
}
return entityNode;
}
///
/// Removes an entity node from the graph.
/// Also removes all its children recursively.
///
public bool RemoveEntity(Entity entityId)
{
if (!_entityNodeMap.TryGetValue(entityId, out var entityNode))
{
return false;
}
// Remove all descendants
foreach (var descendant in entityNode.GetAllDescendants())
{
_entityNodeMap.Remove(descendant.EntityId);
}
// Remove from parent or scene
if (entityNode.ParentNode != null)
{
entityNode.ParentNode.Children.Remove(entityNode);
}
else
{
// Find and remove from scene
foreach (var sceneNode in Scenes)
{
if (sceneNode.Children.Contains(entityNode))
{
sceneNode.Children.Remove(entityNode);
break;
}
}
}
_entityNodeMap.Remove(entityId);
return true;
}
///
/// Gets an entity node by its global entity ID.
///
public EntityNode? GetEntityNode(Entity entityId)
{
_entityNodeMap.TryGetValue(entityId, out var entityNode);
return entityNode;
}
///
/// Sets the parent of an entity node.
///
public void SetEntityParent(Entity childEntityId, Entity newParentEntityId)
{
if (!_entityNodeMap.TryGetValue(childEntityId, out var childNode))
{
throw new InvalidOperationException($"Entity {childEntityId} not found in scene graph.");
}
// Remove from current parent/scene
if (childNode.ParentNode != null)
{
childNode.ParentNode.Children.Remove(childNode);
}
else
{
// Find and remove from scene
foreach (var sceneNode in Scenes)
{
if (sceneNode.Children.Contains(childNode))
{
sceneNode.Children.Remove(childNode);
break;
}
}
}
// Add to new parent
if (newParentEntityId.IsValid && _entityNodeMap.TryGetValue(newParentEntityId, out var newParentNode))
{
newParentNode.Children.Add(childNode);
childNode.ParentNode = newParentNode;
}
else
{
throw new InvalidOperationException($"New parent entity {newParentEntityId} not found.");
}
}
///
/// Rebuilds the scene graph from the editor world's ECS data.
/// Queries entities with SceneID and Hierarchy components.
///
public void RebuildFromWorld()
{
Scenes.Clear();
_entityNodeMap.Clear();
_sceneNodeMap.Clear();
// TODO: Query entities with SceneID and Hierarchy components
// For now, this is a placeholder that will be implemented once we have the full integration
}
///
/// Gets all entities in a scene.
///
public IEnumerable GetEntitiesInScene(short sceneId)
{
var sceneNode = GetSceneNode(sceneId);
if (sceneNode == null)
{
return Enumerable.Empty();
}
var allEntities = new List();
foreach (var child in sceneNode.Children)
{
allEntities.Add(child);
allEntities.AddRange(child.GetAllDescendants());
}
return allEntities;
}
}