Refactor ECS core, improve thread safety, add tests

- Switch Scene IDs to ushort, update invalid value logic
- Add thread safety to SceneManager and ComponentRegistry
- Add GHOST_ZERO_INIT_COMPONENT define for editor configs
- Update mesh header counts to use int, not uint
- Add Collect methods for archetype/component cleanup
- Add With/Without/AsView to ComponentSet for easier use
- Refactor World creation/destruction, version handling
- Refactor ResourceStreamingContext to use init-only props
- Add unit tests for entity queries and world lifecycle
- Minor code cleanups and formatting fixes
This commit is contained in:
2026-05-13 13:36:51 +09:00
parent bb07644580
commit cbaa129d9e
17 changed files with 305 additions and 108 deletions

View File

@@ -37,7 +37,7 @@ public class AssertRegistryTest
{
var sourcePath = "Assets/test.text";
await File.WriteAllBytesAsync(sourcePath, [1, 2, 3], TestContext.CancellationToken);
var metaPath = AssetMetaIO.GetMetaPath(sourcePath);
using var cts = new CancellationTokenSource(5000);
@@ -47,7 +47,7 @@ public class AssertRegistryTest
}
Assert.IsTrue(File.Exists(metaPath));
var meta = await AssetMetaIO.ReadAsync(metaPath, TestContext.CancellationToken);
Assert.IsNotNull(meta);

View File

@@ -104,7 +104,7 @@ public class AssetManagerTest
Assert.IsGreaterThanOrEqualTo((int)AssetState.Ready, entry.StateValue);
var (data, error) = _graphicsEngine.ResourceDatabase.GetResourceBarrierData(handle.AsResource());
Assert.AreEqual(Error.None, error);
Assert.AreEqual(BarrierAccess.ShaderResource, data.access);
Assert.AreEqual(BarrierLayout.ShaderResource, data.layout);

View File

@@ -0,0 +1,71 @@
using Ghost.Engine.Components;
using Ghost.Entities;
using Misaki.HighPerformance.LowLevel.Buffer;
namespace Ghost.UnitTest.ECS;
[TestClass]
[DoNotParallelize]
public class EntityQueryTests
{
private struct Enableable : IEnableableComponent
{
}
private World _world = null!;
[TestInitialize]
public void Setup()
{
AllocationManager.Initialize();
_world = World.Create(null, 64);
}
[TestCleanup]
public void Cleanup()
{
_world.Dispose();
AllocationManager.Dispose();
}
private void CreateDefaultEntities(int count)
{
var set = new ComponentSetView([ComponentTypeID<MeshInstance>.Value, ComponentTypeID<LocalToWorld>.Value]);
_world.EntityManager.CreateEntities(count, set);
}
[TestMethod]
public void ComponentSetCreation()
{
using var set1 = new ComponentSet(AllocationHandle.Persistent, ComponentTypeID<MeshInstance>.Value, ComponentTypeID<LocalToWorld>.Value);
Assert.AreEqual(2, set1.Components.Length);
using var set2 = set1.With(AllocationHandle.Persistent, ComponentTypeID<Camera>.Value);
Assert.AreEqual(3, set2.Components.Length);
Assert.IsTrue(set2.Components.Contains(ComponentTypeID<Camera>.Value));
using var set3 = set2.Without(AllocationHandle.Persistent, ComponentTypeID<MeshInstance>.Value);
Assert.AreEqual(2, set3.Components.Length);
Assert.IsFalse(set3.Components.Contains(ComponentTypeID<MeshInstance>.Value));
}
[TestMethod]
public void SimpleQuery_EntityCountShouldEqual()
{
CreateDefaultEntities(100);
var queryID = QueryBuilder.New()
.WithAll<LocalToWorld, MeshInstance>()
.Build(_world);
ref readonly var query = ref _world.ComponentManager.GetEntityQueryReference(queryID);
var i = 0;
foreach (var (entity, ltw, mesh) in query.GetEntityComponentIterator<LocalToWorld, MeshInstance>())
{
i++;
}
Assert.AreEqual(100, i);
}
}

View File

@@ -0,0 +1,28 @@
using Ghost.Entities;
namespace Ghost.UnitTest.ECS;
[TestClass]
[DoNotParallelize]
public class WorldTests
{
[TestMethod]
public void CreateWorld()
{
using var world = World.Create();
Assert.IsNotNull(world);
}
[TestMethod]
public void AddEntityThenClearWorld()
{
using var world = World.Create();
Assert.IsNotNull(world);
world.EntityManager.CreateEntity();
Assert.AreEqual(1, world.EntityManager.EntityCount);
world.Clear(default);
Assert.AreEqual(0, world.EntityManager.EntityCount);
}
}

View File

@@ -1,12 +1,11 @@
using Ghost.Core;
using Ghost.Core.Utilities;
using Ghost.Engine;
using Ghost.Engine.Streaming;
using Ghost.Graphics.Core;
using Ghost.Graphics.RHI;
using Misaki.HighPerformance.Mathematics;
using Misaki.HighPerformance.Mathematics.Geometry;
using System.Collections.Concurrent;
using System.Runtime.InteropServices;
namespace Ghost.UnitTest.MockingEnvironment;
@@ -130,14 +129,14 @@ internal class MockingContentProvider : IContentProvider
{
magic = MeshContentHeader.MAGIC,
version = MeshContentHeader.VERSION,
vertexCount = (uint)vertices.Length,
indexCount = (uint)indices.Length,
materialPartCount = (uint)materialParts.Length,
meshletCount = (uint)meshlets.Length,
meshletGroupCount = (uint)groups.Length,
meshletHierarchyNodeCount = (uint)hierarchy.Length,
meshletVertexCount = (uint)meshletVertices.Length,
meshletTriangleCount = (uint)meshletTriangles.Length,
vertexCount = vertices.Length,
indexCount = indices.Length,
materialPartCount = materialParts.Length,
meshletCount = meshlets.Length,
meshletGroupCount = groups.Length,
meshletHierarchyNodeCount = hierarchy.Length,
meshletVertexCount = meshletVertices.Length,
meshletTriangleCount = meshletTriangles.Length,
materialSlotCount = 1,
lodLevelCount = 1,
boundsMin = new float3(0, 0, 0),