forked from Misaki/GhostEngine
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.
This commit is contained in:
@@ -15,15 +15,15 @@ public enum OpenWorldMode
|
||||
public static class EditorWorldManager
|
||||
{
|
||||
// TODO: Use guid keys instead of string paths for better performance and uniqueness
|
||||
private static readonly Dictionary<string, WorldNode> _loadedWorlds = new();
|
||||
public static IEnumerable<WorldNode> LoadedWorlds => _loadedWorlds.Values;
|
||||
private static readonly Dictionary<string, WorldNode> s_loadedWorlds = new();
|
||||
public static IEnumerable<WorldNode> LoadedWorlds => s_loadedWorlds.Values;
|
||||
|
||||
public static event Action<WorldNode>? OnWorldLoaded;
|
||||
public static event Action<WorldNode>? OnWorldUnloaded;
|
||||
|
||||
public static async Task LoadWorld(string worldPath)
|
||||
{
|
||||
if (_loadedWorlds.ContainsKey(worldPath)
|
||||
if (s_loadedWorlds.ContainsKey(worldPath)
|
||||
|| !File.Exists(worldPath)
|
||||
|| Path.GetExtension(worldPath) != FileExtensions.SCENE_FILE_EXTENSION)
|
||||
{
|
||||
@@ -33,18 +33,18 @@ public static class EditorWorldManager
|
||||
var progressService = EditorApplication.GetService<IProgressService>();
|
||||
progressService.ShowIndeterminateProgress("Loading world...");
|
||||
|
||||
foreach (var world in _loadedWorlds)
|
||||
foreach (var world in s_loadedWorlds)
|
||||
{
|
||||
world.Value.Unload();
|
||||
OnWorldUnloaded?.Invoke(world.Value);
|
||||
}
|
||||
|
||||
await using var readStream = new FileStream(worldPath, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
var deserializedScene = await JsonSerializer.DeserializeAsync<WorldNode>(readStream, Engine.Resources.StaticResource.defaultSerializerOptions) ?? throw new Exception("Deserialization failed.");
|
||||
var deserializedScene = await JsonSerializer.DeserializeAsync<WorldNode>(readStream, Engine.Resources.EngineResource.defaultSerializerOptions) ?? throw new Exception("Deserialization failed.");
|
||||
|
||||
_loadedWorlds.Clear();
|
||||
s_loadedWorlds.Clear();
|
||||
|
||||
_loadedWorlds[worldPath] = deserializedScene;
|
||||
s_loadedWorlds[worldPath] = deserializedScene;
|
||||
await deserializedScene.LoadAsync();
|
||||
|
||||
progressService.HideProgress();
|
||||
|
||||
@@ -2,7 +2,7 @@ using Ghost.Editor.Core.Controls.Internal;
|
||||
using Ghost.Editor.Core.Inspector;
|
||||
using Ghost.Editor.Core.Resources;
|
||||
using Ghost.Engine.Editor;
|
||||
using Ghost.SparseEntities;
|
||||
using Ghost.Entities;
|
||||
using Microsoft.UI.Text;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
@@ -76,31 +76,46 @@ public partial class EntityNode : IInspectable
|
||||
}
|
||||
}
|
||||
|
||||
public UIElement? InspectorContent
|
||||
public unsafe UIElement? InspectorContent
|
||||
{
|
||||
get
|
||||
{
|
||||
var r = Owner.World.EntityManager.GetEntityLocation(Entity);
|
||||
if (!r)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var root = new StackPanel()
|
||||
{
|
||||
HorizontalAlignment = HorizontalAlignment.Stretch,
|
||||
VerticalAlignment = VerticalAlignment.Top
|
||||
};
|
||||
|
||||
foreach (var (typeHandle, componentPtr) in Owner.World.EntityManager.GetComponentsUnsafe(Entity))
|
||||
var location = r.Value;
|
||||
ref var archetype = ref Owner.World.GetArchetypeReference(location.archetypeID);
|
||||
|
||||
var it = archetype._signature.GetIterator();
|
||||
while (it.Next(out var typeID))
|
||||
{
|
||||
if (componentPtr == IntPtr.Zero)
|
||||
var pComponent = archetype.GetComponentData(location.chunkIndex, location.rowIndex, typeID);
|
||||
if (pComponent == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var type = typeHandle.ToType();
|
||||
if (type == null || type.GetCustomAttribute<HideEditorAttribute>() != null)
|
||||
if (!ComponentRegistry.s_runtimeIDToType.TryGetValue(typeID, out var t))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var dataView = new ComponentDataView(type.Name, Owner.World, Entity, type);
|
||||
root.Children.Add(dataView);
|
||||
if (t.GetCustomAttribute<HideEditorAttribute>() != null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var componentView = new ComponentView(t.Name, Owner.World, Entity, t);
|
||||
root.Children.Add(componentView);
|
||||
}
|
||||
|
||||
return root;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using Ghost.Engine.Components;
|
||||
using Ghost.SparseEntities;
|
||||
using Ghost.Entities;
|
||||
|
||||
namespace Ghost.Editor.Core.SceneGraph;
|
||||
|
||||
@@ -37,23 +37,23 @@ public class SceneGraphHelpers
|
||||
{
|
||||
// 1) If the child already has a parent, detach it first
|
||||
var childHierarchy = scene.World.EntityManager.GetComponent<Hierarchy>(childNode.Entity);
|
||||
if (childHierarchy.ValueRO.parent != Entity.Invalid)
|
||||
if (childHierarchy.parent != Entity.Invalid)
|
||||
{
|
||||
DetachFromParent(scene, childNode);
|
||||
}
|
||||
|
||||
// 2) Link child to new parent
|
||||
childHierarchy.ValueRW.parent = parentNode.Entity;
|
||||
childHierarchy.parent = parentNode.Entity;
|
||||
|
||||
// 3) Insert child at the head of parent's child list
|
||||
var parentHierarchy = scene.World.EntityManager.GetComponent<Hierarchy>(parentNode.Entity);
|
||||
|
||||
childHierarchy.ValueRW.nextSibling = parentHierarchy.ValueRO.firstChild;
|
||||
parentHierarchy.ValueRW.firstChild = childNode.Entity;
|
||||
childHierarchy.nextSibling = parentHierarchy.firstChild;
|
||||
parentHierarchy.firstChild = childNode.Entity;
|
||||
|
||||
// 4) Write back
|
||||
scene.World.EntityManager.SetComponent(parentNode.Entity, in parentHierarchy.ValueRO);
|
||||
scene.World.EntityManager.SetComponent(childNode.Entity, in childHierarchy.ValueRO);
|
||||
scene.World.EntityManager.SetComponent(parentNode.Entity, parentHierarchy);
|
||||
scene.World.EntityManager.SetComponent(childNode.Entity, childHierarchy);
|
||||
|
||||
// 5) Update children list in parent node
|
||||
parentNode.AddChild(childNode);
|
||||
@@ -67,7 +67,7 @@ public class SceneGraphHelpers
|
||||
public static void DetachFromParent(WorldNode scene, EntityNode node)
|
||||
{
|
||||
var hierarchy = scene.World.EntityManager.GetComponent<Hierarchy>(node.Entity);
|
||||
var parent = hierarchy.ValueRO.parent;
|
||||
var parent = hierarchy.parent;
|
||||
if (parent == Entity.Invalid)
|
||||
{
|
||||
return; // already root
|
||||
@@ -76,35 +76,35 @@ public class SceneGraphHelpers
|
||||
var parentHierarchy = scene.World.EntityManager.GetComponent<Hierarchy>(parent);
|
||||
|
||||
// If entity is the first child, simply move head
|
||||
if (parentHierarchy.ValueRO.firstChild == node.Entity)
|
||||
if (parentHierarchy.firstChild == node.Entity)
|
||||
{
|
||||
parentHierarchy.ValueRW.firstChild = hierarchy.ValueRO.nextSibling;
|
||||
parentHierarchy.firstChild = hierarchy.nextSibling;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Otherwise, find the previous sibling in the linked list
|
||||
var prevSibling = parentHierarchy.ValueRO.firstChild;
|
||||
var prevSibling = parentHierarchy.firstChild;
|
||||
while (prevSibling != Entity.Invalid)
|
||||
{
|
||||
var prevHierarchy = scene.World.EntityManager.GetComponent<Hierarchy>(prevSibling);
|
||||
if (prevHierarchy.ValueRW.nextSibling == node.Entity)
|
||||
if (prevHierarchy.nextSibling == node.Entity)
|
||||
{
|
||||
prevHierarchy.ValueRW.nextSibling = hierarchy.ValueRO.nextSibling;
|
||||
scene.World.EntityManager.SetComponent(prevSibling, in prevHierarchy.ValueRO);
|
||||
prevHierarchy.nextSibling = hierarchy.nextSibling;
|
||||
scene.World.EntityManager.SetComponent(prevSibling, prevHierarchy);
|
||||
break;
|
||||
}
|
||||
|
||||
prevSibling = prevHierarchy.ValueRO.nextSibling;
|
||||
prevSibling = prevHierarchy.nextSibling;
|
||||
}
|
||||
}
|
||||
|
||||
// Clear child's references
|
||||
hierarchy.ValueRW.parent = Entity.Invalid;
|
||||
hierarchy.ValueRW.nextSibling = Entity.Invalid;
|
||||
hierarchy.parent = Entity.Invalid;
|
||||
hierarchy.nextSibling = Entity.Invalid;
|
||||
|
||||
// Write back
|
||||
scene.World.EntityManager.SetComponent(parent, in parentHierarchy.ValueRO);
|
||||
scene.World.EntityManager.SetComponent(node.Entity, in hierarchy.ValueRO);
|
||||
scene.World.EntityManager.SetComponent(parent, parentHierarchy);
|
||||
scene.World.EntityManager.SetComponent(node.Entity, hierarchy);
|
||||
|
||||
// Remove from parent's children list
|
||||
scene.EntityNodeLookup[parent].RemoveChild(node);
|
||||
|
||||
@@ -3,14 +3,14 @@ using Ghost.Editor.Core.Inspector;
|
||||
using Ghost.Editor.Core.Resources;
|
||||
using Ghost.Editor.Core.Serializer;
|
||||
using Ghost.Engine.Components;
|
||||
using Ghost.SparseEntities;
|
||||
using Ghost.Engine.IO;
|
||||
using Ghost.Entities;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ghost.Editor.Core.SceneGraph;
|
||||
|
||||
[JsonConverter(typeof(WorldNodeSerializer))]
|
||||
[CustomSerializer(typeof(WorldNodeSerializer))]
|
||||
public partial class WorldNode : SceneGraphNode, IEquatable<WorldNode>
|
||||
{
|
||||
private World _world;
|
||||
@@ -27,11 +27,6 @@ public partial class WorldNode : SceneGraphNode, IEquatable<WorldNode>
|
||||
Name = name;
|
||||
}
|
||||
|
||||
internal WorldNode()
|
||||
{
|
||||
_world = World.Create();
|
||||
}
|
||||
|
||||
private void UpdateLookup(Entity key, EntityNode value)
|
||||
{
|
||||
_entityNodeLookup[key] = value;
|
||||
@@ -85,13 +80,13 @@ public partial class WorldNode : SceneGraphNode, IEquatable<WorldNode>
|
||||
}
|
||||
|
||||
var hc = _world.EntityManager.GetComponent<Hierarchy>(entity);
|
||||
var child = hc.ValueRO.firstChild;
|
||||
var child = hc.firstChild;
|
||||
|
||||
while (child != Entity.Invalid)
|
||||
{
|
||||
node.AddChild(BuildNodeRecursive(child));
|
||||
var childHC = _world.EntityManager.GetComponent<Hierarchy>(child);
|
||||
child = childHC.ValueRO.nextSibling;
|
||||
child = childHC.nextSibling;
|
||||
}
|
||||
|
||||
return node;
|
||||
@@ -99,14 +94,18 @@ public partial class WorldNode : SceneGraphNode, IEquatable<WorldNode>
|
||||
|
||||
private void BuildGraph()
|
||||
{
|
||||
foreach (var (entity, hierarchy) in _world.Query<Hierarchy>())
|
||||
var queryID = new QueryBuilder()
|
||||
.WithAll<Hierarchy>()
|
||||
.Build(_world);
|
||||
|
||||
_world.GetEntityQueryReference(queryID).ForEach<Hierarchy>((entity, ref hierarchy) =>
|
||||
{
|
||||
if (hierarchy.ValueRO.parent == Entity.Invalid)
|
||||
if (hierarchy.parent == Entity.Invalid)
|
||||
{
|
||||
var node = BuildNodeRecursive(entity);
|
||||
AddChild(node);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public Task LoadAsync()
|
||||
@@ -116,7 +115,6 @@ public partial class WorldNode : SceneGraphNode, IEquatable<WorldNode>
|
||||
|
||||
public void Unload()
|
||||
{
|
||||
_world.Dispose();
|
||||
_world = null!;
|
||||
|
||||
Children?.Clear();
|
||||
|
||||
Reference in New Issue
Block a user