using Ghost.Entities; using Misaki.HighPerformance.LowLevel.Buffer; using Misaki.HighPerformance.LowLevel.Collections; namespace Ghost.Engine.Core; /// /// Represents a runtime scene - a collection of entities with the same SceneID. /// public readonly struct Scene : IEquatable { private readonly short _id; /// /// Gets the unique identifier of this scene. /// public short ID => _id; /// /// Gets whether this scene is valid. /// public bool IsValid => _id >= 0; /// /// Gets an invalid scene instance. /// public static Scene Invalid => new(-1); internal Scene(short id) { _id = id; } public bool Equals(Scene other) { return _id == other._id; } public override bool Equals(object? obj) { return obj is Scene other && Equals(other); } public override int GetHashCode() { 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(ID: {_id})"; } } /// /// Manages scenes within a world. /// /// /// This is a minimal runtime representation. All metadata (like scene names) /// should be stored in editor-only classes (SceneNode). /// public static class SceneManager { private static short s_nextSceneID; private static readonly Queue s_recycledSceneIDs = new(); /// /// Creates a new scene in the world. /// /// The created scene. public static Scene CreateScene() { if (!s_recycledSceneIDs.TryDequeue(out var id)) { id = s_nextSceneID++; } return new Scene(id); } /// /// Destroys all entities belonging to the specified scene. /// /// The scene to unload. /// The world containing the entities. public static void UnloadScene(Scene scene, World world) { var queryID = new QueryBuilder().WithAll().Build(world); ref var query = ref world.ComponentManager.GetEntityQueryReference(queryID); using var scope = AllocationManager.CreateStackScope(); var entitiesToDestroy = new UnsafeList(128, scope.AllocationHandle); // Iterate through all matching entities foreach (var chunk in query.GetChunkIterator()) { var entities = chunk.GetEntities(); var sceneIDs = chunk.GetComponentData(); for (var i = 0; i < chunk.Count; i++) { if (sceneIDs[i].scene.ID == scene.ID) { entitiesToDestroy.Add(entities[i]); } } } world.EntityManager.DestroyEntities(entitiesToDestroy.AsSpan()); s_recycledSceneIDs.Enqueue(scene.ID); } /// /// Gets all entities belonging to the specified scene. /// /// The scene to query. /// The world containing the entities. /// Span to store the entities. /// The number of entities written to the span. public static UnsafeList GetSceneEntities(Scene scene, World world, AllocationHandle handle) { var queryID = new QueryBuilder().WithAll().Build(world); ref var query = ref world.ComponentManager.GetEntityQueryReference(queryID); var entities = new UnsafeList(128, handle); // Iterate through all matching entities foreach (var chunk in query.GetChunkIterator()) { var chunkEntities = chunk.GetEntities(); var sceneIDs = chunk.GetComponentData(); for (var i = 0; i < chunk.Count; i++) { if (sceneIDs[i].scene.ID == scene.ID) { entities.Add(chunkEntities[i]); } } } return entities; } }