Files
GhostEngine/Ghost.Editor.Core/SceneGraph/WorldNode.cs
Misaki 00b4e82ded ECS refactor: new ComponentSet, serialization, generators
Major ECS API overhaul: added ComponentSet, refactored ComponentRegistry, and updated all entity/component creation methods. Introduced robust custom serialization infrastructure and per-component source generators for registration and (de)serialization. Updated editor, engine, and test code to use new APIs. Improved code quality, naming, and performance throughout. Removed obsolete code and updated dependencies.
2025-12-20 20:41:40 +09:00

182 lines
4.4 KiB
C#

using Ghost.Editor.Core.AssetHandle;
using Ghost.Editor.Core.Inspector;
using Ghost.Editor.Core.Resources;
using Ghost.Editor.Core.Serializer;
using Ghost.Engine.Components;
using Ghost.Engine.IO;
using Ghost.Entities;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
namespace Ghost.Editor.Core.SceneGraph;
[CustomSerializer(typeof(WorldNodeSerializer))]
public partial class WorldNode : SceneGraphNode, IEquatable<WorldNode>
{
private World _world;
private Dictionary<Entity, EntityNode> _entityNodeLookup = new();
public World World => _world;
public Dictionary<Entity, EntityNode> EntityNodeLookup => _entityNodeLookup;
public override SceneGraphNodeType NodeType => SceneGraphNodeType.Scene;
public WorldNode(World world, string name)
{
_world = world;
Name = name;
}
private void UpdateLookup(Entity key, EntityNode value)
{
_entityNodeLookup[key] = value;
if (value.Children == null)
{
return;
}
foreach (var child in value.Children)
{
if (child is EntityNode entityChild)
{
UpdateLookup(entityChild.Entity, entityChild);
}
}
}
public override void AddChild(SceneGraphNode child)
{
if (child is not EntityNode entityNode)
{
throw new ArgumentException("Child must be of type EntityNode.", nameof(child));
}
base.AddChild(entityNode);
UpdateLookup(entityNode.Entity, entityNode);
}
public override bool RemoveChild(SceneGraphNode child)
{
if (child is not EntityNode entityNode)
{
throw new ArgumentException("Child must be of type EntityNode.", nameof(child));
}
var result = base.RemoveChild(child);
if (result)
{
_entityNodeLookup.Remove(entityNode.Entity);
}
return result;
}
private EntityNode BuildNodeRecursive(Entity entity)
{
if (!_entityNodeLookup.TryGetValue(entity, out var node))
{
node = new EntityNode(this, entity, "New Entity");
_entityNodeLookup[entity] = node;
}
var hc = _world.EntityManager.GetComponent<Hierarchy>(entity);
var child = hc.firstChild;
while (child != Entity.Invalid)
{
node.AddChild(BuildNodeRecursive(child));
var childHC = _world.EntityManager.GetComponent<Hierarchy>(child);
child = childHC.nextSibling;
}
return node;
}
private void BuildGraph()
{
var queryID = new QueryBuilder()
.WithAll<Hierarchy>()
.Build(_world);
_world.GetEntityQueryReference(queryID).ForEach<Hierarchy>((entity, ref hierarchy) =>
{
if (hierarchy.parent == Entity.Invalid)
{
var node = BuildNodeRecursive(entity);
AddChild(node);
}
});
}
public Task LoadAsync()
{
return Task.Run(BuildGraph);
}
public void Unload()
{
_world = null!;
Children?.Clear();
_entityNodeLookup.Clear();
}
public override string ToString()
{
return $"WorldNode: {Name} (World ID: {_world.ID})";
}
public override int GetHashCode()
{
return HashCode.Combine(_world, Name);
}
public override bool Equals(object? obj)
{
return obj is WorldNode other && Equals(other);
}
public bool Equals(WorldNode? other)
{
if (other is null)
{
return false;
}
if (ReferenceEquals(this, other))
{
return true;
}
return _world.Equals(other._world) && Name == other.Name;
}
public static bool operator ==(WorldNode? left, WorldNode? right)
{
if (left is null)
{
return right is null;
}
return left.Equals(right);
}
public static bool operator !=(WorldNode? left, WorldNode? right)
{
return !(left == right);
}
}
public partial class WorldNode : IInspectable
{
public IconSource? Icon => EditorIconSource.scene_24;
[AssetOpenHandler(FileExtensions.SCENE_FILE_EXTENSION)]
public static async void Open(string path)
{
await EditorWorldManager.LoadWorld(path);
}
public UIElement? HeaderContent => null;
public UIElement? InspectorContent => null;
}