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:
@@ -2,5 +2,6 @@ using Ghost.Core.Attributes;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("Ghost.Editor")]
|
||||
[assembly: InternalsVisibleTo("Ghost.Editor.Core")]
|
||||
|
||||
[assembly: EngineAssembly]
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
32
Ghost.Engine/IO/CustomSerializer.cs
Normal file
32
Ghost.Engine/IO/CustomSerializer.cs
Normal 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);
|
||||
}
|
||||
52
Ghost.Engine/IO/SerializerRegistry.cs
Normal file
52
Ghost.Engine/IO/SerializerRegistry.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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()
|
||||
{
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user