Add scene graph draft
This commit is contained in:
@@ -1,88 +1,39 @@
|
||||
using Ghost.Entities;
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
|
||||
namespace Ghost.Engine.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a lightweight handle to a loaded scene.
|
||||
/// Represents a runtime scene - a collection of entities with the same SceneID.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// A Scene is a collection of entities tagged with a unique SceneID component.
|
||||
/// The Scene class provides a convenient handle to interact with all entities
|
||||
/// belonging to a particular scene within a World.
|
||||
/// </remarks>
|
||||
public sealed class Scene : IDisposable, IEquatable<Scene>
|
||||
public readonly struct Scene : IEquatable<Scene>
|
||||
{
|
||||
private static short s_nextSceneID = 0;
|
||||
|
||||
private readonly World _world;
|
||||
private readonly short _id;
|
||||
private readonly string _name;
|
||||
private bool _isDisposed;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the world this scene belongs to.
|
||||
/// </summary>
|
||||
public World World => _world;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the unique identifier for this scene.
|
||||
/// Gets the unique identifier of this scene.
|
||||
/// </summary>
|
||||
public short ID => _id;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of this scene.
|
||||
/// Gets whether this scene is valid.
|
||||
/// </summary>
|
||||
public string Name => _name;
|
||||
public bool IsValid => _id >= 0;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new scene handle.
|
||||
/// Gets an invalid scene instance.
|
||||
/// </summary>
|
||||
/// <param name="world">The world this scene belongs to.</param>
|
||||
/// <param name="name">The name of the scene.</param>
|
||||
internal Scene(World world, string name)
|
||||
{
|
||||
_world = world;
|
||||
_id = s_nextSceneID++;
|
||||
_name = name;
|
||||
}
|
||||
public static Scene Invalid => new(-1);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new scene handle with a specific ID.
|
||||
/// </summary>
|
||||
/// <param name="world">The world this scene belongs to.</param>
|
||||
/// <param name="id">The scene ID.</param>
|
||||
/// <param name="name">The name of the scene.</param>
|
||||
internal Scene(World world, short id, string name)
|
||||
internal Scene(short id)
|
||||
{
|
||||
_world = world;
|
||||
_id = id;
|
||||
_name = name;
|
||||
|
||||
// Update next ID if necessary
|
||||
if (id >= s_nextSceneID)
|
||||
{
|
||||
s_nextSceneID = (short)(id + 1);
|
||||
}
|
||||
}
|
||||
|
||||
~Scene()
|
||||
public bool Equals(Scene other)
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
|
||||
public bool Equals(Scene? other)
|
||||
{
|
||||
if (other is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ReferenceEquals(this, other))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return _world.Equals(other._world) && _id == other._id;
|
||||
return _id == other._id;
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
@@ -92,36 +43,117 @@ public sealed class Scene : IDisposable, IEquatable<Scene>
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(_world, _id);
|
||||
return _id.GetHashCode();
|
||||
}
|
||||
|
||||
public static bool operator ==(Scene left, Scene right)
|
||||
{
|
||||
return left.Equals(right);
|
||||
}
|
||||
|
||||
public static bool operator !=(Scene left, Scene right)
|
||||
{
|
||||
return !left.Equals(right);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Scene: {_name} (ID: {_id})";
|
||||
}
|
||||
|
||||
public static bool operator ==(Scene? left, Scene? right)
|
||||
{
|
||||
if (left is null)
|
||||
{
|
||||
return right is null;
|
||||
}
|
||||
return left.Equals(right);
|
||||
}
|
||||
|
||||
public static bool operator !=(Scene? left, Scene? right)
|
||||
{
|
||||
return !(left == right);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_isDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isDisposed = true;
|
||||
GC.SuppressFinalize(this);
|
||||
return $"Scene {{ ID: {_id} }}";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Manages scenes within a world.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is a minimal runtime representation. All metadata (like scene names)
|
||||
/// should be stored in editor-only classes (SceneNode).
|
||||
/// </remarks>
|
||||
public class SceneManager
|
||||
{
|
||||
private readonly World _world;
|
||||
private short _nextSceneID;
|
||||
|
||||
internal SceneManager(World world)
|
||||
{
|
||||
_world = world;
|
||||
_nextSceneID = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new scene in the world.
|
||||
/// </summary>
|
||||
/// <returns>The created scene.</returns>
|
||||
public Scene CreateScene()
|
||||
{
|
||||
var scene = new Scene(_nextSceneID++);
|
||||
return scene;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Destroys all entities belonging to the specified scene.
|
||||
/// </summary>
|
||||
/// <param name="scene">The scene to unload.</param>
|
||||
public void UnloadScene(Scene scene)
|
||||
{
|
||||
// Build query for entities with SceneID
|
||||
var builder = new QueryBuilder();
|
||||
builder.WithAll([ComponentTypeID<Components.SceneID>.Value]);
|
||||
var queryID = builder.Build(_world);
|
||||
ref var query = ref _world.ComponentManager.GetEntityQueryReference(queryID);
|
||||
|
||||
using var scope = AllocationManager.CreateStackScope();
|
||||
var entitiesToDestroy = new UnsafeList<Entity>(128, scope.AllocationHandle);
|
||||
|
||||
// Iterate through all matching entities
|
||||
foreach (var chunk in query.GetChunkIterator())
|
||||
{
|
||||
var entities = chunk.GetEntities();
|
||||
var sceneIDs = chunk.GetComponentData<Components.SceneID>();
|
||||
|
||||
for (var i = 0; i < chunk.Count; i++)
|
||||
{
|
||||
if (sceneIDs[i].id == scene.ID)
|
||||
{
|
||||
entitiesToDestroy.Add(entities[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_world.EntityManager.DestroyEntities(entitiesToDestroy.AsSpan());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all entities belonging to the specified scene.
|
||||
/// </summary>
|
||||
/// <param name="scene">The scene to query.</param>
|
||||
/// <param name="entities">Span to store the entities.</param>
|
||||
/// <returns>The number of entities written to the span.</returns>
|
||||
public int GetSceneEntities(Scene scene, Span<Entity> entities)
|
||||
{
|
||||
// Build query for entities with SceneID
|
||||
var builder = new QueryBuilder();
|
||||
builder.WithAll([ComponentTypeID<Components.SceneID>.Value]);
|
||||
var queryID = builder.Build(_world);
|
||||
ref var query = ref _world.ComponentManager.GetEntityQueryReference(queryID);
|
||||
|
||||
var index = 0;
|
||||
|
||||
// Iterate through all matching entities
|
||||
foreach (var chunk in query.GetChunkIterator())
|
||||
{
|
||||
var chunkEntities = chunk.GetEntities();
|
||||
var sceneIDs = chunk.GetComponentData<Components.SceneID>();
|
||||
|
||||
for (var i = 0; i < chunk.Count && index < entities.Length; i++)
|
||||
{
|
||||
if (sceneIDs[i].id == scene.ID)
|
||||
{
|
||||
entities[index++] = chunkEntities[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
}
|
||||
|
||||
193
Ghost.Engine/Systems/HierarchyUtility.cs
Normal file
193
Ghost.Engine/Systems/HierarchyUtility.cs
Normal file
@@ -0,0 +1,193 @@
|
||||
using Ghost.Entities;
|
||||
using Ghost.Engine.Components;
|
||||
|
||||
namespace Ghost.Engine.Systems;
|
||||
|
||||
/// <summary>
|
||||
/// Provides utility methods for working with entity hierarchies.
|
||||
/// </summary>
|
||||
public static class HierarchyUtility
|
||||
{
|
||||
/// <summary>
|
||||
/// Sets the parent of an entity, updating the Hierarchy component accordingly.
|
||||
/// </summary>
|
||||
/// <param name="world">The world containing the entities.</param>
|
||||
/// <param name="child">The child entity.</param>
|
||||
/// <param name="parent">The parent entity, or Entity.Invalid to make the entity a root.</param>
|
||||
public static void SetParent(World world, Entity child, Entity parent)
|
||||
{
|
||||
if (!world.EntityManager.HasComponent<Hierarchy>(child))
|
||||
{
|
||||
world.EntityManager.AddComponent(child, Hierarchy.Root);
|
||||
}
|
||||
|
||||
ref var childHierarchy = ref world.EntityManager.GetComponent<Hierarchy>(child);
|
||||
|
||||
// Remove from old parent's children list
|
||||
if (childHierarchy.parent.IsValid)
|
||||
{
|
||||
RemoveFromSiblingList(world, child, childHierarchy.parent);
|
||||
}
|
||||
|
||||
// Set new parent
|
||||
childHierarchy.parent = parent;
|
||||
|
||||
if (parent.IsValid)
|
||||
{
|
||||
if (!world.EntityManager.HasComponent<Hierarchy>(parent))
|
||||
{
|
||||
world.EntityManager.AddComponent(parent, Hierarchy.Root);
|
||||
}
|
||||
|
||||
ref var parentHierarchy = ref world.EntityManager.GetComponent<Hierarchy>(parent);
|
||||
|
||||
// Add to parent's children list
|
||||
childHierarchy.nextSibling = parentHierarchy.firstChild;
|
||||
parentHierarchy.firstChild = child;
|
||||
}
|
||||
else
|
||||
{
|
||||
childHierarchy.nextSibling = Entity.Invalid;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the parent of an entity.
|
||||
/// </summary>
|
||||
/// <param name="world">The world containing the entity.</param>
|
||||
/// <param name="entity">The entity to get the parent of.</param>
|
||||
/// <returns>The parent entity, or Entity.Invalid if the entity has no parent.</returns>
|
||||
public static Entity GetParent(World world, Entity entity)
|
||||
{
|
||||
if (!world.EntityManager.HasComponent<Hierarchy>(entity))
|
||||
{
|
||||
return Entity.Invalid;
|
||||
}
|
||||
|
||||
ref var hierarchy = ref world.EntityManager.GetComponent<Hierarchy>(entity);
|
||||
return hierarchy.parent;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all children of an entity.
|
||||
/// </summary>
|
||||
/// <param name="world">The world containing the entity.</param>
|
||||
/// <param name="parent">The parent entity.</param>
|
||||
/// <param name="children">Span to store the children.</param>
|
||||
/// <returns>The number of children written to the span.</returns>
|
||||
public static int GetChildren(World world, Entity parent, Span<Entity> children)
|
||||
{
|
||||
if (!world.EntityManager.HasComponent<Hierarchy>(parent))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
ref var hierarchy = ref world.EntityManager.GetComponent<Hierarchy>(parent);
|
||||
var currentChild = hierarchy.firstChild;
|
||||
var count = 0;
|
||||
|
||||
while (currentChild.IsValid && count < children.Length)
|
||||
{
|
||||
children[count++] = currentChild;
|
||||
|
||||
if (world.EntityManager.HasComponent<Hierarchy>(currentChild))
|
||||
{
|
||||
ref var childHierarchy = ref world.EntityManager.GetComponent<Hierarchy>(currentChild);
|
||||
currentChild = childHierarchy.nextSibling;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all descendants of an entity (children, grandchildren, etc.) in depth-first order.
|
||||
/// </summary>
|
||||
/// <param name="world">The world containing the entity.</param>
|
||||
/// <param name="root">The root entity.</param>
|
||||
/// <param name="descendants">List to store the descendants.</param>
|
||||
public static void GetDescendants(World world, Entity root, List<Entity> descendants)
|
||||
{
|
||||
Span<Entity> children = stackalloc Entity[32];
|
||||
var childCount = GetChildren(world, root, children);
|
||||
|
||||
for (int i = 0; i < childCount; i++)
|
||||
{
|
||||
var child = children[i];
|
||||
descendants.Add(child);
|
||||
GetDescendants(world, child, descendants);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a child from its parent's sibling list.
|
||||
/// </summary>
|
||||
private static void RemoveFromSiblingList(World world, Entity child, Entity parent)
|
||||
{
|
||||
if (!world.EntityManager.HasComponent<Hierarchy>(parent))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ref var parentHierarchy = ref world.EntityManager.GetComponent<Hierarchy>(parent);
|
||||
ref var childHierarchy = ref world.EntityManager.GetComponent<Hierarchy>(child);
|
||||
|
||||
// If child is the first child
|
||||
if (parentHierarchy.firstChild.Equals(child))
|
||||
{
|
||||
parentHierarchy.firstChild = childHierarchy.nextSibling;
|
||||
childHierarchy.nextSibling = Entity.Invalid;
|
||||
return;
|
||||
}
|
||||
|
||||
// Find the previous sibling
|
||||
var currentSibling = parentHierarchy.firstChild;
|
||||
|
||||
while (currentSibling.IsValid)
|
||||
{
|
||||
if (!world.EntityManager.HasComponent<Hierarchy>(currentSibling))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
ref var siblingHierarchy = ref world.EntityManager.GetComponent<Hierarchy>(currentSibling);
|
||||
|
||||
if (siblingHierarchy.nextSibling.Equals(child))
|
||||
{
|
||||
siblingHierarchy.nextSibling = childHierarchy.nextSibling;
|
||||
childHierarchy.nextSibling = Entity.Invalid;
|
||||
return;
|
||||
}
|
||||
|
||||
currentSibling = siblingHierarchy.nextSibling;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if an entity is an ancestor of another entity.
|
||||
/// </summary>
|
||||
/// <param name="world">The world containing the entities.</param>
|
||||
/// <param name="potentialAncestor">The potential ancestor entity.</param>
|
||||
/// <param name="descendant">The descendant entity.</param>
|
||||
/// <returns>True if potentialAncestor is an ancestor of descendant, false otherwise.</returns>
|
||||
public static bool IsAncestor(World world, Entity potentialAncestor, Entity descendant)
|
||||
{
|
||||
var current = GetParent(world, descendant);
|
||||
|
||||
while (current.IsValid)
|
||||
{
|
||||
if (current.Equals(potentialAncestor))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
current = GetParent(world, current);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user