Add scene graph draft

This commit is contained in:
2026-01-25 22:06:58 +09:00
parent fdf831630b
commit 49f54c6b43
18 changed files with 1632 additions and 1553 deletions

View File

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