Refactor folder structure
This commit is contained in:
116
src/Test/Ghost.Entities.Test/EntityQueryTest.cs
Normal file
116
src/Test/Ghost.Entities.Test/EntityQueryTest.cs
Normal file
@@ -0,0 +1,116 @@
|
||||
using Ghost.Test.Core;
|
||||
using Misaki.HighPerformance.Jobs;
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using Misaki.HighPerformance.Mathematics;
|
||||
|
||||
namespace Ghost.Entities.Test;
|
||||
|
||||
internal struct TestEntityQueryJob : IJobEntity<Transform>
|
||||
{
|
||||
public readonly void Execute(Entity entity, ref Transform transform, int threadIndex)
|
||||
{
|
||||
transform.position += new float3(5, 5, 5);
|
||||
}
|
||||
}
|
||||
|
||||
internal struct TestChunkQueryJob : IJobChunk
|
||||
{
|
||||
public readonly void Execute(ChunkView view, int threadIndex)
|
||||
{
|
||||
var random = new random((uint)threadIndex + 1u);
|
||||
|
||||
var transforms = view.GetComponentDataRW<Transform>();
|
||||
for (var i = 0; i < view.Count; i++)
|
||||
{
|
||||
transforms[i].position += random.NextFloat3();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public partial class EntityQueryTest : ITest
|
||||
{
|
||||
private JobScheduler _jobScheduler = null!;
|
||||
private World _world = null!;
|
||||
|
||||
public void Setup()
|
||||
{
|
||||
_jobScheduler = new JobScheduler(4);
|
||||
_world = World.Create(_jobScheduler);
|
||||
}
|
||||
|
||||
public void Run()
|
||||
{
|
||||
var entities = (Span<Entity>)stackalloc Entity[1000];
|
||||
|
||||
using var scope = AllocationManager.CreateStackScope();
|
||||
using var set = new ComponentSet(scope.AllocationHandle, ComponentTypeID<Transform>.Value);
|
||||
|
||||
_world.EntityManager.CreateEntities(entities, set);
|
||||
|
||||
var queryID = new QueryBuilder().WithAllRW<Transform>().Build(_world);
|
||||
ref var query = ref _world.ComponentManager.GetEntityQueryReference(queryID);
|
||||
|
||||
_world.AdvanceVersion();
|
||||
|
||||
var testJob = new TestChunkQueryJob();
|
||||
var handle = query.ScheduleChunkParallel(testJob, 1, JobHandle.Invalid);
|
||||
_jobScheduler.WaitComplete(handle);
|
||||
|
||||
query.ForEach<Transform>((e, ref t) =>
|
||||
{
|
||||
Console.WriteLine($"Entity {e} Has Position: {t.position}");
|
||||
});
|
||||
|
||||
foreach (var (entity, transform) in query.GetEntityComponentIterator<Transform>())
|
||||
{
|
||||
Console.WriteLine($"Entity {entity} Updated Position: {transform.Get().position}");
|
||||
}
|
||||
|
||||
foreach (var chunk in query.GetChunkIterator())
|
||||
{
|
||||
var transforms = chunk.GetComponentData<Transform>();
|
||||
var chunkEntities = chunk.GetEntities();
|
||||
|
||||
// if (chunk.HasChanged<Transform>(0))
|
||||
{
|
||||
// var bits = chunk.GetEnableBits<Transform>();
|
||||
|
||||
// var it = bits.GetIterator();
|
||||
// while (it.Next(out var index) && index < chunk.Count)
|
||||
for (var index = 0; index < chunk.Count; index++)
|
||||
{
|
||||
Console.WriteLine($"Entity {chunkEntities[index]} Updated Position: {transforms[index].position}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_world.EntityManager.DestroyEntities(entities);
|
||||
}
|
||||
|
||||
public void Cleanup()
|
||||
{
|
||||
_world.Dispose();
|
||||
_jobScheduler.Dispose();
|
||||
JobScheduler.ReleaseTempAllocator();
|
||||
}
|
||||
}
|
||||
|
||||
public struct Transform : IEnableableComponent
|
||||
{
|
||||
public float3 position;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Position: {position}";
|
||||
}
|
||||
}
|
||||
|
||||
public struct Mesh : IComponent
|
||||
{
|
||||
public int index;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Index: {index}";
|
||||
}
|
||||
}
|
||||
21
src/Test/Ghost.Entities.Test/Ghost.Entities.Test.csproj
Normal file
21
src/Test/Ghost.Entities.Test/Ghost.Entities.Test.csproj
Normal file
@@ -0,0 +1,21 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BenchmarkDotNet" Version="0.15.8" />
|
||||
<PackageReference Include="BenchmarkDotNet.Diagnostics.Windows" Version="0.15.8" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Runtime\Ghost.Entities\Ghost.Entities.csproj" />
|
||||
<ProjectReference Include="..\..\Test\Ghost.Test.Core\Ghost.Test.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
14
src/Test/Ghost.Entities.Test/Program.cs
Normal file
14
src/Test/Ghost.Entities.Test/Program.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using BenchmarkDotNet.Running;
|
||||
using Ghost.Entities.Test;
|
||||
using Ghost.Test.Core;
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
|
||||
//AllocationManager.EnableDebugLayer();
|
||||
//TestRunner.Run<SerializationTest>();
|
||||
//AllocationManager.Dispose();
|
||||
|
||||
BenchmarkRunner.Run<QueryBenchmark>();
|
||||
//var test = new QueryBenchmark();
|
||||
//test.Setup();
|
||||
//test.QueryEntities();
|
||||
//test.Cleanup();
|
||||
82
src/Test/Ghost.Entities.Test/QueryBenchmark.cs
Normal file
82
src/Test/Ghost.Entities.Test/QueryBenchmark.cs
Normal file
@@ -0,0 +1,82 @@
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Diagnosers;
|
||||
using Ghost.Core;
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Intrinsics;
|
||||
|
||||
namespace Ghost.Entities.Test;
|
||||
|
||||
internal class GameObject
|
||||
{
|
||||
public Vector4 Position { get; set; }
|
||||
}
|
||||
|
||||
internal struct Position : IComponent
|
||||
{
|
||||
public Vector4 value;
|
||||
}
|
||||
|
||||
[HardwareCounters(HardwareCounter.CacheMisses, HardwareCounter.LlcReference, HardwareCounter.InstructionRetired)]
|
||||
public class QueryBenchmark
|
||||
{
|
||||
private World _world = null!;
|
||||
private Identifier<EntityQuery> _queryIdentifier;
|
||||
|
||||
private GameObject[] _gameObjects = null!;
|
||||
|
||||
private float _dt = Random.Shared.NextSingle();
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
_world = World.Create(entityCapacity: 1_000_000);
|
||||
_gameObjects = new GameObject[1_000_000];
|
||||
|
||||
using var scope = AllocationManager.CreateStackScope();
|
||||
var componentSet = new ComponentSet(scope.AllocationHandle, ComponentTypeID<Position>.Value);
|
||||
_world.EntityManager.CreateEntities(1_000_000, componentSet);
|
||||
|
||||
_queryIdentifier = new QueryBuilder().WithAllRW<Position>().Build(_world);
|
||||
|
||||
for (var i = 0; i < 1_000_000; i++)
|
||||
{
|
||||
_gameObjects[i] = new GameObject { Position = new Vector4(i, i, i, 0) };
|
||||
}
|
||||
}
|
||||
|
||||
[GlobalCleanup]
|
||||
public void Cleanup()
|
||||
{
|
||||
_world.Dispose();
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void QueryGameObjects()
|
||||
{
|
||||
for (var i = 0; i < _gameObjects.Length; i++)
|
||||
{
|
||||
_gameObjects[i].Position += new Vector4(_dt, _dt, _dt, 0);
|
||||
}
|
||||
}
|
||||
|
||||
[Benchmark(Baseline = true)]
|
||||
public void QueryEntities()
|
||||
{
|
||||
ref var query = ref _world.ComponentManager.GetEntityQueryReference(_queryIdentifier);
|
||||
var vecDT = Vector256.Create(_dt);
|
||||
|
||||
foreach (var chunkView in query.GetChunkIterator())
|
||||
{
|
||||
var positions = chunkView.GetComponentDataRW<Position>();
|
||||
ref var address = ref MemoryMarshal.GetReference(positions);
|
||||
|
||||
for (var i = 0; i < positions.Length; i++)
|
||||
{
|
||||
Unsafe.Add(ref address, i).value += new Vector4(_dt, _dt, _dt, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
0
src/Test/Ghost.Entities.Test/ScriptComponentTest.cs
Normal file
0
src/Test/Ghost.Entities.Test/ScriptComponentTest.cs
Normal file
140
src/Test/Ghost.Entities.Test/SerializationTest.cs
Normal file
140
src/Test/Ghost.Entities.Test/SerializationTest.cs
Normal file
@@ -0,0 +1,140 @@
|
||||
using Ghost.Test.Core;
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using Misaki.HighPerformance.Mathematics;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Ghost.Entities.Test;
|
||||
|
||||
public class SerializationTest : ITest
|
||||
{
|
||||
private World _world = null!;
|
||||
|
||||
public void Setup()
|
||||
{
|
||||
_world = World.Create();
|
||||
}
|
||||
|
||||
public unsafe void Run()
|
||||
{
|
||||
using var scope = AllocationManager.CreateStackScope();
|
||||
var set1 = new ComponentSet(scope.AllocationHandle, ComponentTypeID<Transform>.Value);
|
||||
var set2 = new ComponentSet(scope.AllocationHandle, ComponentTypeID<Transform>.Value, ComponentTypeID<Mesh>.Value);
|
||||
|
||||
var e1 = _world.EntityManager.CreateEntity(set1);
|
||||
var e2 = _world.EntityManager.CreateEntity(set2);
|
||||
|
||||
_world.EntityManager.SetComponent(e1, new Transform { position = new float3(1, 2, 3) });
|
||||
_world.EntityManager.SetComponent(e2, new Transform { position = new float3(4, 5, 6) });
|
||||
_world.EntityManager.SetComponent(e2, new Mesh { index = 42 });
|
||||
|
||||
using var stream = new MemoryStream();
|
||||
var serializeOptions = new JsonSerializerOptions
|
||||
{
|
||||
IncludeFields = true
|
||||
};
|
||||
|
||||
using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { Indented = true });
|
||||
|
||||
writer.WriteStartObject();
|
||||
writer.WriteString("Name", "world 1");
|
||||
writer.WriteStartArray("Entities");
|
||||
|
||||
for (var i = 0; i < _world.ComponentManager.ArchetypeCount; i++)
|
||||
{
|
||||
ref var archetype = ref _world.ComponentManager.GetArchetypeReference(i);
|
||||
|
||||
for (var j = 0; j < archetype.ChunkCount; j++)
|
||||
{
|
||||
ref var chunk = ref archetype.GetChunkReference(j);
|
||||
for (var k = 0; k < chunk._count; k++)
|
||||
{
|
||||
writer.WriteStartObject();
|
||||
|
||||
var entity = *(Entity*)(chunk.GetUnsafePtr() + archetype.EntityIDsOffset + k * sizeof(Entity));
|
||||
writer.WriteNumber("ID", entity.ID);
|
||||
writer.WriteStartArray("Components");
|
||||
|
||||
foreach (var layout in archetype._layouts)
|
||||
{
|
||||
var type = ComponentRegistry.s_runtimeIDToType[layout.componentID];
|
||||
var size = ComponentRegistry.GetComponentInfo(layout.componentID).size;
|
||||
|
||||
if (type.AssemblyQualifiedName == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
writer.WriteStartObject();
|
||||
writer.WriteString("Type", type.AssemblyQualifiedName);
|
||||
writer.WritePropertyName("Data");
|
||||
|
||||
var pComponentData = chunk.GetUnsafePtr() + layout.offset + (k * size);
|
||||
var instace = Marshal.PtrToStructure((nint)pComponentData, type);
|
||||
JsonSerializer.Serialize(writer, instace, type, serializeOptions);
|
||||
|
||||
writer.WriteEndObject();
|
||||
}
|
||||
|
||||
writer.WriteEndArray();
|
||||
writer.WriteEndObject();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
writer.WriteEndArray();
|
||||
writer.WriteEndObject();
|
||||
|
||||
writer.Flush();
|
||||
|
||||
var data = stream.ToArray();
|
||||
|
||||
var json = System.Text.Encoding.UTF8.GetString(data);
|
||||
Console.WriteLine(json);
|
||||
|
||||
|
||||
var reader = new Utf8JsonReader(data);
|
||||
|
||||
var root = JsonDocument.ParseValue(ref reader).RootElement;
|
||||
var name = root.GetProperty("Name").GetString();
|
||||
Console.WriteLine($"Deserialized World Name: {name}");
|
||||
|
||||
var entityData = new List<(int EntityID, Type ComponentType, object Instance)>();
|
||||
|
||||
foreach (var entityElement in root.GetProperty("Entities").EnumerateArray())
|
||||
{
|
||||
var id = entityElement.GetProperty("ID").GetInt32();
|
||||
|
||||
// Access the new "Components" array
|
||||
var componentsElement = entityElement.GetProperty("Components");
|
||||
|
||||
foreach (var componentElement in componentsElement.EnumerateArray())
|
||||
{
|
||||
var typeName = componentElement.GetProperty("Type").GetString();
|
||||
var dataElement = componentElement.GetProperty("Data");
|
||||
|
||||
var type = Type.GetType(typeName!);
|
||||
if (type == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var instance = dataElement.Deserialize(type, serializeOptions);
|
||||
if (instance != null)
|
||||
{
|
||||
entityData.Add((id, type, instance));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var (id, type, instance) in entityData)
|
||||
{
|
||||
Console.WriteLine($"Entity ID: {id}, Component: {type.Name}, Data: {instance}");
|
||||
}
|
||||
}
|
||||
|
||||
public void Cleanup()
|
||||
{
|
||||
_world.Dispose();
|
||||
}
|
||||
}
|
||||
47
src/Test/Ghost.Entities.Test/SystemTest.cs
Normal file
47
src/Test/Ghost.Entities.Test/SystemTest.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
using Ghost.Test.Core;
|
||||
|
||||
namespace Ghost.Entities.Test;
|
||||
|
||||
internal class SystemTest : ITest
|
||||
{
|
||||
private World _world = null!;
|
||||
|
||||
public void Setup()
|
||||
{
|
||||
_world = World.Create();
|
||||
}
|
||||
|
||||
public void Run()
|
||||
{
|
||||
var group = _world.SystemManager.GetSystem<DefaultSystemGroup>();
|
||||
group.AddSystem<TestSystemB>();
|
||||
group.AddSystem<TestSystemA>();
|
||||
|
||||
group.SortSystems();
|
||||
|
||||
var api = new SystemAPI();
|
||||
_world.SystemManager.InitializeAll(in api);
|
||||
}
|
||||
|
||||
public void Cleanup()
|
||||
{
|
||||
_world.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
internal class TestSystemA : SystemBase
|
||||
{
|
||||
protected override void OnInitialize(ref readonly SystemAPI systemAPI)
|
||||
{
|
||||
Console.WriteLine("TestSystemA Initialized");
|
||||
}
|
||||
}
|
||||
|
||||
[UpdateAfter(typeof(TestSystemA))]
|
||||
internal class TestSystemB : SystemBase
|
||||
{
|
||||
protected override void OnInitialize(ref readonly SystemAPI systemAPI)
|
||||
{
|
||||
Console.WriteLine("TestSystemB Initialized");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user