ECS refactor: new ComponentSet, serialization, generators

Major ECS API overhaul: added ComponentSet, refactored ComponentRegistry, and updated all entity/component creation methods. Introduced robust custom serialization infrastructure and per-component source generators for registration and (de)serialization. Updated editor, engine, and test code to use new APIs. Improved code quality, naming, and performance throughout. Removed obsolete code and updated dependencies.
This commit is contained in:
2025-12-20 20:41:40 +09:00
parent 3118021272
commit 00b4e82ded
60 changed files with 1216 additions and 814 deletions

View File

@@ -2,5 +2,6 @@ using Ghost.Core.Attributes;
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Ghost.Editor")]
[assembly: InternalsVisibleTo("Ghost.Editor.Core")]
[assembly: EngineAssembly]

View File

@@ -1,13 +1,12 @@
using Ghost.Engine.Editor;
using Ghost.SparseEntities;
using Ghost.SparseEntities.Components;
using Ghost.Entities;
using System.Runtime.CompilerServices;
namespace Ghost.Engine.Components;
[SkipLocalsInit]
[HideEditor]
public struct Hierarchy : IComponentData
public struct Hierarchy : IComponent
{
public Entity parent;
public Entity firstChild;

View File

@@ -1,21 +1,11 @@
using Ghost.Engine.Utilities;
using Ghost.SparseEntities.Components;
using System.Numerics;
using Ghost.Entities;
using Misaki.HighPerformance.Mathematics;
using System.Runtime.CompilerServices;
namespace Ghost.Engine.Components;
[SkipLocalsInit]
public struct LocalToWorld : IComponentData
public struct LocalToWorld : IComponent
{
public Matrix4x4 matrix;
public static LocalToWorld Identity
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new()
{
matrix = MatrixUtility.CreateTRS(Vector3.Zero, Quaternion.Identity, Vector3.One)
};
}
public float4x4 matrix;
}

View File

@@ -1,28 +1,55 @@
using Ghost.Core;
using Ghost.Engine.Models;
using Ghost.Entities;
using Misaki.HighPerformance.Jobs;
namespace Ghost.Engine;
internal class EngineCore
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
internal class EngineEntryAttribute : Attribute
{
public void Start(LaunchArgument args)
}
internal partial class EngineCoreImpl : IDisposable
{
internal readonly JobScheduler _jobScheduler;
internal EngineCoreImpl()
{
ActivationHandler.Handle(args);
//GraphicsPipeline.Initialize();
//GraphicsPipeline.Start();
Logger.LogInfo("Engine started successfully.");
_jobScheduler = new JobScheduler(Environment.ProcessorCount - 2); // We -2 here, one for main thread, one for render thread
}
public void IncrementCPUFenceValue()
internal void IncrementCPUFenceValue()
{
//GraphicsPipeline.SignalCPUReady();
}
public void ShutDown()
public void Dispose()
{
//GraphicsPipeline.SignalCPUReady();
//GraphicsPipeline.Shutdown();
_jobScheduler.Dispose();
JobScheduler.ReleaseTempAllocator();
}
}
[EngineEntry]
public static partial class EngineCore
{
internal static readonly EngineCoreImpl s_impl;
public static JobScheduler JobScheduler => s_impl._jobScheduler;
static EngineCore()
{
s_impl = new EngineCoreImpl();
ComponentRegistry.GetOrRegisterComponent<ManagedEntityRef>();
RegisterIComponentTypes();
}
internal static void Init()
{
}
internal static void Dispose()
{
s_impl.Dispose();
}
}

View File

@@ -16,9 +16,10 @@
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Ghost.SparseEntities\Ghost.SparseEntities.csproj" />
<ProjectReference Include="..\Ghost.Core\Ghost.Core.csproj" />
<ProjectReference Include="..\Ghost.Entities\Ghost.Entities.csproj" />
<ProjectReference Include="..\Ghost.Generator\Ghost.Generator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
<ProjectReference Include="..\Ghost.Graphics\Ghost.Graphics.csproj" />
<!--<ProjectReference Include="..\Ghost.Generator\Ghost.Generator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />-->
</ItemGroup>
</Project>

View File

@@ -0,0 +1,32 @@
using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Ghost.Engine.IO;
public class CustomSerializerAttribute : JsonConverterAttribute
{
public CustomSerializerAttribute([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type serializerType)
: base(serializerType)
{
}
}
public abstract class CustomSerializer<T> : JsonConverter<T>
{
public sealed override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return DeserializeJson(ref reader, options);
}
public sealed override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
{
SerializeJson(writer, value, options);
}
public abstract void SerializeJson(Utf8JsonWriter writer, T value, JsonSerializerOptions options);
public abstract T? DeserializeJson(ref Utf8JsonReader reader, JsonSerializerOptions options);
public abstract void SerializeBinary(BinaryWriter writer, T value);
public abstract T? DeserializeBinary(BinaryReader reader);
}

View File

@@ -0,0 +1,52 @@
using Ghost.Core;
using Ghost.Entities;
using System.Text.Json;
namespace Ghost.Engine.IO;
public static unsafe class ComponentSerializerRegistry
{
public delegate void BinaryWriteDelegate(BinaryWriter writer, void* ptr);
public delegate void JsonWriteDelegate(Utf8JsonWriter writer, void* ptr, JsonSerializerOptions options);
private static BinaryWriteDelegate[] s_binWriters = new BinaryWriteDelegate[64];
private static JsonWriteDelegate[] s_jsonWriters = new JsonWriteDelegate[64];
public static void Register(int typeID, BinaryWriteDelegate binWriter, JsonWriteDelegate jsonWriter)
{
if (typeID < 0)
{
throw new Exception($"Type ID cannot be negative: {typeID}");
}
if (typeID >= s_binWriters.Length)
{
Array.Resize(ref s_binWriters, typeID + 16);
Array.Resize(ref s_jsonWriters, typeID + 16);
}
s_binWriters[typeID] = binWriter;
s_jsonWriters[typeID] = jsonWriter;
}
public static void SerializeBinary(Identifier<IComponent> typeID, BinaryWriter writer, void* ptr)
{
if (s_binWriters[typeID] == null)
{
throw new Exception($"No serializer for ID {typeID}");
}
s_binWriters[typeID](writer, ptr);
}
public static void SerializeJson(Identifier<IComponent> typeID, Utf8JsonWriter writer, void* ptr, JsonSerializerOptions options)
{
if (s_jsonWriters[typeID] == null)
{
// TODO: Fallback to reflection?
return;
}
s_jsonWriters[typeID](writer, ptr, options);
}
}

View File

@@ -4,5 +4,5 @@ internal class EngineData
{
public const string ENGINE_NAME = "Ghost Engine";
public readonly static Version s_engineVersion = new(0, 1, 0);
public readonly static Version EngineVersion = new(0, 1, 0);
}

View File

@@ -2,7 +2,7 @@ using System.Text.Json;
namespace Ghost.Engine.Resources;
public static class StaticResource
public static class EngineResource
{
public static readonly JsonSerializerOptions defaultSerializerOptions = new()
{

View File

@@ -1,57 +0,0 @@
using Ghost.SparseEntities;
namespace Ghost.Engine.Services;
internal static class PlayerLoopService
{
private static bool _isRunning = false;
// TODO: Implement the actual time system
public static float fixedDeltaTime = 0.02f;
public static void Start()
{
if (_isRunning)
{
return;
}
for (var i = 0; i < World.WorldCount; i++)
{
var world = World.GetWorld(i);
foreach (var script in world.QueryScript())
{
script.Start();
}
}
}
public static void Update()
{
for (var i = 0; i < World.WorldCount; i++)
{
var world = World.GetWorld(i);
world.SystemStorage.UpdateSystems();
foreach (var script in world.QueryScript())
{
script.Update();
}
}
}
public static void Shutdown()
{
for (var i = 0; i < World.WorldCount; i++)
{
var world = World.GetWorld(i);
foreach (var script in world.QueryScript())
{
script.OnDestroy();
}
}
_isRunning = false;
}
}