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);
}
}
}
}
}
}