feature/archetype-ecs #1
@@ -20,8 +20,9 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Misaki.HighPerformance" Version="1.0.1" />
|
||||
<PackageReference Include="Misaki.HighPerformance.LowLevel" Version="1.2.5" />
|
||||
<PackageReference Include="Misaki.HighPerformance" Version="1.0.2" />
|
||||
<PackageReference Include="Misaki.HighPerformance.Jobs" Version="1.2.1" />
|
||||
<PackageReference Include="Misaki.HighPerformance.LowLevel" Version="1.3.2" />
|
||||
<PackageReference Include="Misaki.HighPerformance.Mathematics" Version="1.2.6" />
|
||||
<PackageReference Include="System.IO.Hashing" Version="10.0.0" />
|
||||
<PackageReference Include="TerraFX.Interop.Windows" Version="10.0.26100.5" />
|
||||
|
||||
@@ -4,8 +4,7 @@ public interface IHandleType;
|
||||
public interface IIdentifierType;
|
||||
public interface IKeyType;
|
||||
|
||||
public readonly struct Handle<T>
|
||||
where T : IHandleType
|
||||
public readonly struct Handle<T> : IEquatable<Handle<T>>
|
||||
{
|
||||
public readonly int id;
|
||||
public readonly int generation;
|
||||
@@ -57,8 +56,7 @@ public readonly struct Handle<T>
|
||||
}
|
||||
}
|
||||
|
||||
public readonly struct Identifier<T>
|
||||
where T : IIdentifierType
|
||||
public readonly struct Identifier<T> : IEquatable<Identifier<T>>
|
||||
{
|
||||
public readonly int value;
|
||||
|
||||
@@ -74,7 +72,7 @@ public readonly struct Identifier<T>
|
||||
|
||||
public readonly override int GetHashCode()
|
||||
{
|
||||
return value.GetHashCode();
|
||||
return value;
|
||||
}
|
||||
|
||||
public readonly override bool Equals(object? obj)
|
||||
@@ -106,10 +104,32 @@ public readonly struct Identifier<T>
|
||||
{
|
||||
return !a.Equals(b);
|
||||
}
|
||||
|
||||
public static bool operator <(Identifier<T> a, Identifier<T> b)
|
||||
{
|
||||
return a.value < b.value;
|
||||
}
|
||||
|
||||
public static bool operator >(Identifier<T> a, Identifier<T> b)
|
||||
{
|
||||
return a.value > b.value;
|
||||
}
|
||||
|
||||
public static bool operator <=(Identifier<T> a, Identifier<T> b)
|
||||
{
|
||||
return a.value <= b.value;
|
||||
}
|
||||
|
||||
public static bool operator >=(Identifier<T> a, Identifier<T> b)
|
||||
{
|
||||
return a.value >= b.value;
|
||||
}
|
||||
|
||||
public static implicit operator int(Identifier<T> id) => id.value;
|
||||
public static implicit operator Identifier<T>(int value) => new Identifier<T>(value);
|
||||
}
|
||||
|
||||
public readonly struct Key<T>
|
||||
where T : IKeyType
|
||||
{
|
||||
public readonly ulong value;
|
||||
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
using Misaki.HighPerformance.LowLevel;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ghost.Core;
|
||||
|
||||
public readonly struct Result
|
||||
{
|
||||
private readonly bool _isSuccess;
|
||||
private readonly string? _message;
|
||||
|
||||
public readonly bool IsSuccess => _isSuccess;
|
||||
public readonly bool IsFailure => !_isSuccess;
|
||||
private readonly bool _isSuccess;
|
||||
|
||||
public readonly string? Message => _message;
|
||||
public readonly bool IsSuccess => _isSuccess;
|
||||
public readonly bool IsFailure => !_isSuccess;
|
||||
|
||||
public Result(bool success, string? message = null)
|
||||
{
|
||||
@@ -36,16 +38,10 @@ public readonly struct Result
|
||||
return Result<T>.Failure(message);
|
||||
}
|
||||
|
||||
public static Result<T, S> Create<T, S>(T value, S status)
|
||||
where S : Enum
|
||||
public void Deconstruct(out bool success, out string? message)
|
||||
{
|
||||
return new Result<T, S>(value, status);
|
||||
}
|
||||
|
||||
public static RefResult<T, S> CreateRef<T, S>(ref T value, S status)
|
||||
where S : Enum
|
||||
{
|
||||
return new RefResult<T, S>(ref value, status);
|
||||
success = IsSuccess;
|
||||
message = Message;
|
||||
}
|
||||
|
||||
public override string ToString() => IsSuccess ? "OK" : $"Error: {Message}";
|
||||
@@ -55,16 +51,28 @@ public readonly struct Result
|
||||
|
||||
public readonly struct Result<T>
|
||||
{
|
||||
private readonly bool _isSuccess;
|
||||
private readonly T _value;
|
||||
private readonly string? _message;
|
||||
private readonly bool _isSuccess;
|
||||
|
||||
public T Value
|
||||
{
|
||||
get
|
||||
{
|
||||
#if DEBUG || GHOST_EDITOR
|
||||
if (!_isSuccess)
|
||||
{
|
||||
throw new InvalidOperationException($"Cannot access Value when Result is a failure. {_message}");
|
||||
}
|
||||
#endif
|
||||
return _value;
|
||||
}
|
||||
}
|
||||
|
||||
public readonly string? Message => _message;
|
||||
public readonly bool IsSuccess => _isSuccess;
|
||||
public readonly bool IsFailure => !_isSuccess;
|
||||
|
||||
public readonly T Value => _value;
|
||||
public readonly string? Message => _message;
|
||||
|
||||
public Result(bool success, T value, string? message = null)
|
||||
{
|
||||
_isSuccess = success;
|
||||
@@ -82,6 +90,13 @@ public readonly struct Result<T>
|
||||
return new Result<T>(false, default!, message);
|
||||
}
|
||||
|
||||
public void Deconstruct(out bool success, out T value, out string? message)
|
||||
{
|
||||
success = IsSuccess;
|
||||
value = Value;
|
||||
message = Message;
|
||||
}
|
||||
|
||||
public override string ToString() => IsSuccess ? $"OK: {Value}" : $"Error: {Message}";
|
||||
|
||||
public static implicit operator Result<T>(T? data) => data is not null ? Success(data) : Failure(null);
|
||||
@@ -89,9 +104,9 @@ public readonly struct Result<T>
|
||||
public static implicit operator bool(Result<T> result) => result.IsSuccess;
|
||||
}
|
||||
|
||||
public enum ResultStatus : byte
|
||||
public enum ErrorStatus : byte
|
||||
{
|
||||
Success,
|
||||
None,
|
||||
NotFound,
|
||||
InvalidArgument,
|
||||
InvalidState,
|
||||
@@ -104,67 +119,152 @@ public enum ResultStatus : byte
|
||||
UnknownError
|
||||
}
|
||||
|
||||
public readonly struct Result<T, S>
|
||||
where S : Enum
|
||||
public readonly struct Result<T, E>
|
||||
where E : struct, Enum
|
||||
{
|
||||
private readonly T _value;
|
||||
private readonly S _status;
|
||||
private readonly E _error;
|
||||
private readonly bool _isSuccess;
|
||||
|
||||
public T Value => _value;
|
||||
public S Status => _status;
|
||||
public T Value
|
||||
{
|
||||
get
|
||||
{
|
||||
#if DEBUG || GHOST_EDITOR
|
||||
if (!_isSuccess)
|
||||
{
|
||||
throw new InvalidOperationException($"Cannot access Value when Result is a failure. Error: {_error}");
|
||||
}
|
||||
#endif
|
||||
return _value;
|
||||
}
|
||||
}
|
||||
|
||||
public Result(T value, S status)
|
||||
public E Error => _error;
|
||||
public bool IsSuccess => _isSuccess;
|
||||
public bool IsFailure => !_isSuccess;
|
||||
|
||||
public Result(T value, E status, bool isSuccess)
|
||||
{
|
||||
_value = value;
|
||||
_status = status;
|
||||
_error = status;
|
||||
_isSuccess = isSuccess;
|
||||
}
|
||||
|
||||
public static Result<T, S> Create(T value, S status)
|
||||
public static Result<T, E> Success(T value)
|
||||
{
|
||||
return new Result<T, S>(value, status);
|
||||
return new Result<T, E>(value, default, true);
|
||||
}
|
||||
|
||||
public override string ToString() => $"Value: {_value}, Status: {_status}";
|
||||
public static Result<T, E> Failure(E status)
|
||||
{
|
||||
return new Result<T, E>(default!, status, false);
|
||||
}
|
||||
|
||||
public void Deconstruct(out bool success, out T value, out E status)
|
||||
{
|
||||
success = IsSuccess;
|
||||
value = Value;
|
||||
status = Error;
|
||||
}
|
||||
|
||||
public override string ToString() => $"Value: {_value}, Status: {_error}";
|
||||
|
||||
public static implicit operator Result<T, E>(T data) => new(data, default, true);
|
||||
public static implicit operator Result<T, E>(E status) => new(default!, status, false);
|
||||
public static implicit operator bool(Result<T, E> result) => result.IsSuccess;
|
||||
}
|
||||
|
||||
public readonly ref struct RefResult<T, S>
|
||||
where S : Enum
|
||||
public readonly ref struct RefResult<T, E>
|
||||
where E : struct, Enum
|
||||
{
|
||||
private readonly ref T _value;
|
||||
private readonly S _status;
|
||||
private readonly E _error;
|
||||
private readonly bool _isSuccess;
|
||||
|
||||
public ref T Value => ref _value;
|
||||
public S Status => _status;
|
||||
public ref T Value
|
||||
{
|
||||
get
|
||||
{
|
||||
#if DEBUG || GHOST_EDITOR
|
||||
if (!_isSuccess)
|
||||
{
|
||||
throw new InvalidOperationException($"Cannot access Value when Result is a failure. Error: {_error}");
|
||||
}
|
||||
#endif
|
||||
return ref _value;
|
||||
}
|
||||
}
|
||||
|
||||
public RefResult(ref T value, S status)
|
||||
public E Error => _error;
|
||||
public bool IsSuccess => _isSuccess;
|
||||
public bool IsFailure => !_isSuccess;
|
||||
|
||||
public RefResult(ref T value, E error, bool isSuccess)
|
||||
{
|
||||
_value = ref value;
|
||||
_status = status;
|
||||
_error = error;
|
||||
_isSuccess = isSuccess;
|
||||
}
|
||||
|
||||
public static RefResult<T, S> Create(ref T value, S status)
|
||||
public static RefResult<T, E> Success(ref T value)
|
||||
{
|
||||
return new RefResult<T, S>(ref value, status);
|
||||
return new RefResult<T, E>(ref value, default, true);
|
||||
}
|
||||
|
||||
public override string ToString() => $"Value: {_value}, Status: {_status}";
|
||||
public static RefResult<T, E> Failure(E error)
|
||||
{
|
||||
return new RefResult<T, E>(ref Unsafe.NullRef<T>(), error, false);
|
||||
}
|
||||
|
||||
public void Deconstruct(out bool success, out Ref<T> value, out E status)
|
||||
{
|
||||
success = IsSuccess;
|
||||
value = new Ref<T>(ref Value);
|
||||
status = Error;
|
||||
}
|
||||
|
||||
public override string ToString() => $"Value: {_value}, Status: {_error}";
|
||||
|
||||
public static implicit operator RefResult<T, E>(Ref<T> data) => new(ref data.Get(), default, true);
|
||||
public static implicit operator RefResult<T, E>(E error) => new(ref Unsafe.NullRef<T>(), error, false);
|
||||
public static implicit operator bool(RefResult<T, E> result) => result.IsSuccess;
|
||||
}
|
||||
|
||||
public static class ResultExtensions
|
||||
{
|
||||
public static void ThrowIfFailed(this Result result)
|
||||
public static void ThrowIfFailed(this ErrorStatus result, [CallerArgumentExpression(nameof(result))] string? op = null)
|
||||
{
|
||||
if (!result.IsSuccess)
|
||||
if (result != ErrorStatus.None)
|
||||
{
|
||||
throw new InvalidOperationException($"Operation failed: {result.Message}");
|
||||
throw new InvalidOperationException($"{op} failed: {result}");
|
||||
}
|
||||
}
|
||||
|
||||
public static T GetValueOrThrow<T>(this Result<T> result)
|
||||
public static void ThrowIfFailed(this Result result, [CallerArgumentExpression(nameof(result))] string? op = null)
|
||||
{
|
||||
if (!result.IsSuccess)
|
||||
{
|
||||
throw new InvalidOperationException($"Operation failed: {result.Message}");
|
||||
throw new InvalidOperationException($"{op} failed: {result.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public static T GetValueOrThrow<T>(this Result<T> result, [CallerArgumentExpression(nameof(result))] string? op = null)
|
||||
{
|
||||
if (!result.IsSuccess)
|
||||
{
|
||||
throw new InvalidOperationException($"{op} failed: {result.Message}");
|
||||
}
|
||||
|
||||
return result.Value;
|
||||
}
|
||||
|
||||
public static T GetValueOrThrow<T, S>(this Result<T, S> result, [CallerArgumentExpression(nameof(result))] string? op = null)
|
||||
where S : struct, Enum
|
||||
{
|
||||
if (!result.IsSuccess)
|
||||
{
|
||||
throw new InvalidOperationException($"{op} failed: status {result.Error}");
|
||||
}
|
||||
|
||||
return result.Value;
|
||||
@@ -175,21 +275,35 @@ public static class ResultExtensions
|
||||
return result.IsSuccess ? result.Value : defaultValue;
|
||||
}
|
||||
|
||||
public static T GetValueOrThrow<T, S>(this Result<T, S> result, S expect)
|
||||
where S : Enum
|
||||
public static T? GetValueOrDefault<T, S>(this Result<T, S> result, T? defaultValue = default)
|
||||
where S : struct, Enum
|
||||
{
|
||||
if (!EqualityComparer<S>.Default.Equals(result.Status, expect))
|
||||
{
|
||||
throw new InvalidOperationException($"Operation failed: expected status {expect}, but got {result.Status}");
|
||||
}
|
||||
|
||||
return result.Value;
|
||||
return result.IsSuccess ? result.Value : defaultValue;
|
||||
}
|
||||
|
||||
public static T? GetValueOrDefault<T, S>(this Result<T, S> result, S expect, T? defaultValue = default)
|
||||
where S : Enum
|
||||
public static bool TryGetValue<T>(this Result<T> result, out T value)
|
||||
{
|
||||
return (result.Status?.Equals(expect) ?? false) ? defaultValue : result.Value;
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
value = result.Value;
|
||||
return true;
|
||||
}
|
||||
|
||||
value = default!;
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool TryGetValue<T, S>(this Result<T, S> result, out T value)
|
||||
where S : struct, Enum
|
||||
{
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
value = result.Value;
|
||||
return true;
|
||||
}
|
||||
|
||||
value = default!;
|
||||
return false;
|
||||
}
|
||||
|
||||
public static Result OnSuccess(this Result result, Action action)
|
||||
@@ -212,6 +326,17 @@ public static class ResultExtensions
|
||||
return result;
|
||||
}
|
||||
|
||||
public static Result<T, E> OnSuccess<T, E>(this Result<T, E> result, Action<T> action)
|
||||
where E : struct, Enum
|
||||
{
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
action(result.Value);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static Result OnFailed(this Result result, Action<string?> action)
|
||||
{
|
||||
if (result.IsFailure)
|
||||
@@ -231,4 +356,15 @@ public static class ResultExtensions
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static Result<T, E> OnFailed<T, E>(this Result<T, E> result, Action<E> action)
|
||||
where E : struct, Enum
|
||||
{
|
||||
if (result.IsFailure)
|
||||
{
|
||||
action(result.Error);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ using Ghost.Core;
|
||||
using Ghost.Editor.Core.Inspector;
|
||||
using Ghost.Editor.Core.Resources;
|
||||
using Ghost.Editor.Core.Utilities;
|
||||
using Ghost.Entities;
|
||||
using Ghost.SparseEntities;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using System.Reflection;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using Ghost.Entities;
|
||||
using Ghost.Entities.Components;
|
||||
using Ghost.Entities.Query;
|
||||
using Ghost.SparseEntities;
|
||||
using Ghost.SparseEntities.Components;
|
||||
using Ghost.SparseEntities.Query;
|
||||
|
||||
namespace Ghost.Editor.Core.Inspector;
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ using Ghost.Editor.Core.Controls.Internal;
|
||||
using Ghost.Editor.Core.Inspector;
|
||||
using Ghost.Editor.Core.Resources;
|
||||
using Ghost.Engine.Editor;
|
||||
using Ghost.Entities;
|
||||
using Ghost.SparseEntities;
|
||||
using Microsoft.UI.Text;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using Ghost.Engine.Components;
|
||||
using Ghost.Entities;
|
||||
using Ghost.SparseEntities;
|
||||
|
||||
namespace Ghost.Editor.Core.SceneGraph;
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ using Ghost.Editor.Core.Inspector;
|
||||
using Ghost.Editor.Core.Resources;
|
||||
using Ghost.Editor.Core.Serializer;
|
||||
using Ghost.Engine.Components;
|
||||
using Ghost.Entities;
|
||||
using Ghost.SparseEntities;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using Ghost.Editor.Core.SceneGraph;
|
||||
using Ghost.Engine.Utilities;
|
||||
using Ghost.Entities;
|
||||
using Ghost.Entities.Components;
|
||||
using Ghost.SparseEntities;
|
||||
using Ghost.SparseEntities.Components;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using Ghost.Engine.Editor;
|
||||
using Ghost.Entities;
|
||||
using Ghost.Entities.Components;
|
||||
using Ghost.SparseEntities;
|
||||
using Ghost.SparseEntities.Components;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ghost.Engine.Components;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using Ghost.Engine.Utilities;
|
||||
using Ghost.Entities.Components;
|
||||
using Ghost.SparseEntities.Components;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Ghost.Entities\Ghost.Entities.csproj" />
|
||||
<ProjectReference Include="..\Ghost.SparseEntities\Ghost.SparseEntities.csproj" />
|
||||
<ProjectReference Include="..\Ghost.Graphics\Ghost.Graphics.csproj" />
|
||||
<!--<ProjectReference Include="..\Ghost.Generator\Ghost.Generator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />-->
|
||||
</ItemGroup>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Ghost.Entities;
|
||||
using Ghost.SparseEntities;
|
||||
|
||||
namespace Ghost.Engine.Services;
|
||||
|
||||
|
||||
101
Ghost.Entities.Test/EntityQueryTest.cs
Normal file
101
Ghost.Entities.Test/EntityQueryTest.cs
Normal file
@@ -0,0 +1,101 @@
|
||||
using Ghost.Test.Core;
|
||||
using Misaki.HighPerformance.Jobs;
|
||||
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];
|
||||
_world.EntityManager.CreateEntities(entities, ComponentTypeID<Transform>.value);
|
||||
|
||||
var queryID = new QueryBuilder().WithAllRW<Transform>().Build(_world);
|
||||
ref var query = ref _world.GetEntityQueryReference(queryID);
|
||||
|
||||
_world.AdvanceVersion();
|
||||
|
||||
var testJob = new TestChunkQueryJob();
|
||||
var handle = query.ScheduleChunkParallel(testJob, 64, 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 struct Mesh : IComponent
|
||||
{
|
||||
public int index;
|
||||
}
|
||||
@@ -1,191 +0,0 @@
|
||||
using Ghost.Entities.Components;
|
||||
using Ghost.Entities.Query;
|
||||
using Ghost.Entities.Systems;
|
||||
using Ghost.Test.Core;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Ghost.Entities.Test;
|
||||
|
||||
public partial class EntityTest : ITest
|
||||
{
|
||||
private World _world = null!;
|
||||
|
||||
public void Setup()
|
||||
{
|
||||
_world = World.Create();
|
||||
}
|
||||
|
||||
public void Run()
|
||||
{
|
||||
var entity1 = _world.EntityManager.CreateEntity();
|
||||
var entity2 = _world.EntityManager.CreateEntity();
|
||||
var entity3 = _world.EntityManager.CreateEntity();
|
||||
|
||||
_world.EntityManager.AddComponent(entity1, new Transform { position = new Vector3(1, 2, 3) });
|
||||
_world.EntityManager.AddComponent(entity1, new Mesh { index = 42 });
|
||||
_world.EntityManager.AddScript<UIManager>(entity1);
|
||||
_world.EntityManager.AddScript<EventManager>(entity1);
|
||||
|
||||
_world.EntityManager.AddComponent(entity2, new Transform { position = new Vector3(4, 5, 6) });
|
||||
_world.EntityManager.AddComponent(entity2, new Mesh { index = 43 });
|
||||
_world.EntityManager.AddScript<UserScript>(entity2);
|
||||
|
||||
_world.EntityManager.AddComponent(entity3, new Transform { position = new Vector3(7, 8, 9) });
|
||||
_world.EntityManager.AddScript<EventManager>(entity3);
|
||||
|
||||
foreach (var (_, transform) in _world.Query<Transform>())
|
||||
{
|
||||
transform.ValueRW.position += new Vector3(1, 1, 1);
|
||||
}
|
||||
|
||||
var filter = new QueryBuilder()
|
||||
.WithAll<Mesh>()
|
||||
.Build();
|
||||
|
||||
foreach (var (_, mesh) in _world.QueryFilter<Mesh>(in filter))
|
||||
{
|
||||
mesh.ValueRW.index += 1;
|
||||
}
|
||||
|
||||
_world.EntityManager.RemoveEntity(ref entity2);
|
||||
|
||||
var entity4 = _world.EntityManager.CreateEntity();
|
||||
_world.EntityManager.AddComponent(entity4, new Transform { position = new Vector3(10, 11, 12) });
|
||||
_world.EntityManager.AddComponent(entity4, new Mesh { index = 45 });
|
||||
_world.EntityManager.AddScript<UserScript>(entity4);
|
||||
|
||||
_world.SystemStorage.AddSystem<TestSystem2>();
|
||||
_world.SystemStorage.AddSystem<TestSystem>();
|
||||
|
||||
_world.SystemStorage.CreateSystems();
|
||||
_world.SystemStorage.UpdateSystems();
|
||||
}
|
||||
|
||||
public void Cleanup()
|
||||
{
|
||||
_world.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public class TestSystem : ISystem
|
||||
{
|
||||
public void OnCreate(in SystemState state)
|
||||
{
|
||||
}
|
||||
|
||||
public void OnUpdate(in SystemState state)
|
||||
{
|
||||
foreach (var (entity, transform) in state.World.Query<Transform>())
|
||||
{
|
||||
Console.WriteLine($"Entity {entity}: Transform Position = {transform.ValueRO.position}");
|
||||
}
|
||||
}
|
||||
|
||||
public void OnDestroy(in SystemState state)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
[DependsOn(typeof(TestSystem))]
|
||||
public class TestSystem2 : ISystem
|
||||
{
|
||||
public void OnCreate(in SystemState state)
|
||||
{
|
||||
}
|
||||
|
||||
public void OnUpdate(in SystemState state)
|
||||
{
|
||||
foreach (var (entity, mesh) in state.World.Query<Mesh>())
|
||||
{
|
||||
Console.WriteLine($"Entity {entity}: Mesh Index = {mesh.ValueRO.index}");
|
||||
}
|
||||
}
|
||||
|
||||
public void OnDestroy(in SystemState state)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public struct Transform : IComponentData
|
||||
{
|
||||
public Vector3 position;
|
||||
public Quaternion rotation;
|
||||
public Vector3 scale;
|
||||
}
|
||||
|
||||
public struct Mesh : IComponentData
|
||||
{
|
||||
public uint index;
|
||||
}
|
||||
|
||||
public class UserScript : ScriptComponent
|
||||
{
|
||||
public override int ExecutionOrder => -1;
|
||||
|
||||
public override void OnEnable()
|
||||
{
|
||||
Console.WriteLine("UserScript enabled for entity: " + Owner);
|
||||
EntityManager.GetComponent<Transform>(Owner).ValueRW.position += new Vector3(10, 10, 10);
|
||||
}
|
||||
|
||||
override public void OnDisable()
|
||||
{
|
||||
Console.WriteLine("UserScript disabled for entity: " + Owner);
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
Console.WriteLine("UserScript initialized for entity: " + Owner);
|
||||
}
|
||||
|
||||
public override void Start()
|
||||
{
|
||||
Console.WriteLine("UserScript started for entity: " + Owner);
|
||||
}
|
||||
|
||||
public override void Update()
|
||||
{
|
||||
Console.WriteLine("UserScript updating for entity: " + Owner);
|
||||
}
|
||||
|
||||
public override void OnDestroy()
|
||||
{
|
||||
Console.WriteLine("UserScript destroyed for entity: " + Owner);
|
||||
}
|
||||
}
|
||||
|
||||
public class UIManager : ScriptComponent
|
||||
{
|
||||
public override void Start()
|
||||
{
|
||||
Console.WriteLine("UIManager started for entity: " + Owner);
|
||||
}
|
||||
|
||||
public override void Update()
|
||||
{
|
||||
Console.WriteLine("UIManager updating for entity: " + Owner);
|
||||
}
|
||||
|
||||
public override void OnDestroy()
|
||||
{
|
||||
Console.WriteLine("UIManager destroyed for entity: " + Owner);
|
||||
}
|
||||
}
|
||||
|
||||
public class EventManager : ScriptComponent
|
||||
{
|
||||
public override void Start()
|
||||
{
|
||||
Console.WriteLine("EventManager started for entity: " + Owner);
|
||||
}
|
||||
|
||||
public override void Update()
|
||||
{
|
||||
Console.WriteLine("EventManager updating for entity: " + Owner);
|
||||
}
|
||||
|
||||
public override void OnDestroy()
|
||||
{
|
||||
Console.WriteLine("EventManager destroyed for entity: " + Owner);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
|
||||
using Ghost.Entities.Test;
|
||||
using Ghost.Test.Core;
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
|
||||
TestRunner.Run<EntityTest>();
|
||||
AllocationManager.EnableDebugLayer();
|
||||
TestRunner.Run<SystemTest>();
|
||||
AllocationManager.Dispose();
|
||||
|
||||
0
Ghost.Entities.Test/ScriptComponentTest.cs
Normal file
0
Ghost.Entities.Test/ScriptComponentTest.cs
Normal file
52
Ghost.Entities.Test/SystemTest.cs
Normal file
52
Ghost.Entities.Test/SystemTest.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
using Ghost.Test.Core;
|
||||
using Misaki.HighPerformance.Jobs;
|
||||
|
||||
namespace Ghost.Entities.Test;
|
||||
|
||||
internal class SystemTest : ITest
|
||||
{
|
||||
private JobScheduler _jobScheduler = null!;
|
||||
private World _world = null!;
|
||||
|
||||
public void Setup()
|
||||
{
|
||||
_jobScheduler = new JobScheduler(4);
|
||||
_world = World.Create(_jobScheduler);
|
||||
}
|
||||
|
||||
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();
|
||||
_jobScheduler.Dispose();
|
||||
JobScheduler.ReleaseTempAllocator();
|
||||
}
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
||||
702
Ghost.Entities/Archetype.cs
Normal file
702
Ghost.Entities/Archetype.cs
Normal file
@@ -0,0 +1,702 @@
|
||||
using Ghost.Core;
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
using Misaki.HighPerformance.LowLevel.Utilities;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ghost.Entities;
|
||||
|
||||
internal unsafe sealed class ChunkDebugView
|
||||
{
|
||||
[DebuggerDisplay("{Name,nq}: {Data}")]
|
||||
internal class ComponentArrayView
|
||||
{
|
||||
public string Name { get; }
|
||||
[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
|
||||
public object Data { get; }
|
||||
|
||||
public ComponentArrayView(string name, object data)
|
||||
{
|
||||
Name = name;
|
||||
Data = data;
|
||||
}
|
||||
}
|
||||
|
||||
private Chunk _chunk;
|
||||
|
||||
public ChunkDebugView(Chunk chunk)
|
||||
{
|
||||
_chunk = chunk;
|
||||
}
|
||||
|
||||
[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
|
||||
public object[] Items => GetItems(in _chunk);
|
||||
|
||||
private static T[] ReadComponentArray<T>(long pData, int offsetInChunk, int count)
|
||||
where T : unmanaged
|
||||
{
|
||||
var result = new T[count];
|
||||
unsafe
|
||||
{
|
||||
var basePtr = (byte*)pData + offsetInChunk;
|
||||
var span = new Span<T>(basePtr, count);
|
||||
span.CopyTo(result);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static object[] GetItems(ref readonly Chunk chunk)
|
||||
{
|
||||
#if !(DEBUG || GHOST_EDITOR)
|
||||
return [];
|
||||
#else
|
||||
var pData = chunk.GetUnsafePtr();
|
||||
var count = chunk._count;
|
||||
var capacity = chunk._capacity;
|
||||
var worldID = chunk._worldID;
|
||||
var archetypeID = chunk._archetypeID;
|
||||
|
||||
if (count == 0)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
var views = new List<object>();
|
||||
var r = World.GetWorld(worldID);
|
||||
if (!r)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
ref var archetype = ref r.Value.GetArchetypeReference(archetypeID);
|
||||
var it = archetype._signature.GetIterator();
|
||||
while (it.Next(out var index))
|
||||
{
|
||||
var type = ComponentRegister.s_runtimeIDToType[index];
|
||||
if (type == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var layout = archetype.GetLayout(index).Value;
|
||||
var readMethod = typeof(ChunkDebugView)
|
||||
.GetMethod(nameof(ReadComponentArray), System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static)!
|
||||
.MakeGenericMethod(type);
|
||||
|
||||
// 3. Invoke it to get a Position[] or Velocity[]
|
||||
var array = readMethod.Invoke(null, [(long)pData, layout.offset, count]);
|
||||
if (array == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// 4. Wrap it in a nice label so the debugger shows "Position[]"
|
||||
views.Add(new ComponentArrayView(type.Name, array));
|
||||
}
|
||||
|
||||
return [.. views];
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
[DebuggerTypeProxy(typeof(ChunkDebugView))]
|
||||
internal unsafe struct Chunk : IDisposable
|
||||
{
|
||||
public const int CHUNK_BUFFER_SIZE = 16384; // 16 KB
|
||||
public const int BIT_ALIGNMENT = 8;
|
||||
public const int BIT_SHIFT = 3; // log2(BIT_ALIGNMENT)
|
||||
public const int BIT_ALIGNMENT_MINUS_ONE = BIT_ALIGNMENT - 1;
|
||||
|
||||
private UnsafeArray<byte> _data;
|
||||
private UnsafeArray<int> _versions;
|
||||
|
||||
// TODO: Add structual change versioning, similar to DidOrderChange in unity ecs.
|
||||
internal int _structuralVersion;
|
||||
|
||||
internal int _count;
|
||||
internal readonly int _capacity;
|
||||
|
||||
#if DEBUG || GHOST_EDITOR
|
||||
// For debugging purpose
|
||||
internal int _worldID;
|
||||
internal int _archetypeID;
|
||||
#endif
|
||||
|
||||
public Chunk(int bufferSize, int capacity, int componentCount, int globalVersion)
|
||||
{
|
||||
_data = new UnsafeArray<byte>(bufferSize, Allocator.Persistent, AllocationOption.Clear);
|
||||
_versions = new UnsafeArray<int>(componentCount, Allocator.Persistent);
|
||||
_capacity = capacity;
|
||||
_count = 0;
|
||||
|
||||
_versions.AsSpan().Fill(globalVersion);
|
||||
_structuralVersion = globalVersion;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly byte* GetUnsafePtr()
|
||||
{
|
||||
return (byte*)_data.GetUnsafePtr();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly int* GetVersionUnsafePtr()
|
||||
{
|
||||
return (int*)_versions.GetUnsafePtr();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_data.Dispose();
|
||||
_versions.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
internal unsafe struct Archetype : IDisposable
|
||||
{
|
||||
internal struct ComponentMemoryLayout
|
||||
{
|
||||
public int componentID;
|
||||
public int size;
|
||||
public int offset;
|
||||
public int enableBitsOffset;
|
||||
public int versionIndex;
|
||||
}
|
||||
|
||||
private struct Edge
|
||||
{
|
||||
public int componentID;
|
||||
public int targetArchetype; // can't use Identifier<Archetype> because cycle causer
|
||||
}
|
||||
|
||||
internal UnsafeBitSet _signature;
|
||||
internal UnsafeList<Chunk> _chunks;
|
||||
internal UnsafeArray<ComponentMemoryLayout> _layouts;
|
||||
|
||||
private UnsafeArray<int> _componentIDToLayoutIndex;
|
||||
// TODO: Is hash map better?
|
||||
private UnsafeList<Edge> _edgesAdd;
|
||||
private UnsafeList<Edge> _edgesRemove;
|
||||
|
||||
private readonly Identifier<Archetype> _id;
|
||||
private readonly Identifier<World> _worldID;
|
||||
|
||||
private readonly int _hash;
|
||||
private int _entityCapacity;
|
||||
private int _maxComponentID;
|
||||
private int _entityIdsOffset;
|
||||
|
||||
public readonly Identifier<Archetype> ID => _id;
|
||||
public readonly Identifier<World> WorldID => _worldID;
|
||||
|
||||
public readonly int EntityCapacity => _entityCapacity;
|
||||
public readonly int ChunkCount => _chunks.Count;
|
||||
public readonly int EntityIDsOffset => _entityIdsOffset;
|
||||
|
||||
public Archetype(Identifier<Archetype> id, Identifier<World> worldID, ReadOnlySpan<Identifier<IComponent>> componentIds)
|
||||
{
|
||||
_id = id;
|
||||
_worldID = worldID;
|
||||
|
||||
_chunks = new UnsafeList<Chunk>(4, Allocator.Persistent);
|
||||
_edgesAdd = new UnsafeList<Edge>(4, Allocator.Persistent);
|
||||
_edgesRemove = new UnsafeList<Edge>(4, Allocator.Persistent);
|
||||
|
||||
if (componentIds.IsEmpty)
|
||||
{
|
||||
_signature = new UnsafeBitSet(1, Allocator.Persistent, AllocationOption.Clear);
|
||||
_hash = 0;
|
||||
|
||||
_signature.ClearAll();
|
||||
_entityCapacity = Chunk.CHUNK_BUFFER_SIZE / sizeof(Entity);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var highestComponentID = 0;
|
||||
for (var i = 0; i < componentIds.Length; i++)
|
||||
{
|
||||
if (componentIds[i] > highestComponentID)
|
||||
{
|
||||
highestComponentID = componentIds[i];
|
||||
}
|
||||
}
|
||||
|
||||
_signature = new UnsafeBitSet(highestComponentID + 1, Allocator.Persistent, AllocationOption.Clear);
|
||||
_hash = _signature.GetHashCode();
|
||||
|
||||
CalculateLayout(componentIds);
|
||||
}
|
||||
|
||||
private void CalculateLayout(ReadOnlySpan<Identifier<IComponent>> componentIds)
|
||||
{
|
||||
var entitySize = sizeof(Entity);
|
||||
var entityAlign = (int)MemoryUtility.AlignOf<Entity>();
|
||||
|
||||
var components = (Span<ComponentInfo>)stackalloc ComponentInfo[componentIds.Length];
|
||||
for (var i = 0; i < componentIds.Length; i++)
|
||||
{
|
||||
_signature.SetBit(componentIds[i]);
|
||||
components[i] = ComponentRegister.GetComponentInfo(componentIds[i]);
|
||||
}
|
||||
|
||||
// Calculate total size per entity to get an initial capacity estimate
|
||||
var bytesPerEntity = entitySize;
|
||||
var maxComponentID = 0;
|
||||
for (var i = 0; i < components.Length; i++)
|
||||
{
|
||||
var comp = components[i];
|
||||
bytesPerEntity += comp.size;
|
||||
if (comp.id > maxComponentID)
|
||||
{
|
||||
maxComponentID = comp.id;
|
||||
}
|
||||
}
|
||||
|
||||
_maxComponentID = maxComponentID;
|
||||
_entityCapacity = Chunk.CHUNK_BUFFER_SIZE / bytesPerEntity;
|
||||
_layouts = new UnsafeArray<ComponentMemoryLayout>(components.Length, Allocator.Persistent);
|
||||
_componentIDToLayoutIndex = new UnsafeArray<int>(_maxComponentID + 1, Allocator.Persistent);
|
||||
|
||||
_componentIDToLayoutIndex.AsSpan().Fill(-1);
|
||||
|
||||
components.Sort((a, b) => b.alignment.CompareTo(a.alignment));
|
||||
var tempOffsets = stackalloc int[components.Length];
|
||||
var tempBitmaskOffsets = stackalloc int[components.Length];
|
||||
|
||||
while (_entityCapacity > 0)
|
||||
{
|
||||
var currentOffset = 0;
|
||||
var fits = true;
|
||||
|
||||
currentOffset = (currentOffset + entityAlign - 1) & ~(entityAlign - 1);
|
||||
|
||||
_entityIdsOffset = currentOffset;
|
||||
currentOffset += _entityCapacity * entitySize;
|
||||
|
||||
for (var i = 0; i < components.Length; i++)
|
||||
{
|
||||
var size = components[i].size;
|
||||
var align = components[i].alignment;
|
||||
|
||||
currentOffset = (currentOffset + align - 1) & ~(align - 1);
|
||||
tempOffsets[i] = currentOffset;
|
||||
currentOffset += _entityCapacity * size;
|
||||
|
||||
var bitmaskOffset = -1;
|
||||
if (components[i].isEnableable)
|
||||
{
|
||||
var bitmaskSize = (_entityCapacity + Chunk.BIT_ALIGNMENT_MINUS_ONE) / Chunk.BIT_ALIGNMENT;
|
||||
// Reserve space for the bitmask (1 bit per entity)
|
||||
|
||||
currentOffset = (currentOffset + Chunk.BIT_ALIGNMENT_MINUS_ONE) & ~Chunk.BIT_ALIGNMENT_MINUS_ONE; // Align
|
||||
bitmaskOffset = currentOffset;
|
||||
currentOffset += bitmaskSize;
|
||||
}
|
||||
|
||||
tempBitmaskOffsets[i] = bitmaskOffset;
|
||||
|
||||
if (currentOffset > Chunk.CHUNK_BUFFER_SIZE)
|
||||
{
|
||||
fits = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (fits)
|
||||
{
|
||||
for (var i = 0; i < components.Length; i++)
|
||||
{
|
||||
_layouts[i] = new ComponentMemoryLayout
|
||||
{
|
||||
componentID = components[i].id,
|
||||
offset = tempOffsets[i],
|
||||
size = components[i].size,
|
||||
enableBitsOffset = tempBitmaskOffsets[i],
|
||||
versionIndex = i
|
||||
};
|
||||
|
||||
_componentIDToLayoutIndex[components[i].id] = i;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
_entityCapacity--;
|
||||
}
|
||||
}
|
||||
|
||||
public void AllocateEntity(out int chunkIndex, out int rowIndex)
|
||||
{
|
||||
var world = World.GetWorldUncheck(_worldID);
|
||||
|
||||
for (var i = 0; i < _chunks.Count; i++)
|
||||
{
|
||||
ref var chunk = ref _chunks[i];
|
||||
if (chunk._count < _entityCapacity)
|
||||
{
|
||||
rowIndex = chunk._count;
|
||||
chunk._count++;
|
||||
chunk._structuralVersion = world.Version;
|
||||
chunkIndex = i;
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Need to allocate a new chunk
|
||||
var newChunk = new Chunk(Chunk.CHUNK_BUFFER_SIZE, _entityCapacity, _layouts.Count, world.Version);
|
||||
#if DEBUG || GHOST_EDITOR
|
||||
newChunk._worldID = _worldID;
|
||||
newChunk._archetypeID = _id;
|
||||
#endif
|
||||
|
||||
// Set all enable to true by default for enableable components
|
||||
for (var i = 0; i < _layouts.Count; i++)
|
||||
{
|
||||
var layout = _layouts[i];
|
||||
if (layout.enableBitsOffset != -1)
|
||||
{
|
||||
var pChunk = newChunk.GetUnsafePtr();
|
||||
var pBits = pChunk + layout.enableBitsOffset;
|
||||
MemoryUtility.MemSet(pBits, 0xFF, (nuint)((_entityCapacity + Chunk.BIT_ALIGNMENT_MINUS_ONE) / Chunk.BIT_ALIGNMENT));
|
||||
}
|
||||
}
|
||||
|
||||
rowIndex = 0;
|
||||
newChunk._count++;
|
||||
chunkIndex = _chunks.Count;
|
||||
|
||||
_chunks.Add(newChunk);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly void SetEntity(int chunkIndex, int rowIndex, Entity entity)
|
||||
{
|
||||
var chunk = _chunks[chunkIndex];
|
||||
var chunkBase = chunk.GetUnsafePtr();
|
||||
var dst = chunkBase + _entityIdsOffset + (sizeof(Entity) * rowIndex);
|
||||
|
||||
MemoryUtility.MemCpy(dst, &entity, (nuint)sizeof(Entity));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly ErrorStatus SetComponentData(int chunkIndex, int rowIndex, Identifier<IComponent> componentID, void* pComponent)
|
||||
{
|
||||
var r = GetLayout(componentID);
|
||||
if (r.Error != ErrorStatus.None)
|
||||
{
|
||||
return r.Error;
|
||||
}
|
||||
|
||||
var offset = r.Value.offset;
|
||||
ref var chunk = ref _chunks[chunkIndex];
|
||||
|
||||
var chunkBase = chunk.GetUnsafePtr();
|
||||
var size = ComponentRegister.GetComponentInfo(componentID).size;
|
||||
var dst = chunkBase + offset + (size * rowIndex);
|
||||
|
||||
MemoryUtility.MemCpy(dst, pComponent, (nuint)size);
|
||||
|
||||
var world = World.GetWorldUncheck(_worldID);
|
||||
MarkChanged(chunkIndex, componentID, world.Version);
|
||||
|
||||
return ErrorStatus.None;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly void* GetComponentData(int chunkIndex, int rowIndex, Identifier<IComponent> componentID)
|
||||
{
|
||||
var r = GetLayout(componentID);
|
||||
if (r.Error != ErrorStatus.None)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var offset = r.Value.offset;
|
||||
var chunk = _chunks[chunkIndex];
|
||||
|
||||
var chunkBase = chunk.GetUnsafePtr();
|
||||
var size = ComponentRegister.GetComponentInfo(componentID).size;
|
||||
return chunkBase + offset + (size * rowIndex);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ref Chunk GetChunkReference(int index)
|
||||
{
|
||||
return ref _chunks[index];
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly Result<ComponentMemoryLayout, ErrorStatus> GetLayout(int componentID)
|
||||
{
|
||||
if (componentID >= _componentIDToLayoutIndex.Count)
|
||||
{
|
||||
return ErrorStatus.InvalidArgument;
|
||||
}
|
||||
|
||||
var layoutIndex = _componentIDToLayoutIndex[componentID];
|
||||
if (layoutIndex == -1)
|
||||
{
|
||||
return ErrorStatus.NotFound;
|
||||
}
|
||||
|
||||
return _layouts[layoutIndex];
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly ErrorStatus MarkChanged(int chunkIndex, int componentTypeId, int globalVersion)
|
||||
{
|
||||
var layoutResult = GetLayout(componentTypeId);
|
||||
if (layoutResult.IsFailure)
|
||||
{
|
||||
return layoutResult.Error;
|
||||
}
|
||||
|
||||
ref var chunk = ref _chunks[chunkIndex];
|
||||
chunk.GetVersionUnsafePtr()[layoutResult.Value.versionIndex] = globalVersion;
|
||||
|
||||
return ErrorStatus.None;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly Result<int, ErrorStatus> GetVersion(int chunkIndex, int componentTypeId)
|
||||
{
|
||||
var layoutResult = GetLayout(componentTypeId);
|
||||
if (layoutResult.Error != ErrorStatus.None)
|
||||
{
|
||||
return layoutResult.Error;
|
||||
}
|
||||
|
||||
ref var chunk = ref _chunks[chunkIndex];
|
||||
return chunk.GetVersionUnsafePtr()[layoutResult.Value.versionIndex];
|
||||
}
|
||||
|
||||
public ErrorStatus RemoveEntity(int chunkIndex, int rowIndex)
|
||||
{
|
||||
if (chunkIndex < 0 || chunkIndex >= _chunks.Count)
|
||||
{
|
||||
return ErrorStatus.InvalidArgument;
|
||||
}
|
||||
|
||||
var world = World.GetWorldUncheck(_worldID);
|
||||
ref var chunk = ref _chunks[chunkIndex];
|
||||
var lastIndex = chunk._count - 1;
|
||||
|
||||
// If we are NOT removing the very last entity, we must swap.
|
||||
if (rowIndex != lastIndex)
|
||||
{
|
||||
var chunkBase = chunk.GetUnsafePtr();
|
||||
var pLastEntity = chunkBase + _entityIdsOffset + (sizeof(Entity) * lastIndex);
|
||||
var pRowEntity = chunkBase + _entityIdsOffset + (sizeof(Entity) * rowIndex);
|
||||
|
||||
var result = world.EntityManager.UpdateEntityLocation(*(Entity*)pLastEntity, _id, chunkIndex, rowIndex);
|
||||
if (result != ErrorStatus.None)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
// Only operate the swap back after the update is succeed.
|
||||
MemoryUtility.MemCpy(pRowEntity, pLastEntity, (nuint)sizeof(Entity));
|
||||
|
||||
for (var i = 0; i < _layouts.Count; i++)
|
||||
{
|
||||
var layout = _layouts[i];
|
||||
|
||||
var pRow = chunk.GetUnsafePtr() + layout.offset + (layout.size * rowIndex);
|
||||
var pLast = chunk.GetUnsafePtr() + layout.offset + (layout.size * lastIndex);
|
||||
|
||||
MemoryUtility.MemCpy(pRow, pLast, (nuint)layout.size);
|
||||
}
|
||||
}
|
||||
|
||||
chunk._count--;
|
||||
chunk._structuralVersion = world.Version;
|
||||
|
||||
return ErrorStatus.None;
|
||||
}
|
||||
|
||||
public ErrorStatus RemoveEntities(int chunkIndex, ReadOnlySpan<int> sortedIndicesToRemove)
|
||||
{
|
||||
if (chunkIndex < 0 || chunkIndex >= _chunks.Count)
|
||||
{
|
||||
return ErrorStatus.InvalidArgument;
|
||||
}
|
||||
|
||||
if (sortedIndicesToRemove.Length == 0)
|
||||
{
|
||||
return ErrorStatus.None;
|
||||
}
|
||||
|
||||
ref var chunk = ref _chunks[chunkIndex];
|
||||
|
||||
int oldCount = chunk._count;
|
||||
int removeCount = sortedIndicesToRemove.Length;
|
||||
int newCount = oldCount - removeCount; // The boundary between "Keep" and "Drop"
|
||||
|
||||
var chunkBase = chunk.GetUnsafePtr();
|
||||
var world = World.GetWorldUncheck(_worldID); // Typo fixed from 'wrold'
|
||||
|
||||
// Pointers for the swap logic
|
||||
// 1. 'holePtr' tracks which index in the sorted list we are processing
|
||||
int holePtr = 0;
|
||||
|
||||
// 2. 'candidateIndex' starts at the end of the OLD array and moves backward
|
||||
int candidateIndex = oldCount - 1;
|
||||
|
||||
// 3. 'removalTailPtr' tracks removals at the end of the array to skip them
|
||||
int removalTailPtr = sortedIndicesToRemove.Length - 1;
|
||||
|
||||
// Iterate through the holes that are strictly INSIDE the new valid range
|
||||
while (holePtr < removeCount)
|
||||
{
|
||||
int holeIndex = sortedIndicesToRemove[holePtr];
|
||||
|
||||
// If the current hole is beyond the new count, it's in the "Drop Zone".
|
||||
// Since the list is sorted, all subsequent holes are also in the drop zone.
|
||||
// We are done filling holes.
|
||||
if (holeIndex >= newCount)
|
||||
break;
|
||||
|
||||
// --- Find a Valid Filler ---
|
||||
// We look for an entity at the end of the array that IS NOT scheduled for removal.
|
||||
while (candidateIndex >= newCount)
|
||||
{
|
||||
// Check if the current candidate is actually marked for removal
|
||||
bool isCandidateRemoved = false;
|
||||
|
||||
// Because sortedIndices is sorted, we check the end of the list
|
||||
// to see if the candidateIndex matches a removal request.
|
||||
if (removalTailPtr >= 0 && sortedIndicesToRemove[removalTailPtr] == candidateIndex)
|
||||
{
|
||||
isCandidateRemoved = true;
|
||||
removalTailPtr--; // Consume this removal
|
||||
}
|
||||
|
||||
if (!isCandidateRemoved)
|
||||
{
|
||||
// Found a valid filler!
|
||||
break;
|
||||
}
|
||||
|
||||
// This candidate was also removed, so skip it and keep looking left
|
||||
candidateIndex--;
|
||||
}
|
||||
|
||||
// --- Perform The Swap ---
|
||||
// Move 'candidateIndex' (Filler) into 'holeIndex' (Hole)
|
||||
|
||||
var pFillerEntity = chunkBase + _entityIdsOffset + (sizeof(Entity) * candidateIndex);
|
||||
var pHoleEntity = chunkBase + _entityIdsOffset + (sizeof(Entity) * holeIndex);
|
||||
|
||||
// 1. Update the Map (Critical Step)
|
||||
// We tell the world: "The entity that WAS at 'candidateIndex' is now at 'holeIndex'"
|
||||
var result = world.EntityManager.UpdateEntityLocation(*(Entity*)pFillerEntity, _id, chunkIndex, holeIndex);
|
||||
if (result != ErrorStatus.None)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
// 2. Overwrite Entity ID
|
||||
MemoryUtility.MemCpy(pHoleEntity, pFillerEntity, (nuint)sizeof(Entity));
|
||||
|
||||
// 3. Overwrite Components
|
||||
for (var i = 0; i < _layouts.Count; i++)
|
||||
{
|
||||
var layout = _layouts[i];
|
||||
var pRow = chunkBase + layout.offset + (layout.size * holeIndex);
|
||||
var pLast = chunkBase + layout.offset + (layout.size * candidateIndex);
|
||||
MemoryUtility.MemCpy(pRow, pLast, (nuint)layout.size);
|
||||
}
|
||||
|
||||
// Prepare for next hole
|
||||
holePtr++;
|
||||
candidateIndex--;
|
||||
}
|
||||
|
||||
chunk._count = newCount;
|
||||
chunk._structuralVersion = world.Version;
|
||||
|
||||
return ErrorStatus.None;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly bool HasComponent(Identifier<IComponent> componentID)
|
||||
{
|
||||
return _signature.IsSet(componentID);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void AddEdgeAdd(Identifier<IComponent> componentID, Identifier<Archetype> targetArchetype)
|
||||
{
|
||||
_edgesAdd.Add(new Edge
|
||||
{
|
||||
componentID = componentID,
|
||||
targetArchetype = targetArchetype
|
||||
});
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly Identifier<Archetype> GetEdgeAdd(Identifier<IComponent> componentID)
|
||||
{
|
||||
for (var i = 0; i < _edgesAdd.Count; i++)
|
||||
{
|
||||
var edge = _edgesAdd[i];
|
||||
if (edge.componentID == componentID)
|
||||
{
|
||||
return edge.targetArchetype;
|
||||
}
|
||||
}
|
||||
|
||||
return Identifier<Archetype>.Invalid;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void AddEdgeRemove(Identifier<IComponent> componentID, Identifier<Archetype> targetArchetype)
|
||||
{
|
||||
_edgesRemove.Add(new Edge
|
||||
{
|
||||
componentID = componentID,
|
||||
targetArchetype = targetArchetype
|
||||
});
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly Identifier<Archetype> GetEdgeRemove(Identifier<IComponent> componentID)
|
||||
{
|
||||
for (var i = 0; i < _edgesRemove.Count; i++)
|
||||
{
|
||||
var edge = _edgesRemove[i];
|
||||
if (edge.componentID == componentID)
|
||||
{
|
||||
return edge.targetArchetype;
|
||||
}
|
||||
}
|
||||
|
||||
return Identifier<Archetype>.Invalid;
|
||||
}
|
||||
|
||||
public override readonly int GetHashCode()
|
||||
{
|
||||
return _hash;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_chunks.IsCreated)
|
||||
{
|
||||
foreach (ref var chunk in _chunks)
|
||||
{
|
||||
chunk.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
_signature.Dispose();
|
||||
_chunks.Dispose();
|
||||
_componentIDToLayoutIndex.Dispose();
|
||||
_layouts.Dispose();
|
||||
_edgesAdd.Dispose();
|
||||
_edgesRemove.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
global using EntityID = System.Int32;
|
||||
global using GenerationID = System.UInt16;
|
||||
global using WorldID = System.UInt16;
|
||||
global using GenerationID = System.Int32;
|
||||
|
||||
using Ghost.Core.Attributes;
|
||||
using System.Runtime.CompilerServices;
|
||||
@@ -10,3 +9,4 @@ using System.Runtime.CompilerServices;
|
||||
[assembly: InternalsVisibleTo("Ghost.Entities.Test")]
|
||||
|
||||
[assembly: EngineAssembly]
|
||||
|
||||
|
||||
19
Ghost.Entities/Common.cs
Normal file
19
Ghost.Entities/Common.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
namespace Ghost.Entities;
|
||||
|
||||
public readonly struct TimeData
|
||||
{
|
||||
public int FrameCount
|
||||
{
|
||||
get; init;
|
||||
}
|
||||
|
||||
public float DeltaTime
|
||||
{
|
||||
get; init;
|
||||
}
|
||||
|
||||
public double ElapsedTime
|
||||
{
|
||||
get; init;
|
||||
}
|
||||
}
|
||||
125
Ghost.Entities/Component.cs
Normal file
125
Ghost.Entities/Component.cs
Normal file
@@ -0,0 +1,125 @@
|
||||
using Ghost.Core;
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
using Misaki.HighPerformance.LowLevel.Utilities;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ghost.Entities;
|
||||
|
||||
public interface IComponent
|
||||
{
|
||||
}
|
||||
|
||||
public interface IEnableableComponent : IComponent
|
||||
{
|
||||
}
|
||||
|
||||
internal struct ComponentInfo
|
||||
{
|
||||
// public string stableName; // Do we actually need this?
|
||||
public int id;
|
||||
public int size;
|
||||
public int alignment;
|
||||
public bool isEnableable;
|
||||
}
|
||||
|
||||
public static class ComponentTypeID<T>
|
||||
where T : unmanaged, IComponent
|
||||
{
|
||||
public static readonly Identifier<IComponent> value = ComponentRegister.GetOrRegisterComponent<T>();
|
||||
}
|
||||
|
||||
internal static class ComponentRegister
|
||||
{
|
||||
private static readonly List<ComponentInfo> s_registeredComponents = new();
|
||||
private static readonly Dictionary<IntPtr, int> s_typeHandleToID = new();
|
||||
private static readonly Dictionary<string, int> s_nameToRuntimeID = new();
|
||||
#if DEBUG || GHOST_EDITOR
|
||||
internal static readonly Dictionary<int, Type> s_runtimeIDToType = new();
|
||||
#endif
|
||||
|
||||
public static unsafe Identifier<IComponent> GetOrRegisterComponent<T>()
|
||||
where T : unmanaged, IComponent
|
||||
{
|
||||
var type = typeof(T);
|
||||
var typeHandle = type.TypeHandle.Value;
|
||||
|
||||
lock (s_registeredComponents)
|
||||
{
|
||||
if (s_typeHandleToID.TryGetValue(typeHandle, out var existingID))
|
||||
{
|
||||
return existingID;
|
||||
}
|
||||
|
||||
var newID = new Identifier<IComponent>(s_registeredComponents.Count);
|
||||
var stableName = typeof(T).FullName ?? typeof(T).Name;
|
||||
var info = new ComponentInfo
|
||||
{
|
||||
// stableName = new FixedText64(stableName),
|
||||
id = newID,
|
||||
size = sizeof(T),
|
||||
alignment = (int)MemoryUtility.AlignOf<T>(),
|
||||
isEnableable = typeof(IEnableableComponent).IsAssignableFrom(type),
|
||||
// isManaged = typeof(IManagedWrapper).IsAssignableFrom(type),
|
||||
};
|
||||
|
||||
s_registeredComponents.Add(info);
|
||||
|
||||
s_typeHandleToID[typeHandle] = newID;
|
||||
s_nameToRuntimeID[stableName] = newID;
|
||||
#if DEBUG || GHOST_EDITOR
|
||||
s_runtimeIDToType[newID.value] = typeof(T);
|
||||
#endif
|
||||
|
||||
return newID;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Identifier<IComponent> GetComponentID(Type type)
|
||||
{
|
||||
var typeHandle = type.TypeHandle.Value;
|
||||
lock (s_registeredComponents)
|
||||
{
|
||||
if (s_typeHandleToID.TryGetValue(typeHandle, out var existingID))
|
||||
{
|
||||
return existingID;
|
||||
}
|
||||
}
|
||||
|
||||
return Identifier<IComponent>.Invalid;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ComponentInfo GetComponentInfo(Identifier<IComponent> typeId)
|
||||
{
|
||||
lock (s_registeredComponents)
|
||||
{
|
||||
return s_registeredComponents[typeId];
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: A ComponentSet structure to cache the hashcode for better performance.
|
||||
public static int GetHashCode(params ReadOnlySpan<Identifier<IComponent>> componentTypeIDs)
|
||||
{
|
||||
var largestID = 0;
|
||||
foreach (var id in componentTypeIDs)
|
||||
{
|
||||
if (id.value > largestID)
|
||||
{
|
||||
largestID = id.value;
|
||||
}
|
||||
}
|
||||
|
||||
var length = UnsafeBitSet.RequiredLength(largestID + 1);
|
||||
var bits = (Span<uint>)stackalloc uint[length];
|
||||
bits.Clear();
|
||||
|
||||
var bitSet = new SpanBitSet(bits);
|
||||
foreach (var id in componentTypeIDs)
|
||||
{
|
||||
bitSet.SetBit(id.value);
|
||||
}
|
||||
|
||||
return bitSet.GetHashCode();
|
||||
}
|
||||
}
|
||||
@@ -1,715 +0,0 @@
|
||||
using Ghost.Core;
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ghost.Entities.Components;
|
||||
|
||||
internal static class SingletonContainer<T>
|
||||
where T : unmanaged, IComponentData
|
||||
{
|
||||
public static readonly Dictionary<int, T> container = new();
|
||||
}
|
||||
|
||||
internal interface IComponentPool : IDisposable
|
||||
{
|
||||
public EntityID Count
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public void Add(Entity entity, IComponentData component);
|
||||
public bool Remove(Entity entity);
|
||||
public bool Has(Entity entity);
|
||||
public IComponentData Get(Entity entity);
|
||||
public IntPtr GetUnsafe(Entity entity);
|
||||
public void Set(Entity entity, in IComponentData component);
|
||||
|
||||
public IEnumerable<(Entity entity, IComponentData component)> Enumerate();
|
||||
}
|
||||
|
||||
internal interface IComponentPool<T> : IComponentPool
|
||||
where T : IComponentData
|
||||
{
|
||||
public void Add(Entity entity, T Component);
|
||||
public void Set(Entity entity, in T component);
|
||||
}
|
||||
|
||||
internal class ComponentPool<T> : IComponentPool<T>
|
||||
where T : unmanaged, IComponentData
|
||||
{
|
||||
private struct ComponentMetadata
|
||||
{
|
||||
public T data;
|
||||
public Entity owner;
|
||||
}
|
||||
|
||||
private EntityID _nextId;
|
||||
private EntityID _capacity;
|
||||
|
||||
private ComponentMetadata[] _components;
|
||||
private EntityID[] _lookup;
|
||||
|
||||
public EntityID Count => _nextId;
|
||||
|
||||
public ComponentPool(int initialSize)
|
||||
{
|
||||
_nextId = 0;
|
||||
_capacity = initialSize;
|
||||
|
||||
_components = new ComponentMetadata[initialSize];
|
||||
_lookup = new EntityID[initialSize];
|
||||
|
||||
_lookup.AsSpan().Fill(Entity.INVALID_ID);
|
||||
}
|
||||
|
||||
public ComponentPool()
|
||||
: this(16)
|
||||
{
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static EntityID GetLookupIndex(Entity entity)
|
||||
{
|
||||
return entity.ID;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private EntityID GetComponentIndex(Entity entity)
|
||||
{
|
||||
return _lookup[GetLookupIndex(entity)];
|
||||
}
|
||||
|
||||
public void Add(Entity entity, IComponentData component)
|
||||
{
|
||||
if (component is not T typedComponent)
|
||||
{
|
||||
throw new ArgumentException($"Component type mismatch. Expected {typeof(T)}, but got {component.GetType()}.");
|
||||
}
|
||||
|
||||
Add(entity, typedComponent);
|
||||
}
|
||||
|
||||
public void Add(Entity entity, T component)
|
||||
{
|
||||
if (!entity.IsValid)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var lookupIndex = GetLookupIndex(entity);
|
||||
var componentIndex = GetComponentIndex(entity);
|
||||
|
||||
if (componentIndex != Entity.INVALID_ID)
|
||||
{
|
||||
// Overwrite the old data if generation is larger
|
||||
if (entity.Generation > _components[componentIndex].owner.Generation)
|
||||
{
|
||||
var index = _lookup[lookupIndex];
|
||||
_components[index].data = component;
|
||||
_components[index].owner = entity;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (_nextId >= _capacity)
|
||||
{
|
||||
var newCapacity = _capacity * 2;
|
||||
Array.Resize(ref _components, newCapacity);
|
||||
Array.Resize(ref _lookup, newCapacity);
|
||||
_lookup.AsSpan(_capacity, newCapacity - _capacity).Fill(Entity.INVALID_ID);
|
||||
|
||||
_capacity = newCapacity;
|
||||
}
|
||||
|
||||
_lookup[lookupIndex] = _nextId;
|
||||
_components[_nextId] = new ComponentMetadata
|
||||
{
|
||||
data = component,
|
||||
owner = entity
|
||||
};
|
||||
|
||||
_nextId++;
|
||||
}
|
||||
|
||||
public bool Remove(Entity entity)
|
||||
{
|
||||
// We do not remove anything here, the generation of the entity will be used to determine if the component is valid.
|
||||
return true;
|
||||
}
|
||||
|
||||
public IComponentData Get(Entity entity)
|
||||
{
|
||||
return GetRef(entity);
|
||||
}
|
||||
|
||||
public unsafe IntPtr GetUnsafe(Entity entity)
|
||||
{
|
||||
return (IntPtr)Unsafe.AsPointer(ref GetRef(entity));
|
||||
}
|
||||
|
||||
public ref T GetRef(Entity entity)
|
||||
{
|
||||
if (!entity.IsValid)
|
||||
{
|
||||
return ref Unsafe.NullRef<T>();
|
||||
}
|
||||
|
||||
var index = GetComponentIndex(entity);
|
||||
return ref _components[index].data;
|
||||
}
|
||||
|
||||
public bool Has(Entity entity)
|
||||
{
|
||||
if (entity.ID >= _lookup.Length)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var index = GetComponentIndex(entity);
|
||||
return index != Entity.INVALID_ID && _components[index].owner.Generation == entity.Generation;
|
||||
}
|
||||
|
||||
public void Set(Entity entity, in IComponentData component)
|
||||
{
|
||||
if (component is not T typedComponent)
|
||||
{
|
||||
throw new ArgumentException($"Component type mismatch. Expected {typeof(T)}, but got {component.GetType()}.");
|
||||
}
|
||||
Set(entity, typedComponent);
|
||||
}
|
||||
|
||||
public void Set(Entity entity, in T component)
|
||||
{
|
||||
if (!entity.IsValid || entity.ID >= _lookup.Length || GetComponentIndex(entity) == Entity.INVALID_ID)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var index = GetComponentIndex(entity);
|
||||
_components[index].data = component;
|
||||
_components[index].owner = entity;
|
||||
}
|
||||
|
||||
public IEnumerable<(Entity entity, IComponentData component)> Enumerate()
|
||||
{
|
||||
for (var i = 0; i < _nextId; i++)
|
||||
{
|
||||
if (_components[i].owner.IsValid)
|
||||
{
|
||||
yield return (_components[i].owner, _components[i].data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_components = Array.Empty<ComponentMetadata>();
|
||||
_lookup = Array.Empty<EntityID>();
|
||||
_nextId = 0;
|
||||
_capacity = 0;
|
||||
}
|
||||
}
|
||||
|
||||
internal class ScriptComponentPool : IComponentPool<ScriptComponent>
|
||||
{
|
||||
private Dictionary<Entity, List<ScriptComponent>>? _scriptComponents;
|
||||
private List<ScriptComponent>? _executionList;
|
||||
|
||||
private readonly World _world;
|
||||
|
||||
internal IReadOnlyDictionary<Entity, List<ScriptComponent>>? ScriptComponents => _scriptComponents;
|
||||
internal IReadOnlyList<ScriptComponent>? ExecutionList => _executionList;
|
||||
|
||||
public bool IsInitialized => _scriptComponents != null;
|
||||
public int Count => _scriptComponents?.Keys.Count ?? 0;
|
||||
|
||||
public ScriptComponentPool(World world)
|
||||
{
|
||||
_world = world;
|
||||
}
|
||||
|
||||
internal void Initialize(int capacity = 16)
|
||||
{
|
||||
_scriptComponents ??= new(capacity);
|
||||
}
|
||||
|
||||
internal void RebuildExecutionList()
|
||||
{
|
||||
if (_scriptComponents == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_executionList ??= new List<ScriptComponent>(_scriptComponents.Count);
|
||||
_executionList.Clear();
|
||||
|
||||
foreach (var kvp in _scriptComponents)
|
||||
{
|
||||
_executionList.AddRange(kvp.Value);
|
||||
}
|
||||
|
||||
_executionList.Sort((a, b) => a.ExecutionOrder.CompareTo(b.ExecutionOrder));
|
||||
}
|
||||
|
||||
public void Add(Entity entity, IComponentData component)
|
||||
{
|
||||
if (component is not ScriptComponent scriptComponent)
|
||||
{
|
||||
throw new ArgumentException($"Component type mismatch. Expected {typeof(ScriptComponent)}, but got {component.GetType()}.");
|
||||
}
|
||||
|
||||
Add(entity, scriptComponent);
|
||||
}
|
||||
|
||||
public void Add(Entity entity, ScriptComponent component)
|
||||
{
|
||||
if (!IsInitialized)
|
||||
{
|
||||
Initialize();
|
||||
}
|
||||
|
||||
ref var scriptList = ref CollectionsMarshal.GetValueRefOrAddDefault(_scriptComponents!, entity, out var exists);
|
||||
scriptList ??= new List<ScriptComponent>();
|
||||
|
||||
scriptList.Add(component);
|
||||
|
||||
component.Owner = entity;
|
||||
component._world = _world;
|
||||
component.Enable = true;
|
||||
component.Initialize();
|
||||
}
|
||||
|
||||
public bool Remove<T>(Entity entity)
|
||||
where T : ScriptComponent
|
||||
{
|
||||
if (!Has(entity)
|
||||
|| !_scriptComponents!.TryGetValue(entity, out var scriptList)
|
||||
|| scriptList == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var scriptToRemove = scriptList.FirstOrDefault(script => script is T);
|
||||
if (scriptToRemove == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
scriptToRemove.OnDestroy();
|
||||
scriptList.Remove(scriptToRemove);
|
||||
if (scriptList.Count == 0)
|
||||
{
|
||||
_scriptComponents.Remove(entity);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool RemoveAt(Entity entity, int index)
|
||||
{
|
||||
if (!Has(entity)
|
||||
|| !_scriptComponents!.TryGetValue(entity, out var scriptList)
|
||||
|| scriptList == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (index < 0 || index > scriptList.Count)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
scriptList.RemoveAt(index);
|
||||
if (scriptList.Count == 0)
|
||||
{
|
||||
_scriptComponents.Remove(entity);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool Remove(Entity entity)
|
||||
{
|
||||
if (!Has(entity)
|
||||
|| !_scriptComponents!.TryGetValue(entity, out var scriptList)
|
||||
|| scriptList == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (var script in scriptList)
|
||||
{
|
||||
script.OnDestroy();
|
||||
}
|
||||
|
||||
_scriptComponents.Remove(entity);
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool Has(Entity entity)
|
||||
{
|
||||
return _scriptComponents?.ContainsKey(entity) ?? false;
|
||||
}
|
||||
|
||||
[Obsolete("Use GetAll instead of Get for ScriptComponentPool.")]
|
||||
public IComponentData Get(Entity entity)
|
||||
{
|
||||
if (!Has(entity)
|
||||
|| !_scriptComponents!.TryGetValue(entity, out var scriptList)
|
||||
|| scriptList == null
|
||||
|| scriptList.Count == 0)
|
||||
{
|
||||
return null!;
|
||||
}
|
||||
|
||||
return scriptList[0];
|
||||
}
|
||||
|
||||
[Obsolete("Use GetAll instead of GetUnsafe for ScriptComponentPool.")]
|
||||
public unsafe IntPtr GetUnsafe(Entity entity)
|
||||
{
|
||||
if (!Has(entity)
|
||||
|| !_scriptComponents!.TryGetValue(entity, out var scriptList)
|
||||
|| scriptList == null
|
||||
|| scriptList.Count == 0)
|
||||
{
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
|
||||
return (IntPtr)Unsafe.AsPointer(ref CollectionsMarshal.AsSpan(scriptList)[0]);
|
||||
}
|
||||
|
||||
public void Set(Entity entity, in IComponentData component)
|
||||
{
|
||||
if (component is not ScriptComponent scriptComponent)
|
||||
{
|
||||
throw new ArgumentException($"Component type mismatch. Expected {typeof(ScriptComponent)}, but got {component.GetType()}.");
|
||||
}
|
||||
Set(entity, scriptComponent);
|
||||
}
|
||||
|
||||
public void Set(Entity entity, in ScriptComponent component)
|
||||
{
|
||||
if (!Has(entity)
|
||||
|| !_scriptComponents!.TryGetValue(entity, out var scriptList)
|
||||
|| scriptList == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var index = scriptList.IndexOf(component);
|
||||
if (index >= 0)
|
||||
{
|
||||
scriptList[index] = component;
|
||||
component.Owner = entity;
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<(Entity entity, IComponentData component)> Enumerate()
|
||||
{
|
||||
if (_scriptComponents == null)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
foreach (var kvp in _scriptComponents)
|
||||
{
|
||||
foreach (var script in kvp.Value)
|
||||
{
|
||||
yield return (kvp.Key, script);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IReadOnlyList<IComponentData>? GetAll(Entity entity)
|
||||
{
|
||||
if (_scriptComponents == null
|
||||
|| !_scriptComponents.TryGetValue(entity, out var scriptList))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return scriptList;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_scriptComponents != null)
|
||||
{
|
||||
if (_executionList != null)
|
||||
{
|
||||
foreach (var script in _executionList)
|
||||
{
|
||||
script.OnDestroy();
|
||||
}
|
||||
|
||||
_executionList.Clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var scriptList in _scriptComponents.Values)
|
||||
{
|
||||
if (scriptList == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var script in scriptList)
|
||||
{
|
||||
script.OnDestroy();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
_scriptComponents.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[SkipLocalsInit]
|
||||
internal struct ComponentStorage : IDisposable
|
||||
{
|
||||
private static int s_nextId = 0;
|
||||
private static class TypeID<T>
|
||||
{
|
||||
public static readonly int value = s_nextId++;
|
||||
}
|
||||
|
||||
private int _currentCapacity = 16;
|
||||
|
||||
private IComponentPool?[] _componentPools = new IComponentPool[16];
|
||||
private UnsafeBitSet[] _componentEntityMasks = new UnsafeBitSet[16];
|
||||
|
||||
private readonly Dictionary<TypeHandle, int> _typeIDMap = new(16);
|
||||
private readonly Dictionary<int, TypeHandle> _typeHandleMap = new(16);
|
||||
|
||||
private readonly ScriptComponentPool _scriptComponentPool;
|
||||
|
||||
private readonly World _world;
|
||||
|
||||
internal ComponentStorage(World world)
|
||||
{
|
||||
_world = world;
|
||||
_scriptComponentPool = new ScriptComponentPool(world);
|
||||
}
|
||||
|
||||
internal readonly IReadOnlyList<IComponentPool?> ComponentPools => _componentPools;
|
||||
internal readonly IReadOnlyList<UnsafeBitSet> ComponentEntityMasks => _componentEntityMasks;
|
||||
internal readonly ScriptComponentPool ScriptComponentPool => _scriptComponentPool;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private readonly int GetTypeID(TypeHandle typeHandle)
|
||||
{
|
||||
if (_typeIDMap.TryGetValue(typeHandle, out var id))
|
||||
{
|
||||
return id;
|
||||
}
|
||||
|
||||
return typeof(TypeID<>).MakeGenericType(typeHandle!)
|
||||
.GetField("value", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public)
|
||||
?.GetValue(null) as int? ?? -1;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private readonly int GetTypeID<T>()
|
||||
{
|
||||
return TypeID<T>.value;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void Resize(int newCapacity)
|
||||
{
|
||||
Array.Resize(ref _componentPools, newCapacity);
|
||||
Array.Resize(ref _componentEntityMasks, newCapacity);
|
||||
_currentCapacity = newCapacity;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal readonly TypeHandle GetComponentPoolType(int poolIndex)
|
||||
{
|
||||
if (poolIndex < 0 || poolIndex >= _currentCapacity)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(poolIndex), "Invalid pool index.");
|
||||
}
|
||||
|
||||
return _typeHandleMap[poolIndex];
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly bool TryGetPool(TypeHandle typeHandle, [NotNullWhen(true)] out IComponentPool? pool)
|
||||
{
|
||||
var result = _typeIDMap.TryGetValue(typeHandle, out var id);
|
||||
if (!result || id >= _currentCapacity)
|
||||
{
|
||||
pool = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
pool = _componentPools[id];
|
||||
return pool != null;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly bool TryGetPool(Type type, [NotNullWhen(true)] out IComponentPool? pool)
|
||||
{
|
||||
return TryGetPool(TypeHandle.Get(type), out pool);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly bool TryGetPool<T>([NotNullWhen(true)] out ComponentPool<T>? pool)
|
||||
where T : unmanaged, IComponentData
|
||||
{
|
||||
var id = TypeID<T>.value;
|
||||
if (id >= _currentCapacity)
|
||||
{
|
||||
pool = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
pool = (ComponentPool<T>?)_componentPools[id];
|
||||
return pool != null;
|
||||
}
|
||||
|
||||
public IComponentPool GetOrCreateComponentPool(Type type)
|
||||
{
|
||||
var typeHandle = TypeHandle.Get(type);
|
||||
if (_typeIDMap.TryGetValue(typeHandle, out var id))
|
||||
{
|
||||
if (id < _currentCapacity && _componentPools[id] is IComponentPool existingPool)
|
||||
{
|
||||
return existingPool;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
id = GetTypeID(typeHandle);
|
||||
}
|
||||
|
||||
if (id >= _currentCapacity)
|
||||
{
|
||||
Resize(_currentCapacity * 2);
|
||||
_typeIDMap[typeHandle] = id;
|
||||
_typeHandleMap[id] = typeHandle;
|
||||
}
|
||||
else if (_componentPools[id] is IComponentPool existingPool)
|
||||
{
|
||||
return existingPool;
|
||||
}
|
||||
|
||||
var pool = Activator.CreateInstance(typeof(ComponentPool<>).MakeGenericType(type)) as IComponentPool
|
||||
?? throw new InvalidOperationException($"Failed to create component pool for type {type.FullName}");
|
||||
|
||||
_componentPools[id] = pool;
|
||||
|
||||
return pool;
|
||||
}
|
||||
|
||||
public ComponentPool<T> GetOrCreateComponentPool<T>()
|
||||
where T : unmanaged, IComponentData
|
||||
{
|
||||
var id = TypeID<T>.value;
|
||||
var typeHandle = TypeHandle.Get<T>();
|
||||
|
||||
if (id >= _currentCapacity)
|
||||
{
|
||||
Resize(_currentCapacity * 2);
|
||||
_typeIDMap[typeHandle] = id;
|
||||
_typeHandleMap[id] = typeHandle;
|
||||
}
|
||||
else if (_componentPools[id] is ComponentPool<T> existingPool)
|
||||
{
|
||||
return existingPool;
|
||||
}
|
||||
|
||||
var pool = new ComponentPool<T>();
|
||||
_componentPools[id] = pool;
|
||||
|
||||
return pool;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly bool TryGetMask(TypeHandle typeHandle, [NotNullWhen(true)] out UnsafeBitSet? bitSet)
|
||||
{
|
||||
if (!_typeIDMap.TryGetValue(typeHandle, out var id)
|
||||
|| id >= _currentCapacity)
|
||||
{
|
||||
bitSet = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
bitSet = _componentEntityMasks[id];
|
||||
return bitSet.HasValue;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly bool TryGetMask<T>([NotNullWhen(true)] out UnsafeBitSet? bitSet)
|
||||
where T : unmanaged, IComponentData
|
||||
{
|
||||
return TryGetMask(TypeHandle.Get<T>(), out bitSet);
|
||||
}
|
||||
|
||||
public ref UnsafeBitSet GetOrCreateMask(TypeHandle typeHandle)
|
||||
{
|
||||
if (!_typeIDMap.TryGetValue(typeHandle, out var id))
|
||||
{
|
||||
id = GetTypeID(typeHandle);
|
||||
_typeIDMap[typeHandle] = id;
|
||||
_typeHandleMap[id] = typeHandle;
|
||||
}
|
||||
|
||||
if (id >= _currentCapacity)
|
||||
{
|
||||
Resize(_currentCapacity * 2);
|
||||
}
|
||||
|
||||
ref var set = ref _componentEntityMasks[id];
|
||||
if (!set.IsCreated)
|
||||
{
|
||||
set = new UnsafeBitSet();
|
||||
}
|
||||
|
||||
return ref set;
|
||||
}
|
||||
|
||||
public ref UnsafeBitSet GetOrCreateMask<T>()
|
||||
where T : unmanaged, IComponentData
|
||||
{
|
||||
return ref GetOrCreateMask(TypeHandle.Get<T>());
|
||||
}
|
||||
|
||||
public ref UnsafeBitSet GetOrCreateMask(Type type)
|
||||
{
|
||||
return ref GetOrCreateMask(TypeHandle.Get(type));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly void RebuildExecutionList()
|
||||
{
|
||||
_scriptComponentPool.RebuildExecutionList();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly void Remove(Entity entity)
|
||||
{
|
||||
_scriptComponentPool.Remove(entity);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly void Dispose()
|
||||
{
|
||||
foreach (var pool in _componentPools)
|
||||
{
|
||||
pool?.Dispose();
|
||||
}
|
||||
|
||||
foreach (var bitSet in _componentEntityMasks)
|
||||
{
|
||||
bitSet.Dispose();
|
||||
}
|
||||
|
||||
_scriptComponentPool.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
namespace Ghost.Entities.Components;
|
||||
|
||||
public interface IComponentData
|
||||
{
|
||||
}
|
||||
@@ -1,110 +0,0 @@
|
||||
namespace Ghost.Entities.Components;
|
||||
|
||||
public abstract class ScriptComponent : IComponentData
|
||||
{
|
||||
private bool _enable;
|
||||
|
||||
internal World _world = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a Value indicating whether this script component is enabled.
|
||||
/// </summary>
|
||||
public bool Enable
|
||||
{
|
||||
get => _enable;
|
||||
set
|
||||
{
|
||||
if (_enable == value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_enable = value;
|
||||
if (_enable)
|
||||
{
|
||||
OnEnable();
|
||||
}
|
||||
else
|
||||
{
|
||||
OnDisable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the entity that owns this script component.
|
||||
/// </summary>
|
||||
public Entity Owner
|
||||
{
|
||||
get;
|
||||
internal set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the EntityManager instance associated with the current world.
|
||||
/// </summary>
|
||||
protected EntityManager EntityManager => _world.EntityManager;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the priority of the script component.
|
||||
/// Change this during runtime does not affect the execution order.
|
||||
/// </summary>
|
||||
public virtual int ExecutionOrder => 0;
|
||||
|
||||
/// <summary>
|
||||
/// Called when the script component is enabled.
|
||||
/// </summary>
|
||||
public virtual void OnEnable()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the script component is disabled.
|
||||
/// </summary>
|
||||
public virtual void OnDisable()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the script component is initialized.
|
||||
/// </summary>
|
||||
public virtual void Initialize()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the script component is started.
|
||||
/// </summary>
|
||||
public virtual void Start()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called every frame.
|
||||
/// </summary>
|
||||
public virtual void Update()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called every frame after all Update methods have been called.
|
||||
/// </summary>
|
||||
public virtual void LateUpdate()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called at a fixed interval.
|
||||
/// This method is called at a fixed time step, independent of the frame rate.
|
||||
/// </summary>
|
||||
public virtual void FixedUpdate()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the script component is destroyed.
|
||||
/// </summary>
|
||||
public virtual void OnDestroy()
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,30 +1,29 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ghost.Entities;
|
||||
|
||||
[SkipLocalsInit]
|
||||
public struct Entity : IEquatable<Entity>, IComparable<Entity>
|
||||
[StructLayout(LayoutKind.Sequential, Size = 8)]
|
||||
public readonly record struct Entity
|
||||
{
|
||||
public const EntityID INVALID_ID = -1;
|
||||
|
||||
[JsonInclude]
|
||||
private EntityID _id;
|
||||
private GenerationID _generation;
|
||||
private readonly EntityID _id;
|
||||
private readonly GenerationID _generation;
|
||||
|
||||
public readonly EntityID ID
|
||||
public EntityID ID
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _id;
|
||||
}
|
||||
|
||||
public readonly GenerationID Generation
|
||||
public GenerationID Generation
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _generation;
|
||||
}
|
||||
|
||||
public readonly bool IsValid
|
||||
public bool IsValid
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => ID != INVALID_ID;
|
||||
@@ -42,40 +41,7 @@ public struct Entity : IEquatable<Entity>, IComparable<Entity>
|
||||
_generation = generation;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal void IncrementGeneration() => _generation++;
|
||||
|
||||
public readonly bool Equals(Entity other)
|
||||
{
|
||||
return _id == other._id && _generation == other._generation;
|
||||
}
|
||||
|
||||
public readonly int CompareTo(Entity other)
|
||||
{
|
||||
return _id.CompareTo(other._id);
|
||||
}
|
||||
|
||||
public override readonly bool Equals(object? obj)
|
||||
{
|
||||
return obj is Entity other && Equals(other);
|
||||
}
|
||||
|
||||
public override readonly int GetHashCode()
|
||||
{
|
||||
return _id.GetHashCode();
|
||||
}
|
||||
|
||||
public static bool operator ==(Entity left, Entity right)
|
||||
{
|
||||
return left.Equals(right);
|
||||
}
|
||||
|
||||
public static bool operator !=(Entity left, Entity right)
|
||||
{
|
||||
return !(left == right);
|
||||
}
|
||||
|
||||
public override readonly string ToString()
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Entity {{ Index: {ID}, Generation: {Generation} }}";
|
||||
}
|
||||
|
||||
222
Ghost.Entities/EntityCommandBuffer.cs
Normal file
222
Ghost.Entities/EntityCommandBuffer.cs
Normal file
@@ -0,0 +1,222 @@
|
||||
using Ghost.Core;
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
using Misaki.HighPerformance.LowLevel.Utilities;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ghost.Entities;
|
||||
|
||||
public unsafe class EntityCommandBuffer : IDisposable
|
||||
{
|
||||
private enum ECBOpCode : byte
|
||||
{
|
||||
CreateEntity,
|
||||
CreateEntityWithComponents,
|
||||
DestroyEntity,
|
||||
AddComponent,
|
||||
RemoveComponent,
|
||||
SetComponent,
|
||||
}
|
||||
|
||||
private readonly EntityManager _entityManager;
|
||||
private UnsafeList<byte> _buffer;
|
||||
private bool _disposed;
|
||||
|
||||
public EntityCommandBuffer(EntityManager entityManager)
|
||||
{
|
||||
_entityManager = entityManager;
|
||||
_buffer = new UnsafeList<byte>(4096, Allocator.Persistent);
|
||||
}
|
||||
|
||||
~EntityCommandBuffer()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
|
||||
private void WriteHeader(ECBOpCode op)
|
||||
{
|
||||
_buffer.Add((byte)op);
|
||||
}
|
||||
|
||||
private void Write<T>(T value)
|
||||
where T : unmanaged
|
||||
{
|
||||
var size = sizeof(T);
|
||||
var idx = _buffer.Count;
|
||||
|
||||
if (idx + size > _buffer.Capacity)
|
||||
{
|
||||
_buffer.Resize(idx + size);
|
||||
}
|
||||
|
||||
MemoryUtility.MemCpy((byte*)_buffer.GetUnsafePtr() + idx, &value, (nuint)size);
|
||||
}
|
||||
|
||||
private void WriteSpan<T>(ReadOnlySpan<T> span)
|
||||
where T : unmanaged
|
||||
{
|
||||
var size = sizeof(T) * span.Length;
|
||||
var idx = _buffer.Count;
|
||||
|
||||
if (idx + size > _buffer.Capacity)
|
||||
{
|
||||
_buffer.Resize(idx + size);
|
||||
}
|
||||
|
||||
fixed (T* ptr = span)
|
||||
{
|
||||
MemoryUtility.MemCpy((byte*)_buffer.GetUnsafePtr() + idx, ptr, (nuint)size);
|
||||
}
|
||||
}
|
||||
|
||||
private T Read<T>(ref int cursor)
|
||||
where T : unmanaged
|
||||
{
|
||||
var size = sizeof(T);
|
||||
var ptr = (byte*)_buffer.GetUnsafePtr();
|
||||
|
||||
var value = *(T*)&ptr[cursor];
|
||||
cursor += size;
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
private Span<T> ReadSpan<T>(ref int cursor, int length)
|
||||
where T : unmanaged
|
||||
{
|
||||
var size = sizeof(T) * length;
|
||||
var ptr = (byte*)_buffer.GetUnsafePtr();
|
||||
|
||||
var span = new Span<T>(&ptr[cursor], length);
|
||||
cursor += size;
|
||||
|
||||
return span;
|
||||
}
|
||||
|
||||
private void* ReadBuffer(ref int cursor, int size)
|
||||
{
|
||||
var ptr = (byte*)_buffer.GetUnsafePtr();
|
||||
var bufferPtr = ptr + cursor;
|
||||
cursor += size;
|
||||
|
||||
return bufferPtr;
|
||||
}
|
||||
|
||||
public void CreateEntity(int count = 1)
|
||||
{
|
||||
WriteHeader(ECBOpCode.CreateEntity);
|
||||
Write(count);
|
||||
}
|
||||
|
||||
public void CreateEntity(int count, params ReadOnlySpan<Identifier<IComponent>> componentTypeIDs)
|
||||
{
|
||||
WriteHeader(ECBOpCode.CreateEntityWithComponents);
|
||||
Write(count);
|
||||
Write(componentTypeIDs.Length);
|
||||
WriteSpan(componentTypeIDs);
|
||||
}
|
||||
|
||||
public void DestroyEntity(Entity entity)
|
||||
{
|
||||
WriteHeader(ECBOpCode.DestroyEntity);
|
||||
Write(entity);
|
||||
}
|
||||
|
||||
public void AddComponent<T>(Entity entity, T component = default)
|
||||
where T : unmanaged, IComponent
|
||||
{
|
||||
WriteHeader(ECBOpCode.AddComponent);
|
||||
Write(entity);
|
||||
Write(ComponentTypeID<T>.value);
|
||||
Write(component);
|
||||
}
|
||||
|
||||
public void RemoveComponent<T>(Entity entity)
|
||||
where T : unmanaged, IComponent
|
||||
{
|
||||
WriteHeader(ECBOpCode.RemoveComponent);
|
||||
Write(entity);
|
||||
Write(ComponentTypeID<T>.value);
|
||||
}
|
||||
|
||||
public void SetComponent<T>(Entity entity, T component)
|
||||
where T : unmanaged, IComponent
|
||||
{
|
||||
WriteHeader(ECBOpCode.SetComponent);
|
||||
Write(entity);
|
||||
Write(ComponentTypeID<T>.value);
|
||||
Write(component);
|
||||
}
|
||||
|
||||
internal void Playback()
|
||||
{
|
||||
var cursor = 0;
|
||||
var length = _buffer.Count;
|
||||
|
||||
while (cursor < length)
|
||||
{
|
||||
var op = Read<ECBOpCode>(ref cursor);
|
||||
|
||||
switch (op)
|
||||
{
|
||||
case ECBOpCode.CreateEntity:
|
||||
var count = Read<int>(ref cursor);
|
||||
_entityManager.CreateEntities(count);
|
||||
break;
|
||||
|
||||
case ECBOpCode.CreateEntityWithComponents:
|
||||
var entityCount = Read<int>(ref cursor);
|
||||
var compCount = Read<int>(ref cursor);
|
||||
var compTypeIDs = ReadSpan<Identifier<IComponent>>(ref cursor, compCount);
|
||||
_entityManager.CreateEntities(entityCount, compTypeIDs);
|
||||
break;
|
||||
|
||||
case ECBOpCode.DestroyEntity:
|
||||
var entityToDestroy = Read<Entity>(ref cursor);
|
||||
_entityManager.DestroyEntity(entityToDestroy);
|
||||
break;
|
||||
|
||||
case ECBOpCode.AddComponent:
|
||||
var entityToAdd = Read<Entity>(ref cursor);
|
||||
var addCompTypeID = Read<Identifier<IComponent>>(ref cursor);
|
||||
var pAddCompData = ReadBuffer(ref cursor, ComponentRegister.GetComponentInfo(addCompTypeID).size);
|
||||
_entityManager.AddComponent(entityToAdd, addCompTypeID, pAddCompData);
|
||||
break;
|
||||
|
||||
case ECBOpCode.RemoveComponent:
|
||||
var entityToRemove = Read<Entity>(ref cursor);
|
||||
var removeCompTypeID = Read<Identifier<IComponent>>(ref cursor);
|
||||
_entityManager.RemoveComponent(entityToRemove, removeCompTypeID);
|
||||
break;
|
||||
|
||||
case ECBOpCode.SetComponent:
|
||||
var entityToSet = Read<Entity>(ref cursor);
|
||||
var setCompTypeID = Read<Identifier<IComponent>>(ref cursor);
|
||||
var pSetCompData = ReadBuffer(ref cursor, ComponentRegister.GetComponentInfo(setCompTypeID).size);
|
||||
_entityManager.SetComponent(entityToSet, setCompTypeID, pSetCompData);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Reset();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal void Reset()
|
||||
{
|
||||
_buffer.Clear();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_buffer.Dispose();
|
||||
|
||||
_disposed = true;
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
224
Ghost.Entities/EntityManager.Managed.cs
Normal file
224
Ghost.Entities/EntityManager.Managed.cs
Normal file
@@ -0,0 +1,224 @@
|
||||
using Misaki.HighPerformance.Collections;
|
||||
using Misaki.HighPerformance.Utilities;
|
||||
|
||||
namespace Ghost.Entities;
|
||||
|
||||
public partial class EntityManager
|
||||
{
|
||||
private readonly SlotMap<List<ScriptComponent>> _scriptComponents;
|
||||
|
||||
internal SlotMap<List<ScriptComponent>> ScriptComponents => _scriptComponents;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new ManagedEntity and associates it with the given Entity.
|
||||
/// </summary>
|
||||
/// <param name="entity">The Entity to associate with the ManagedEntity.</param>
|
||||
/// <returns>The created ManagedEntity.</returns>
|
||||
public ManagedEntity CreateManagedEntity(Entity entity)
|
||||
{
|
||||
var managedEntity = CreateManagedEntity();
|
||||
AddComponent(entity, new ManagedEntityRef
|
||||
{
|
||||
entity = managedEntity
|
||||
});
|
||||
|
||||
return managedEntity;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new ManagedEntity.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// You must call this if you add <see cref="ManagedEntityRef"/> manually to an entity.
|
||||
/// Otherwise, use <see cref="CreateManagedEntity(Entity)"/>.
|
||||
/// </remarks>
|
||||
/// <returns>The created ManagedEntity.</returns>
|
||||
public ManagedEntity CreateManagedEntity()
|
||||
{
|
||||
var id = _scriptComponents.Add(new(8), out var generation);
|
||||
var managedEntity = new ManagedEntity
|
||||
{
|
||||
id = id,
|
||||
generation = generation
|
||||
};
|
||||
|
||||
return managedEntity;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Destroys the given ManagedEntity and calls OnDestroy on all associated ScriptComponents.
|
||||
/// </summary>
|
||||
/// <param name="managedEntity">The ManagedEntity to destroy.</param>
|
||||
public void DestroyManagedEntity(ManagedEntity managedEntity)
|
||||
{
|
||||
if (_scriptComponents.TryGetElement(managedEntity.id, managedEntity.generation, out var scripts))
|
||||
{
|
||||
foreach (var script in scripts)
|
||||
{
|
||||
script.OnDestroy();
|
||||
}
|
||||
|
||||
_scriptComponents.Remove(managedEntity.id, managedEntity.generation);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the given ManagedEntity exists.
|
||||
/// </summary>
|
||||
/// <param name="managedEntity">The ManagedEntity to check.</param>
|
||||
/// <returns>True if the ManagedEntity exists, false otherwise.</returns>
|
||||
public bool Exists(ManagedEntity managedEntity)
|
||||
{
|
||||
return _scriptComponents.Contains(managedEntity.id, managedEntity.generation);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a ScriptComponent of type T to the given ManagedEntity and Entity.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of ScriptComponent to add.</typeparam>
|
||||
/// <param name="managedEntity">The ManagedEntity to add the ScriptComponent to.</
|
||||
/// <param name="entity">The Entity associated with the ManagedEntity.</param>
|
||||
public void AddScriptComponent<T>(ManagedEntity managedEntity, Entity entity)
|
||||
where T : ScriptComponent, new()
|
||||
{
|
||||
if (_scriptComponents.TryGetElement(managedEntity.id, managedEntity.generation, out var scripts))
|
||||
{
|
||||
var script = new T
|
||||
{
|
||||
_world = _world,
|
||||
_entity = entity,
|
||||
_managedEntity = managedEntity
|
||||
};
|
||||
|
||||
scripts.Add(script);
|
||||
script.OnCreate();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException($"ManagedEntity {managedEntity} does not exist.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a ScriptComponent of type T to the given Entity.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of ScriptComponent to add.</typeparam>
|
||||
/// <param name="entity">The Entity to add the ScriptComponent to.</param>
|
||||
public unsafe void AddScriptComponent<T>(Entity entity)
|
||||
where T : ScriptComponent, new()
|
||||
{
|
||||
var location = _entityLocations.GetElementAt(entity.ID, entity.Generation);
|
||||
ref var archetype = ref _world.GetArchetypeReference(location.archetypeID);
|
||||
|
||||
var pManagedEntityRef = (ManagedEntityRef*)archetype.GetComponentData(location.chunkIndex, location.rowIndex, ComponentTypeID<ManagedEntityRef>.value);
|
||||
if (pManagedEntityRef == null)
|
||||
{
|
||||
throw new InvalidOperationException($"Entity {entity} does not have ManagedEntityRef component.");
|
||||
}
|
||||
|
||||
AddScriptComponent<T>(pManagedEntityRef->entity, entity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Destroys the ScriptComponent of type T associated with the given ManagedEntity.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of ScriptComponent to destroy.</typeparam>
|
||||
/// <param name="managedEntity">The ManagedEntity whose ScriptComponent is to be destroyed </param>
|
||||
/// <returns>True if the ScriptComponent was found and destroyed, false otherwise.</returns
|
||||
public bool DestroyScriptComponent<T>(ManagedEntity managedEntity)
|
||||
where T : ScriptComponent
|
||||
{
|
||||
if (_scriptComponents.TryGetElement(managedEntity.id, managedEntity.generation, out var scripts))
|
||||
{
|
||||
for (var i = 0; i < scripts.Count; i++)
|
||||
{
|
||||
if (scripts[i] is T script)
|
||||
{
|
||||
script.OnDestroy();
|
||||
scripts.RemoveAndSwapBack(i);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException($"ManagedEntity {managedEntity} does not exist.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the given ManagedEntity has a ScriptComponent of type T.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of ScriptComponent to check for.</typeparam>
|
||||
/// <param name="managedEntity">The ManagedEntity to check.</param>
|
||||
/// <returns>True if the ManagedEntity has a ScriptComponent of type T, false </returns>
|
||||
public bool HasScriptComponent<T>(ManagedEntity managedEntity)
|
||||
where T : ScriptComponent
|
||||
{
|
||||
if (_scriptComponents.TryGetElement(managedEntity.id, managedEntity.generation, out var scripts))
|
||||
{
|
||||
foreach (var script in scripts)
|
||||
{
|
||||
if (script is T)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException($"ManagedEntity {managedEntity} does not exist.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the ScriptComponent of type T associated with the given ManagedEntity.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of ScriptComponent to get.</typeparam>
|
||||
/// <param name="managedEntity">The ManagedEntity whose ScriptComponent is to be retrieved
|
||||
/// <returns>The ScriptComponent of type T.</returns>
|
||||
public T GetScriptComponent<T>(ManagedEntity managedEntity)
|
||||
where T : ScriptComponent
|
||||
{
|
||||
if (_scriptComponents.TryGetElement(managedEntity.id, managedEntity.generation, out var scripts))
|
||||
{
|
||||
foreach (var script in scripts)
|
||||
{
|
||||
if (script is T typedScript)
|
||||
{
|
||||
return typedScript;
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidOperationException($"ManagedEntity {managedEntity} does not have script component of type {typeof(T)}.");
|
||||
}
|
||||
|
||||
throw new InvalidOperationException($"ManagedEntity {managedEntity} does not exist.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all ScriptComponents of type T associated with the given ManagedEntity.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of ScriptComponent to get.</typeparam>
|
||||
/// <param name="managedEntity">The ManagedEntity whose ScriptComponents are to be retrieved
|
||||
/// <returns>The list of ScriptComponents of type T.</returns>
|
||||
public List<T> GetScriptComponents<T>(ManagedEntity managedEntity)
|
||||
where T : ScriptComponent
|
||||
{
|
||||
if (_scriptComponents.TryGetElement(managedEntity.id, managedEntity.generation, out var scripts))
|
||||
{
|
||||
var result = new List<T>();
|
||||
foreach (var script in scripts)
|
||||
{
|
||||
if (script is T typedScript)
|
||||
{
|
||||
result.Add(typedScript);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException($"ManagedEntity {managedEntity} does not exist.");
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
87
Ghost.Entities/EntityQuery.JobChunk.cs
Normal file
87
Ghost.Entities/EntityQuery.JobChunk.cs
Normal file
@@ -0,0 +1,87 @@
|
||||
using Ghost.Core;
|
||||
using Misaki.HighPerformance.Jobs;
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ghost.Entities;
|
||||
|
||||
public interface IJobChunk
|
||||
{
|
||||
void Execute(ChunkView view, int threadIndex);
|
||||
}
|
||||
|
||||
internal unsafe struct ChunkInfo
|
||||
{
|
||||
public Chunk* pChunk;
|
||||
public Archetype* pArchetype;
|
||||
}
|
||||
|
||||
internal unsafe struct JobChunkBatch<TJob> : IJobParallelFor
|
||||
where TJob : unmanaged, IJobChunk
|
||||
{
|
||||
|
||||
public TJob userJob;
|
||||
public ReadOnlyUnsafeCollection<ChunkInfo> chunkInfos;
|
||||
|
||||
public void Execute(int loopIndex, int threadIndex)
|
||||
{
|
||||
var info = chunkInfos[loopIndex];
|
||||
var view = new ChunkView(in *info.pArchetype, in *info.pChunk);
|
||||
|
||||
userJob.Execute(view, threadIndex);
|
||||
}
|
||||
}
|
||||
|
||||
internal struct DisposeJobChunk : IJob
|
||||
{
|
||||
public UnsafeList<ChunkInfo> list;
|
||||
|
||||
public void Execute(int threadIndex)
|
||||
{
|
||||
list.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe partial struct EntityQuery
|
||||
{
|
||||
public JobHandle ScheduleChunkParallel<TJob>(TJob job, int batchSize, JobHandle dependency)
|
||||
where TJob : unmanaged, IJobChunk
|
||||
{
|
||||
var world = World.GetWorld(_worldID).GetValueOrThrow();
|
||||
|
||||
var chunkInfos = new UnsafeList<ChunkInfo>(_matchingArchetypes.Count * 2, JobScheduler.TempAllocatorHandle);
|
||||
|
||||
foreach (var archID in _matchingArchetypes)
|
||||
{
|
||||
ref var arch = ref world.GetArchetypeReference(archID);
|
||||
|
||||
for (int i = 0; i < arch.ChunkCount; i++)
|
||||
{
|
||||
var pChunk = (Chunk*)arch._chunks.GetUnsafePtr() + i;
|
||||
|
||||
chunkInfos.Add(new ChunkInfo
|
||||
{
|
||||
pArchetype = (Archetype*)Unsafe.AsPointer(ref arch),
|
||||
pChunk = pChunk
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
var batchJob = new JobChunkBatch<TJob>
|
||||
{
|
||||
userJob = job,
|
||||
chunkInfos = chunkInfos.AsReadOnly()
|
||||
};
|
||||
|
||||
var handle = world.JobScheduler.ScheduleParallel(ref batchJob, chunkInfos.Count, batchSize, dependency);
|
||||
|
||||
var disposeJob = new DisposeJobChunk
|
||||
{
|
||||
list = chunkInfos
|
||||
};
|
||||
|
||||
world.JobScheduler.Schedule(ref disposeJob, handle);
|
||||
|
||||
return handle;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
@@ -8,60 +8,57 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<IsAotCompatible>True</IsAotCompatible>
|
||||
<IsAotCompatible>False</IsAotCompatible>
|
||||
<IsTrimmable>False</IsTrimmable>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
<IsAotCompatible>True</IsAotCompatible>
|
||||
<IsTrimmable>True</IsTrimmable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="Template\ForEach.cs">
|
||||
<Compile Remove="Templates\ForEach.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="Templates\EntityQuery.ForEach.gen.cs">
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>EntityQuery.ForEach.tt</DependentUpon>
|
||||
</None>
|
||||
<None Include="Templates\ForEach.gen.cs">
|
||||
<DependentUpon>ForEach.tt</DependentUpon>
|
||||
</None>
|
||||
<None Include="Template\QueryItem.cs">
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>QueryItem.tt</DependentUpon>
|
||||
</None>
|
||||
<None Include="Template\QueryRefComponent.cs">
|
||||
<None Include="Templates\QueryBuilder.With.gen.cs">
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>QueryRefComponent.tt</DependentUpon>
|
||||
</None>
|
||||
<None Include="Template\World.Query.cs">
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>World.Query.tt</DependentUpon>
|
||||
<DependentUpon>QueryBuilder.With.tt</DependentUpon>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="Template\ForEach.tt">
|
||||
<ProjectReference Include="..\Ghost.Core\Ghost.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="Templates\EntityQuery.ForEach.tt">
|
||||
<Generator>TextTemplatingFileGenerator</Generator>
|
||||
<LastGenOutput>ForEach.cs</LastGenOutput>
|
||||
<LastGenOutput>EntityQuery.ForEach.gen.cs</LastGenOutput>
|
||||
</None>
|
||||
<None Update="Template\QueryEnumerable.tt">
|
||||
<None Update="Templates\ForEach.tt">
|
||||
<LastGenOutput>ForEach.gen.cs</LastGenOutput>
|
||||
<Generator>TextTemplatingFileGenerator</Generator>
|
||||
<LastGenOutput>QueryEnumerable.cs</LastGenOutput>
|
||||
</None>
|
||||
<None Update="Template\Helpers.tt">
|
||||
<Generator>TextTemplatingFilePreprocessor</Generator>
|
||||
<LastGenOutput>Helpers.cs</LastGenOutput>
|
||||
</None>
|
||||
<None Update="Template\QueryItem.tt">
|
||||
<None Update="Templates\EntityQuery.JobEntityParallel.tt">
|
||||
<Generator>TextTemplatingFileGenerator</Generator>
|
||||
<LastGenOutput>QueryItem.cs</LastGenOutput>
|
||||
<LastGenOutput>EntityQuery.JobEntityParallel.gen.cs</LastGenOutput>
|
||||
</None>
|
||||
<None Update="Template\QueryRefComponent.tt">
|
||||
<None Update="Templates\QueryBuilder.With.tt">
|
||||
<Generator>TextTemplatingFileGenerator</Generator>
|
||||
<LastGenOutput>QueryRefComponent.cs</LastGenOutput>
|
||||
</None>
|
||||
<None Update="Template\World.Query.tt">
|
||||
<Generator>TextTemplatingFileGenerator</Generator>
|
||||
<LastGenOutput>World.Query.cs</LastGenOutput>
|
||||
<LastGenOutput>QueryBuilder.With.gen.cs</LastGenOutput>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
@@ -70,44 +67,26 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Update="Template\ForEach.cs">
|
||||
<Compile Update="Templates\EntityQuery.ForEach.gen.cs">
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>EntityQuery.ForEach.tt</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Templates\EntityQuery.JobEntityParallel.gen.cs">
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>EntityQuery.JobEntityParallel.tt</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Templates\ForEach.gen.cs">
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>ForEach.tt</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Template\Helpers.cs">
|
||||
<Compile Update="Templates\QueryBuilder.With.gen.cs">
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>Helpers.tt</DependentUpon>
|
||||
<DependentUpon>QueryBuilder.With.tt</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Template\QueryEnumerable.cs">
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>QueryEnumerable.tt</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Template\QueryItem.cs">
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>QueryItem.tt</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Template\QueryRefComponent.cs">
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>QueryRefComponent.tt</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Template\World.Query.cs">
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>World.Query.tt</DependentUpon>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Helpers\" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Ghost.Core\Ghost.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
256
Ghost.Entities/ManagedComponent.cs
Normal file
256
Ghost.Entities/ManagedComponent.cs
Normal file
@@ -0,0 +1,256 @@
|
||||
#if false
|
||||
using Ghost.Core;
|
||||
using Misaki.HighPerformance.Collections;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ghost.Entities;
|
||||
|
||||
public interface IManagedComponent;
|
||||
public interface IManagedWrapper;
|
||||
|
||||
public readonly struct Managed<T> : IComponent, IManagedWrapper
|
||||
where T : IManagedComponent
|
||||
{
|
||||
public readonly int id;
|
||||
public readonly int generation;
|
||||
|
||||
internal Managed(int id, int generation)
|
||||
{
|
||||
this.id = id;
|
||||
this.generation = generation;
|
||||
}
|
||||
}
|
||||
|
||||
public static class ManagedComponemtnID<T>
|
||||
where T : IManagedComponent
|
||||
{
|
||||
public static readonly Identifier<IManagedComponent> value = ManagedComponentRegister.GetOrRegisterComponent<T>();
|
||||
}
|
||||
|
||||
internal struct ManagedComponentInfo
|
||||
{
|
||||
public int id;
|
||||
public bool isScriptComponent;
|
||||
}
|
||||
|
||||
internal static class ManagedComponentRegister
|
||||
{
|
||||
private static readonly List<ManagedComponentInfo> s_registeredComponents = new();
|
||||
private static readonly Dictionary<IntPtr, int> s_typeHandleToID = new();
|
||||
private static readonly Dictionary<string, int> s_nameToRuntimeID = new();
|
||||
#if DEBUG || GHOST_EDITOR
|
||||
internal static readonly Dictionary<int, Type> s_runtimeIDToType = new();
|
||||
#endif
|
||||
|
||||
public static Identifier<IManagedComponent> GetOrRegisterComponent<T>()
|
||||
where T : IManagedComponent
|
||||
{
|
||||
var typeHandle = typeof(T).TypeHandle.Value;
|
||||
|
||||
lock (s_registeredComponents)
|
||||
{
|
||||
if (s_typeHandleToID.TryGetValue(typeHandle, out var existingID))
|
||||
{
|
||||
return existingID;
|
||||
}
|
||||
|
||||
var newID = new Identifier<IManagedComponent>(s_registeredComponents.Count);
|
||||
var stableName = typeof(T).FullName ?? typeof(T).Name;
|
||||
var info = new ManagedComponentInfo
|
||||
{
|
||||
id = newID,
|
||||
isScriptComponent = typeof(ScriptComponent).IsAssignableFrom(typeof(T)),
|
||||
};
|
||||
|
||||
s_registeredComponents.Add(info);
|
||||
|
||||
s_typeHandleToID[typeHandle] = newID;
|
||||
s_nameToRuntimeID[stableName] = newID;
|
||||
#if DEBUG || GHOST_EDITOR
|
||||
s_runtimeIDToType[newID.value] = typeof(T);
|
||||
#endif
|
||||
|
||||
return newID;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Identifier<IManagedComponent> GetComponentID(Type type)
|
||||
{
|
||||
var typeHandle = type.TypeHandle.Value;
|
||||
lock (s_registeredComponents)
|
||||
{
|
||||
if (s_typeHandleToID.TryGetValue(typeHandle, out var existingID))
|
||||
{
|
||||
return existingID;
|
||||
}
|
||||
}
|
||||
|
||||
return Identifier<IManagedComponent>.Invalid;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ManagedComponentInfo GetComponentInfo(Identifier<IManagedComponent> typeId)
|
||||
{
|
||||
lock (s_registeredComponents)
|
||||
{
|
||||
return s_registeredComponents[typeId];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal interface IManagedComponentStorage
|
||||
{
|
||||
public Identifier<IManagedComponent> TypeID { get; }
|
||||
}
|
||||
|
||||
internal class ManagedComponentStorage<T> : IManagedComponentStorage
|
||||
where T : IManagedComponent
|
||||
{
|
||||
private readonly SlotMap<T> _storage = new(16);
|
||||
|
||||
public Identifier<IManagedComponent> TypeID => ManagedComponemtnID<T>.value;
|
||||
|
||||
public Managed<T> Add(T component)
|
||||
{
|
||||
var id = _storage.Add(component, out var generation);
|
||||
return new Managed<T>(id, generation);
|
||||
}
|
||||
|
||||
public void Remove(Managed<T> managed)
|
||||
{
|
||||
_storage.Remove(managed.id, managed.generation);
|
||||
}
|
||||
|
||||
public ref T GetComponentReference(Managed<T> managed)
|
||||
{
|
||||
return ref _storage.GetElementReferenceAt(managed.id, managed.generation, out _);
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class ScriptComponent : IManagedComponent
|
||||
{
|
||||
internal World _world = null!;
|
||||
internal Entity _entity;
|
||||
|
||||
public World World => _world;
|
||||
public Entity Entity => _entity;
|
||||
|
||||
protected ref T GetComponent<T>()
|
||||
where T : unmanaged, IComponent
|
||||
{
|
||||
return ref _world.EntityManager.GetComponent<T>(_entity);
|
||||
}
|
||||
|
||||
public virtual void OnCreate()
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void OnDestroy()
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void OnEnable()
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void OnDisable()
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void Start()
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void Update()
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void FixedUpdate()
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void LateUpdate()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public partial class EntityManager
|
||||
{
|
||||
private IManagedComponentStorage[] _storages;
|
||||
|
||||
internal IManagedComponentStorage[] Storages => _storages;
|
||||
|
||||
private ManagedComponentStorage<T> GetOrCreateManagedComponentStorage<T>()
|
||||
where T : IManagedComponent
|
||||
{
|
||||
var id = ManagedComponemtnID<T>.value;
|
||||
if (_storages == null || _storages.Length <= id.value)
|
||||
{
|
||||
Array.Resize(ref _storages, id.value + 1);
|
||||
}
|
||||
|
||||
ref var storage = ref _storages[id.value];
|
||||
storage ??= new ManagedComponentStorage<T>();
|
||||
|
||||
return (ManagedComponentStorage<T>)storage;
|
||||
}
|
||||
|
||||
public Managed<T> AddManagedComponent<T>(Entity entity)
|
||||
where T : IManagedComponent, new()
|
||||
{
|
||||
var instance = new T();
|
||||
if (instance is ScriptComponent scriptComponent)
|
||||
{
|
||||
scriptComponent._world = _world;
|
||||
scriptComponent._entity = entity;
|
||||
scriptComponent.OnCreate();
|
||||
}
|
||||
|
||||
var managed = GetOrCreateManagedComponentStorage<T>().Add(instance);
|
||||
AddComponent(entity, managed);
|
||||
|
||||
return managed;
|
||||
}
|
||||
|
||||
public bool RemoveManagedComponent<T>(Entity entity)
|
||||
where T : IManagedComponent
|
||||
{
|
||||
ref var component = ref GetComponent<Managed<T>>(entity);
|
||||
if (!Unsafe.IsNullRef(ref component))
|
||||
{
|
||||
var storage = GetOrCreateManagedComponentStorage<T>();
|
||||
var componentRef = storage.GetComponentReference(component);
|
||||
if (componentRef is ScriptComponent scriptComponent)
|
||||
{
|
||||
scriptComponent.OnDestroy();
|
||||
}
|
||||
|
||||
RemoveComponent<Managed<T>>(entity);
|
||||
storage.Remove(component);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public ref T GetManagedComponent<T>(Entity entity)
|
||||
where T : IManagedComponent
|
||||
{
|
||||
ref var component = ref GetComponent<Managed<T>>(entity);
|
||||
if (Unsafe.IsNullRef(ref component))
|
||||
{
|
||||
return ref Unsafe.NullRef<T>();
|
||||
}
|
||||
|
||||
return ref GetOrCreateManagedComponentStorage<T>().GetComponentReference(component);
|
||||
}
|
||||
|
||||
public ref T GetManagedComponent<T>(Managed<T> managedComponent)
|
||||
where T : IManagedComponent
|
||||
{
|
||||
return ref GetOrCreateManagedComponentStorage<T>().GetComponentReference(managedComponent);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
69
Ghost.Entities/ManagedEntity.cs
Normal file
69
Ghost.Entities/ManagedEntity.cs
Normal file
@@ -0,0 +1,69 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ghost.Entities;
|
||||
|
||||
public record struct ManagedEntity
|
||||
{
|
||||
public int id;
|
||||
public int generation;
|
||||
}
|
||||
|
||||
public struct ManagedEntityRef : IComponent
|
||||
{
|
||||
public ManagedEntity entity;
|
||||
}
|
||||
|
||||
public abstract class ScriptComponent
|
||||
{
|
||||
internal World _world = null!;
|
||||
internal Entity _entity;
|
||||
internal ManagedEntity _managedEntity;
|
||||
|
||||
public World World => _world;
|
||||
public Entity Entity => _entity;
|
||||
public ManagedEntity ManagedEntity => _managedEntity;
|
||||
|
||||
protected ref T GetComponent<T>()
|
||||
where T : unmanaged, IComponent
|
||||
{
|
||||
ref var value = ref _world.EntityManager.GetComponent<T>(_entity);
|
||||
if (Unsafe.IsNullRef(ref value))
|
||||
{
|
||||
throw new InvalidOperationException($"Entity {_entity} does not have component of type {typeof(T)}");
|
||||
}
|
||||
|
||||
return ref value;
|
||||
}
|
||||
|
||||
public virtual void OnCreate()
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void OnDestroy()
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void OnEnable()
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void OnDisable()
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void Start()
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void Update()
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void FixedUpdate()
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void LateUpdate()
|
||||
{
|
||||
}
|
||||
}
|
||||
604
Ghost.Entities/Query.cs
Normal file
604
Ghost.Entities/Query.cs
Normal file
@@ -0,0 +1,604 @@
|
||||
using Ghost.Core;
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ghost.Entities;
|
||||
|
||||
internal struct EntityQueryMask : IDisposable, IEquatable<EntityQueryMask>
|
||||
{
|
||||
public UnsafeBitSet structuralAll;
|
||||
public UnsafeBitSet structuralAny;
|
||||
public UnsafeBitSet structuralAbsent;
|
||||
|
||||
public UnsafeBitSet requireEnabled;
|
||||
public UnsafeBitSet requireDisabled;
|
||||
public UnsafeBitSet rejectIfEnabled;
|
||||
|
||||
public UnsafeBitSet writeAccess;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly bool Matches(ref readonly UnsafeBitSet archetypeSignature)
|
||||
{
|
||||
return (!structuralAll.IsCreated || structuralAll.All(archetypeSignature))
|
||||
&& (!structuralAbsent.IsCreated || structuralAbsent.None(archetypeSignature))
|
||||
&& (!structuralAny.IsCreated || structuralAny.Count == 0 || structuralAny.Any(archetypeSignature));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override readonly int GetHashCode()
|
||||
{
|
||||
var hash = 17;
|
||||
if (structuralAll.IsCreated) hash = hash * 23 + structuralAll.GetHashCode();
|
||||
if (structuralAbsent.IsCreated) hash = hash * 23 + structuralAbsent.GetHashCode();
|
||||
if (structuralAny.IsCreated) hash = hash * 23 + structuralAny.GetHashCode();
|
||||
if (requireEnabled.IsCreated) hash = hash * 23 + requireEnabled.GetHashCode();
|
||||
if (requireDisabled.IsCreated) hash = hash * 23 + requireDisabled.GetHashCode();
|
||||
if (rejectIfEnabled.IsCreated) hash = hash * 23 + rejectIfEnabled.GetHashCode();
|
||||
if (writeAccess.IsCreated) hash = hash * 23 + writeAccess.GetHashCode();
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
public readonly bool Equals(EntityQueryMask other)
|
||||
{
|
||||
return structuralAll.Equals(other.structuralAll)
|
||||
&& structuralAny.Equals(other.structuralAny)
|
||||
&& structuralAbsent.Equals(other.structuralAbsent)
|
||||
&& requireEnabled.Equals(other.requireEnabled)
|
||||
&& requireDisabled.Equals(other.requireDisabled)
|
||||
&& rejectIfEnabled.Equals(other.rejectIfEnabled)
|
||||
&& writeAccess.Equals(other.writeAccess);
|
||||
}
|
||||
|
||||
public override readonly bool Equals(object? obj)
|
||||
{
|
||||
return obj is EntityQueryMask mask && Equals(mask);
|
||||
}
|
||||
|
||||
public static bool operator ==(EntityQueryMask left, EntityQueryMask right)
|
||||
{
|
||||
return left.Equals(right);
|
||||
}
|
||||
|
||||
public static bool operator !=(EntityQueryMask left, EntityQueryMask right)
|
||||
{
|
||||
return !(left == right);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
structuralAll.Dispose();
|
||||
structuralAny.Dispose();
|
||||
structuralAbsent.Dispose();
|
||||
|
||||
requireEnabled.Dispose();
|
||||
requireDisabled.Dispose();
|
||||
rejectIfEnabled.Dispose();
|
||||
|
||||
writeAccess.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides a read-only view over a chunk of entities and their component data within an archetype.
|
||||
/// </summary>
|
||||
/// <remarks>This does not filter disabled/enabled components. You must handle that manually.</remarks>
|
||||
public readonly unsafe ref struct ChunkView
|
||||
{
|
||||
// We flatten all the information we need for fast access.
|
||||
private readonly ReadOnlyUnsafeCollection<Archetype.ComponentMemoryLayout> _layouts;
|
||||
private readonly byte* _pChunkData;
|
||||
private readonly int* _pVersion;
|
||||
private readonly int _entityOffset;
|
||||
private readonly int _entityCount;
|
||||
private readonly int _structuralVersion;
|
||||
private readonly int _currentVersion;
|
||||
|
||||
public readonly int Count => _entityCount;
|
||||
|
||||
internal ChunkView(ref readonly Archetype archetype, ref readonly Chunk chunk)
|
||||
{
|
||||
_layouts = archetype._layouts.AsReadOnly();
|
||||
_pChunkData = chunk.GetUnsafePtr();
|
||||
_entityOffset = archetype.EntityIDsOffset;
|
||||
_entityCount = chunk._count;
|
||||
_pVersion = chunk.GetVersionUnsafePtr();
|
||||
|
||||
_structuralVersion = chunk._structuralVersion;
|
||||
_currentVersion = World.GetWorldUncheck(archetype.WorldID).Version;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private Archetype.ComponentMemoryLayout GetLayout(Identifier<IComponent> id)
|
||||
{
|
||||
var layout = _layouts[id.value];
|
||||
if (layout.enableBitsOffset == -1)
|
||||
{
|
||||
throw new InvalidOperationException($"Component {id} is not exist in the archetype.");
|
||||
}
|
||||
|
||||
return layout;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified component has changed since the given version.
|
||||
/// </summary>
|
||||
/// <param name="id">The identifier of the component to check for changes.</param>
|
||||
/// <param name="version">The version number to compare against the component's current version. Must be greater than or equal to zero.</param>
|
||||
/// <returns>true if the component's current version is less than or equal to the specified version; otherwise, false.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool HasChanged(Identifier<IComponent> id, int version)
|
||||
{
|
||||
var layout = GetLayout(id);
|
||||
return version < _pVersion[layout.versionIndex];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified version indicates that the component of type <typeparamref name="T"/> has
|
||||
/// changed since the last recorded version.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of component to check for changes. Must be an unmanaged type that implements <see cref="IComponent"/>.</typeparam>
|
||||
/// <param name="version">The version number to compare against the current version of the component.</param>
|
||||
/// <returns>true if the component of type T has changed since the specified version; otherwise, false.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly bool HasChanged<T>(int version)
|
||||
where T : unmanaged, IComponent
|
||||
{
|
||||
var layout = GetLayout(ComponentTypeID<T>.value);
|
||||
return version < _pVersion[layout.versionIndex];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the chunk's structure has changed since the specified version.
|
||||
/// </summary>
|
||||
/// <param name="version">The version number to compare against the chunk's structural version.</param>
|
||||
/// <returns>true if the chunk's structure has changed since the specified version; otherwise, false.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly bool HasStructuralChanged(int version)
|
||||
{
|
||||
return version < _structuralVersion;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current version number associated with the specified component identifier.
|
||||
/// </summary>
|
||||
/// <param name="id">The identifier of the component for which to retrieve the version number. Must reference a valid component.</param>
|
||||
/// <returns>The version number of the specified component.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly int GetComponentVersion(Identifier<IComponent> id)
|
||||
{
|
||||
return _pVersion[id];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current version number associated with the specified component type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The component type for which to retrieve the version. Must be an unmanaged type that implements <see cref="IComponent"/>.</typeparam>
|
||||
/// <returns>The version number of the component type <typeparamref name="T"/>.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly int GetComponentVersion<T>()
|
||||
where T : unmanaged, IComponent
|
||||
{
|
||||
return _pVersion[ComponentTypeID<T>.value];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a read-only span containing structuralAll entities stored in the current chunk.
|
||||
/// </summary>
|
||||
/// <returns>A read-only span of <see cref="Entity"/> values representing the entities in the chunk.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ReadOnlySpan<Entity> GetEntities()
|
||||
{
|
||||
var pEntity = (Entity*)(_pChunkData + _entityOffset);
|
||||
return new ReadOnlySpan<Entity>(pEntity, _entityCount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a readonly span providing direct access to the component data of type T0 for structuralAll entities in the chunk.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of component to access. Must be an unmanaged type that implements <see cref="Component"/>.</typeparam>
|
||||
/// <returns>A readonly span of type <see cref="{T}"/> containing the component data for each entity in the chunk.</returns>
|
||||
/// <exception cref="InvalidOperationException">Thrown if the specified component type is not present in the archetype.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ReadOnlySpan<T> GetComponentData<T>()
|
||||
where T : unmanaged, IComponent
|
||||
{
|
||||
var layout = GetLayout(ComponentTypeID<T>.value);
|
||||
var pComponentData = _pChunkData + layout.offset;
|
||||
return new ReadOnlySpan<T>(pComponentData, _entityCount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a span providing direct access to the component data of type T0 for structuralAll entities in the chunk.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of component to access. Must be an unmanaged type that implements <see cref="Component"/>.</typeparam>
|
||||
/// <returns>A span of type <see cref="{T}"/> containing the component data for each entity in the chunk.</returns>
|
||||
/// <exception cref="InvalidOperationException">Thrown if the specified component type is not present in the archetype.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Span<T> GetComponentDataRW<T>()
|
||||
where T : unmanaged, IComponent
|
||||
{
|
||||
var compId = ComponentTypeID<T>.value;
|
||||
var layout = GetLayout(compId);
|
||||
|
||||
_pVersion[layout.versionIndex] = _currentVersion;
|
||||
|
||||
var pComponentData = _pChunkData + layout.offset;
|
||||
return new Span<T>(pComponentData, _entityCount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a bit set representing the enabled state of each instance of the specified enableable component
|
||||
/// type within the current chunk.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The component type for which to retrieve enablement bits. Must be unmanaged and implement <see cref="IEnableableComponent"/>.</typeparam>
|
||||
/// <returns>A <see cref="SpanBitSet"/> that provides access to the enablement bits for all instances of the specified component type in the chunk.</returns>
|
||||
/// <exception cref="InvalidOperationException">Thrown if the specified component type does not support enablement.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public SpanBitSet GetEnableBits<T>()
|
||||
where T : unmanaged, IEnableableComponent
|
||||
{
|
||||
var layout = _layouts[ComponentTypeID<T>.value];
|
||||
var maskBase = _pChunkData + layout.enableBitsOffset;
|
||||
return new SpanBitSet(new Span<uint>(maskBase, (_entityCount + 31) / 32));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified component of type <typeparamref name="T"/> at the given index is currently enabled.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the component to check. Must be an unmanaged type that implements <see cref="IEnableableComponent"/>.</typeparam>
|
||||
/// <param name="index">The zero-based index of the component instance to check within the chunk.</param>
|
||||
/// <returns>true if the component at the specified index is enabled; otherwise, false.</returns>
|
||||
/// <exception cref="InvalidOperationException">Thrown if the specified component type <typeparamref name="T"/> does not support enable/disable functionality.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool IsComponentEnabled<T>(int index)
|
||||
where T : unmanaged, IEnableableComponent
|
||||
{
|
||||
var layout = GetLayout(ComponentTypeID<T>.value);
|
||||
var pMask = _pChunkData + layout.enableBitsOffset;
|
||||
return EntityQuery.CheckBit(pMask, index);
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe partial struct EntityQuery : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides an enumerator for iterating over chunks of entities and their component data that match a set of archetypes within a world.
|
||||
/// </summary>
|
||||
public readonly ref struct ChunkIterator
|
||||
{
|
||||
public ref struct Enumerator
|
||||
{
|
||||
private readonly ChunkIterator _iterator;
|
||||
private int _archetypeIndex;
|
||||
private int _chunkIndex;
|
||||
|
||||
internal Enumerator(ChunkIterator iterator)
|
||||
{
|
||||
_iterator = iterator;
|
||||
_archetypeIndex = 0;
|
||||
_chunkIndex = -1;
|
||||
}
|
||||
|
||||
public readonly ChunkView Current
|
||||
{
|
||||
get
|
||||
{
|
||||
ref var archetype = ref _iterator._world.GetArchetypeReference(_iterator._matchingArchetypes[_archetypeIndex]);
|
||||
ref var chunk = ref archetype.GetChunkReference(_chunkIndex);
|
||||
return new ChunkView(in archetype, in chunk);
|
||||
}
|
||||
}
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
_chunkIndex++;
|
||||
|
||||
while (_archetypeIndex < _iterator._matchingArchetypes.Count)
|
||||
{
|
||||
ref var archetype = ref _iterator._world.GetArchetypeReference(_iterator._matchingArchetypes[_archetypeIndex]);
|
||||
if (_chunkIndex < archetype.ChunkCount)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
_chunkIndex = 0;
|
||||
_archetypeIndex++;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_archetypeIndex = 0;
|
||||
_chunkIndex = -1;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly ReadOnlyUnsafeCollection<Identifier<Archetype>> _matchingArchetypes;
|
||||
private readonly World _world;
|
||||
|
||||
internal ChunkIterator(ReadOnlyUnsafeCollection<Identifier<Archetype>> matchingArchetypes, World world)
|
||||
{
|
||||
_matchingArchetypes = matchingArchetypes;
|
||||
_world = world;
|
||||
}
|
||||
|
||||
public readonly Enumerator GetEnumerator()
|
||||
{
|
||||
return new Enumerator(this);
|
||||
}
|
||||
}
|
||||
|
||||
internal EntityQueryMask _mask;
|
||||
|
||||
private UnsafeList<Identifier<Archetype>> _matchingArchetypes;
|
||||
private readonly Identifier<EntityQuery> _id;
|
||||
private readonly Identifier<World> _worldID;
|
||||
|
||||
internal EntityQuery(Identifier<EntityQuery> id, Identifier<World> worldID, EntityQueryMask mask)
|
||||
{
|
||||
_id = id;
|
||||
_worldID = worldID;
|
||||
_mask = mask;
|
||||
_matchingArchetypes = new UnsafeList<Identifier<Archetype>>(8, Allocator.Persistent);
|
||||
}
|
||||
|
||||
// TODO: Fetching layout every time is not optimal. Cache them?
|
||||
private static bool IsEntityValid(byte* chunkBase, int entityIndex, ref readonly Archetype archetype, ref readonly EntityQueryMask mask)
|
||||
{
|
||||
// 1. Check "Require Enabled" (WithAll)
|
||||
var it = mask.requireEnabled.GetIterator();
|
||||
while (it.Next(out var id))
|
||||
{
|
||||
// Get the EnableBitmask for this component in this chunk
|
||||
var layoutResult = archetype.GetLayout(id);
|
||||
if (layoutResult.Error != ErrorStatus.None
|
||||
// Not enableable, always true
|
||||
|| layoutResult.Value.enableBitsOffset == -1)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check bit
|
||||
if (!CheckBit(chunkBase + layoutResult.Value.enableBitsOffset, entityIndex))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Check "Require Disabled" (WithDisabled)
|
||||
it = mask.requireDisabled.GetIterator();
|
||||
while (it.Next(out var id))
|
||||
{
|
||||
var layoutResult = archetype.GetLayout(id);
|
||||
if (layoutResult.Error != ErrorStatus.None)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// If component is not enableable, it is technically "Always Enabled",
|
||||
// so it cannot satisfy "WithDisabled".
|
||||
|
||||
// Check bit (Must be 0)
|
||||
if (layoutResult.Value.enableBitsOffset == -1
|
||||
|| CheckBit(chunkBase + layoutResult.Value.enableBitsOffset, entityIndex))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Check "Reject if Enabled" (The "Soft WithNone")
|
||||
it = mask.rejectIfEnabled.GetIterator();
|
||||
while (it.Next(out var id))
|
||||
{
|
||||
var layoutResult = archetype.GetLayout(id);
|
||||
if (layoutResult.Error != ErrorStatus.None)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// If component is not enableable, it is technically "Always Enabled",
|
||||
// so it cannot satisfy "Reject if Enabled".
|
||||
|
||||
// Check bit (Must be 0)
|
||||
if (layoutResult.Value.enableBitsOffset == -1
|
||||
|| CheckBit(chunkBase + layoutResult.Value.enableBitsOffset, entityIndex))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static bool CheckBit(byte* maskBase, int index)
|
||||
{
|
||||
var byteIndex = index >> Chunk.BIT_SHIFT;
|
||||
var bitIndex = index & Chunk.BIT_ALIGNMENT_MINUS_ONE;
|
||||
return (maskBase[byteIndex] & (1 << bitIndex)) != 0;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal void AddArchetypeIfMatch(ref readonly Archetype archetype)
|
||||
{
|
||||
if (_mask.Matches(in archetype._signature))
|
||||
{
|
||||
_matchingArchetypes.Add(archetype.ID);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly ChunkIterator GetChunkIterator()
|
||||
{
|
||||
var world = World.GetWorld(_worldID).Value;
|
||||
return new ChunkIterator(_matchingArchetypes.AsReadOnly(), world);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly int GetEntityCount()
|
||||
{
|
||||
var total = 0;
|
||||
var r = World.GetWorld(_worldID);
|
||||
if (r.IsFailure)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
var world = r.Value;
|
||||
for(var i = 0; i < _matchingArchetypes.Count; i++)
|
||||
{
|
||||
var archetypeID = _matchingArchetypes[i];
|
||||
ref var archetype = ref world.GetArchetypeReference(archetypeID);
|
||||
for (var j = 0; j < archetype.ChunkCount; j++)
|
||||
{
|
||||
ref var chunk = ref archetype.GetChunkReference(j);
|
||||
total += chunk._count;
|
||||
}
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_mask.Dispose();
|
||||
_matchingArchetypes.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public ref partial struct QueryBuilder
|
||||
{
|
||||
private readonly Stack.Scope _scope;
|
||||
|
||||
private UnsafeList<Identifier<IComponent>> _all;
|
||||
private UnsafeList<Identifier<IComponent>> _any;
|
||||
private UnsafeList<Identifier<IComponent>> _absent;
|
||||
private UnsafeList<Identifier<IComponent>> _none;
|
||||
private UnsafeList<Identifier<IComponent>> _disabled;
|
||||
private UnsafeList<Identifier<IComponent>> _present;
|
||||
|
||||
private UnsafeList<Identifier<IComponent>> _rw;
|
||||
|
||||
public QueryBuilder()
|
||||
{
|
||||
_scope = AllocationManager.CreateStackScope();
|
||||
|
||||
_all = new UnsafeList<Identifier<IComponent>>(4, _scope.AllocationHandle);
|
||||
_any = new UnsafeList<Identifier<IComponent>>(4, _scope.AllocationHandle);
|
||||
_absent = new UnsafeList<Identifier<IComponent>>(4, _scope.AllocationHandle);
|
||||
_none = new UnsafeList<Identifier<IComponent>>(4, _scope.AllocationHandle);
|
||||
_disabled = new UnsafeList<Identifier<IComponent>>(4, _scope.AllocationHandle);
|
||||
_present = new UnsafeList<Identifier<IComponent>>(4, _scope.AllocationHandle);
|
||||
|
||||
_rw = new UnsafeList<Identifier<IComponent>>(4, _scope.AllocationHandle);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void FindMax(UnsafeList<Identifier<IComponent>> list, ref int max)
|
||||
{
|
||||
foreach (var id in list)
|
||||
{
|
||||
if (id.value > max) max = id.value;
|
||||
}
|
||||
}
|
||||
|
||||
public Identifier<EntityQuery> Build(World world, Allocator allocator = Allocator.Persistent)
|
||||
{
|
||||
// 1. Calculate max component ID to size the BitSets
|
||||
var maxID = 0;
|
||||
FindMax(_all, ref maxID);
|
||||
FindMax(_any, ref maxID);
|
||||
FindMax(_absent, ref maxID);
|
||||
FindMax(_none, ref maxID);
|
||||
FindMax(_disabled, ref maxID);
|
||||
FindMax(_present, ref maxID);
|
||||
|
||||
// 2. Create the Mask
|
||||
var mask = new EntityQueryMask
|
||||
{
|
||||
structuralAll = new UnsafeBitSet(maxID + 1, allocator, AllocationOption.Clear),
|
||||
structuralAny = new UnsafeBitSet(maxID + 1, allocator, AllocationOption.Clear),
|
||||
structuralAbsent = new UnsafeBitSet(maxID + 1, allocator, AllocationOption.Clear),
|
||||
requireEnabled = new UnsafeBitSet(maxID + 1, allocator, AllocationOption.Clear),
|
||||
requireDisabled = new UnsafeBitSet(maxID + 1, allocator, AllocationOption.Clear),
|
||||
rejectIfEnabled = new UnsafeBitSet(maxID + 1, allocator, AllocationOption.Clear),
|
||||
|
||||
writeAccess = new UnsafeBitSet(maxID + 1, allocator, AllocationOption.Clear),
|
||||
};
|
||||
|
||||
// 3. Fill BitSets
|
||||
foreach (var id in _all)
|
||||
{
|
||||
mask.structuralAll.SetBit(id); // Structure: Must Exist
|
||||
mask.requireEnabled.SetBit(id); // Filter: Must be Enabled
|
||||
}
|
||||
|
||||
foreach (var id in _disabled)
|
||||
{
|
||||
mask.structuralAll.SetBit(id); // Structure: Must Exist
|
||||
mask.requireDisabled.SetBit(id); // Filter: Must be Disabled
|
||||
}
|
||||
|
||||
foreach (var id in _none)
|
||||
{
|
||||
if (ComponentRegister.GetComponentInfo(id).isEnableable)
|
||||
{
|
||||
mask.rejectIfEnabled.SetBit(id); // Filter: Must Not be Enabled (Can be Absent or Disabled)
|
||||
}
|
||||
else
|
||||
{
|
||||
mask.structuralAbsent.SetBit(id); // Structure: Must Not Exist
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var id in _present)
|
||||
{
|
||||
mask.structuralAll.SetBit(id);
|
||||
}
|
||||
|
||||
foreach (var id in _absent)
|
||||
{
|
||||
mask.structuralAbsent.SetBit(id);
|
||||
}
|
||||
|
||||
foreach (var id in _any)
|
||||
{
|
||||
mask.structuralAny.SetBit(id);
|
||||
}
|
||||
|
||||
foreach (var id in _rw)
|
||||
{
|
||||
mask.writeAccess.SetBit(id);
|
||||
}
|
||||
|
||||
// 4. Ask World for the Query (Cached)
|
||||
var maskHash = mask.GetHashCode();
|
||||
var queryID = world.GetEntityQueryIDByMaskHash(maskHash);
|
||||
if (queryID.IsValid)
|
||||
{
|
||||
// Check if the masks are actually equal (Hash collision?).
|
||||
// Really worth it? It's unlikely to have collisions here.
|
||||
if (world.GetEntityQueryReference(queryID)._mask.Equals(mask))
|
||||
{
|
||||
mask.Dispose();
|
||||
goto Return;
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: We do not dispose the mask here, as it is now owned by the EntityQuery.
|
||||
queryID = world.CreateEntityQuery(mask, maskHash);
|
||||
|
||||
Return:
|
||||
Dispose();
|
||||
return queryID;
|
||||
}
|
||||
|
||||
private void Dispose()
|
||||
{
|
||||
_scope.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
using Ghost.Core;
|
||||
|
||||
namespace Ghost.Entities.Query;
|
||||
|
||||
public struct QueryBuilder
|
||||
{
|
||||
private QueryFilter _filter;
|
||||
|
||||
public QueryBuilder()
|
||||
{
|
||||
_filter = new QueryFilter();
|
||||
}
|
||||
|
||||
public QueryBuilder WithAll<T>()
|
||||
{
|
||||
_filter._all.Add(TypeHandle.Get<T>());
|
||||
return this;
|
||||
}
|
||||
|
||||
public QueryBuilder WithAny<T>()
|
||||
{
|
||||
_filter._any.Add(TypeHandle.Get<T>());
|
||||
return this;
|
||||
}
|
||||
|
||||
public QueryBuilder WithAbsent<T>()
|
||||
{
|
||||
_filter._absent.Add(TypeHandle.Get<T>());
|
||||
return this;
|
||||
}
|
||||
|
||||
public QueryBuilder WithDisabled<T>()
|
||||
{
|
||||
_filter._disabled.Add(TypeHandle.Get<T>());
|
||||
return this;
|
||||
}
|
||||
|
||||
public readonly QueryFilter Build()
|
||||
{
|
||||
return _filter;
|
||||
}
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
using Ghost.Core;
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
|
||||
namespace Ghost.Entities.Query;
|
||||
|
||||
public struct QueryFilter : IDisposable
|
||||
{
|
||||
//private readonly Stack.Scope _scope;
|
||||
|
||||
internal UnsafeList<TypeHandle> _all;
|
||||
internal UnsafeList<TypeHandle> _any;
|
||||
internal UnsafeList<TypeHandle> _absent;
|
||||
internal UnsafeList<TypeHandle> _disabled;
|
||||
|
||||
public QueryFilter()
|
||||
{
|
||||
//_scope = AllocationManager.CreateStackScope();
|
||||
|
||||
_all = new UnsafeList<TypeHandle>(4, Allocator.Stack);
|
||||
_any = new UnsafeList<TypeHandle>(4, Allocator.Stack);
|
||||
_absent = new UnsafeList<TypeHandle>(4, Allocator.Stack);
|
||||
_disabled = new UnsafeList<TypeHandle>(4, Allocator.Stack);
|
||||
}
|
||||
|
||||
public readonly UnsafeBitSet ComputeFilterBitMask(World world, Allocator allocator)
|
||||
{
|
||||
UnsafeBitSet allMask = default;
|
||||
UnsafeBitSet anyMask = default;
|
||||
UnsafeBitSet absentMask = default;
|
||||
|
||||
var result = new UnsafeBitSet(world.EntityManager.EntityCount, allocator);
|
||||
result.ClearAll();
|
||||
|
||||
using (AllocationManager.CreateStackScope())
|
||||
{
|
||||
foreach (var typeHandle in _all)
|
||||
{
|
||||
var mask = world.ComponentStorage.GetOrCreateMask(typeHandle);
|
||||
|
||||
if (!allMask.IsCreated)
|
||||
{
|
||||
allMask = new UnsafeBitSet(mask.Count, Allocator.Stack, AllocationOption.None);
|
||||
allMask.SetAll();
|
||||
}
|
||||
|
||||
allMask.And(mask);
|
||||
}
|
||||
|
||||
foreach (var typeHandle in _any)
|
||||
{
|
||||
var mask = world.ComponentStorage.GetOrCreateMask(typeHandle);
|
||||
|
||||
if (!anyMask.IsCreated)
|
||||
{
|
||||
anyMask = new UnsafeBitSet(mask.Count, Allocator.Stack);
|
||||
}
|
||||
|
||||
anyMask.And(mask);
|
||||
}
|
||||
|
||||
foreach (var typeHandle in _absent)
|
||||
{
|
||||
var mask = world.ComponentStorage.GetOrCreateMask(typeHandle);
|
||||
|
||||
if (!absentMask.IsCreated)
|
||||
{
|
||||
absentMask = new UnsafeBitSet(mask.Count, Allocator.Stack);
|
||||
}
|
||||
|
||||
absentMask.Or(mask);
|
||||
}
|
||||
|
||||
if (allMask.IsCreated)
|
||||
{
|
||||
result.And(allMask);
|
||||
}
|
||||
|
||||
if (anyMask.IsCreated)
|
||||
{
|
||||
result.And(anyMask);
|
||||
}
|
||||
|
||||
if (absentMask.IsCreated)
|
||||
{
|
||||
result.ANDC(absentMask);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public readonly void Dispose()
|
||||
{
|
||||
//_scope.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
using Ghost.Entities.Components;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ghost.Entities.Query;
|
||||
|
||||
public interface IQueryTypeParameter<T>
|
||||
where T : IComponentData
|
||||
{
|
||||
}
|
||||
|
||||
public ref struct CompRef<T> : IQueryTypeParameter<T>
|
||||
where T : IComponentData
|
||||
{
|
||||
internal ref T _value;
|
||||
|
||||
public ref T ValueRW
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => ref _value;
|
||||
}
|
||||
|
||||
public readonly ref T ValueRO
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => ref _value;
|
||||
}
|
||||
|
||||
public readonly bool IsValid
|
||||
{
|
||||
get;
|
||||
init;
|
||||
}
|
||||
|
||||
public CompRef(ref T value, bool isValid)
|
||||
{
|
||||
_value = ref value;
|
||||
IsValid = isValid;
|
||||
}
|
||||
|
||||
public CompRef(ref T value)
|
||||
: this(ref value, true)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public readonly ref struct CompRO<T> : IQueryTypeParameter<T>
|
||||
where T : IComponentData
|
||||
{
|
||||
internal readonly ref T _value;
|
||||
|
||||
public readonly ref T ValueRO
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => ref _value;
|
||||
}
|
||||
|
||||
public readonly bool IsValid
|
||||
{
|
||||
get;
|
||||
init;
|
||||
}
|
||||
|
||||
public CompRO(ref T value, bool isValid)
|
||||
{
|
||||
_value = ref value;
|
||||
IsValid = isValid;
|
||||
}
|
||||
|
||||
public CompRO(ref T value)
|
||||
: this(ref value, true)
|
||||
{
|
||||
}
|
||||
}
|
||||
407
Ghost.Entities/System.cs
Normal file
407
Ghost.Entities/System.cs
Normal file
@@ -0,0 +1,407 @@
|
||||
using Ghost.Core;
|
||||
|
||||
namespace Ghost.Entities;
|
||||
|
||||
public readonly ref struct SystemAPI
|
||||
{
|
||||
public TimeData Time
|
||||
{
|
||||
get; init;
|
||||
}
|
||||
}
|
||||
|
||||
public interface ISystem
|
||||
{
|
||||
World World
|
||||
{
|
||||
get; init;
|
||||
}
|
||||
|
||||
void Initialize(ref readonly SystemAPI systemAPI);
|
||||
void Update(ref readonly SystemAPI systemAPI);
|
||||
void Cleanup(ref readonly SystemAPI systemAPI);
|
||||
}
|
||||
|
||||
public abstract class SystemBase : ISystem
|
||||
{
|
||||
private List<int>? _requiredQueries;
|
||||
|
||||
public World World
|
||||
{
|
||||
get; init;
|
||||
} = null!;
|
||||
|
||||
public int LastSystemVersion
|
||||
{
|
||||
get; internal set;
|
||||
} = -2;
|
||||
|
||||
private bool ShouldUpdate()
|
||||
{
|
||||
if (_requiredQueries == null || _requiredQueries.Count == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach (var queryID in _requiredQueries)
|
||||
{
|
||||
ref var query = ref World.GetEntityQueryReference(new Identifier<EntityQuery>(queryID));
|
||||
if (query.GetEntityCount() == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected void RequireQueryForUpdate(Identifier<EntityQuery> queryID)
|
||||
{
|
||||
_requiredQueries ??= new List<int>(4);
|
||||
_requiredQueries.Add(queryID.value);
|
||||
}
|
||||
|
||||
public void Initialize(ref readonly SystemAPI systemAPI)
|
||||
{
|
||||
OnInitialize(in systemAPI);
|
||||
}
|
||||
|
||||
public void Update(ref readonly SystemAPI systemAPI)
|
||||
{
|
||||
if (ShouldUpdate())
|
||||
{
|
||||
if (World.Version - LastSystemVersion > 1)
|
||||
{
|
||||
OnStartRunning();
|
||||
}
|
||||
|
||||
OnUpdate(in systemAPI);
|
||||
LastSystemVersion = World.Version;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (World.Version - LastSystemVersion <= 1)
|
||||
{
|
||||
OnStopRunning();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Cleanup(ref readonly SystemAPI systemAPI)
|
||||
{
|
||||
OnCleanup(in systemAPI);
|
||||
}
|
||||
|
||||
protected virtual void OnInitialize(ref readonly SystemAPI systemAPI)
|
||||
{
|
||||
}
|
||||
|
||||
protected virtual void OnUpdate(ref readonly SystemAPI systemAPI)
|
||||
{
|
||||
}
|
||||
|
||||
protected virtual void OnCleanup(ref readonly SystemAPI systemAPI)
|
||||
{
|
||||
}
|
||||
|
||||
protected virtual void OnStopRunning()
|
||||
{
|
||||
}
|
||||
|
||||
protected virtual void OnStartRunning()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = true)]
|
||||
public class UpdateAfterAttribute : Attribute
|
||||
{
|
||||
public Type SystemType { get; }
|
||||
|
||||
public UpdateAfterAttribute(Type systemType)
|
||||
{
|
||||
SystemType = systemType;
|
||||
}
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = true)]
|
||||
public class UpdateBeforeAttribute : Attribute
|
||||
{
|
||||
public Type SystemType { get; }
|
||||
|
||||
public UpdateBeforeAttribute(Type systemType)
|
||||
{
|
||||
SystemType = systemType;
|
||||
}
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false)]
|
||||
public class SystemGroupAttribute : Attribute
|
||||
{
|
||||
public Type GroupType { get; }
|
||||
|
||||
public SystemGroupAttribute(Type groupType)
|
||||
{
|
||||
GroupType = groupType;
|
||||
}
|
||||
}
|
||||
|
||||
#if false
|
||||
internal static partial class SystemGroupRegistry
|
||||
{
|
||||
private static readonly Dictionary<Type, List<ISystem>> _systemGroupMap = new();
|
||||
|
||||
// TODO: Use Source Generators to generate group registrations at compile time.
|
||||
|
||||
public static void RegisterSystemGroup<T>(Type groupType)
|
||||
where T : ISystem, new()
|
||||
{
|
||||
if (!_systemGroupMap.ContainsKey(typeof(T)))
|
||||
{
|
||||
_systemGroupMap[typeof(T)] = new();
|
||||
}
|
||||
|
||||
_systemGroupMap[groupType].Add(new T());
|
||||
}
|
||||
|
||||
public static List<ISystem> GetSystemsForGroup(Type groupType)
|
||||
{
|
||||
if (_systemGroupMap.TryGetValue(groupType, out var systems))
|
||||
{
|
||||
return systems;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException($"No systems registered for System Group of type {groupType.FullName}");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
public abstract class SystemGroup : ISystem
|
||||
{
|
||||
private readonly List<ISystem> _systems = [];
|
||||
private List<ISystem>? _sortedSystems;
|
||||
|
||||
private uint _version = 0;
|
||||
private uint _sortedVersion = 0;
|
||||
|
||||
public World World
|
||||
{
|
||||
get; init;
|
||||
} = null!;
|
||||
|
||||
// public SystemGroup()
|
||||
// {
|
||||
// _systems = SystemGroupRegistry.GetSystemsForGroup(GetType());
|
||||
// }
|
||||
|
||||
private static List<ISystem> Sort(List<ISystem> systems)
|
||||
{
|
||||
// 1. Build the Graph
|
||||
// Key: The System, Value: Systems that MUST run before the Key
|
||||
var dependencies = new Dictionary<Type, HashSet<Type>>();
|
||||
var systemMap = systems.ToDictionary(s => s.GetType(), s => s);
|
||||
|
||||
foreach (var sys in systems)
|
||||
{
|
||||
var type = sys.GetType();
|
||||
if (!dependencies.TryGetValue(type, out HashSet<Type>? value))
|
||||
{
|
||||
value = [];
|
||||
dependencies[type] = value;
|
||||
}
|
||||
|
||||
// Handle [UpdateAfter(typeof(Other))] -> Other comes before This
|
||||
foreach (var attr in type.GetCustomAttributes(typeof(UpdateAfterAttribute), true))
|
||||
{
|
||||
var depType = ((UpdateAfterAttribute)attr).SystemType;
|
||||
value.Add(depType);
|
||||
}
|
||||
|
||||
// Handle [UpdateBefore(typeof(Other))] -> This comes before Other
|
||||
// Which means: Other depends on This
|
||||
foreach (var attr in type.GetCustomAttributes(typeof(UpdateBeforeAttribute), true))
|
||||
{
|
||||
var targetType = ((UpdateBeforeAttribute)attr).SystemType;
|
||||
if (!dependencies.ContainsKey(targetType)) dependencies[targetType] = [];
|
||||
dependencies[targetType].Add(type);
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Topological Sort (Kahn's Algorithm variant)
|
||||
var sortedList = new List<ISystem>();
|
||||
var visited = new HashSet<Type>();
|
||||
|
||||
// We loop until we have sorted everyone
|
||||
while (sortedList.Count < systems.Count)
|
||||
{
|
||||
bool addedAny = false;
|
||||
|
||||
foreach (var sys in systems)
|
||||
{
|
||||
var type = sys.GetType();
|
||||
if (visited.Contains(type)) continue;
|
||||
|
||||
// Check if all dependencies for this system are already visited/sorted
|
||||
bool canRun = true;
|
||||
if (dependencies.TryGetValue(type, out var deps))
|
||||
{
|
||||
foreach (var dep in deps)
|
||||
{
|
||||
// If the dependency exists in our list but hasn't run yet, we can't run.
|
||||
// (We check systemMap to ignore dependencies that don't exist in this world)
|
||||
if (systemMap.ContainsKey(dep) && !visited.Contains(dep))
|
||||
{
|
||||
canRun = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (canRun)
|
||||
{
|
||||
sortedList.Add(sys);
|
||||
visited.Add(type);
|
||||
addedAny = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!addedAny)
|
||||
{
|
||||
throw new InvalidOperationException("Circular Dependency detected in Systems! Check your [UpdateAfter] attributes.");
|
||||
}
|
||||
}
|
||||
|
||||
return sortedList;
|
||||
}
|
||||
|
||||
public void AddSystem<T>()
|
||||
where T : ISystem, new()
|
||||
{
|
||||
_systems.Add(new T()
|
||||
{
|
||||
World = World
|
||||
});
|
||||
|
||||
_version++;
|
||||
}
|
||||
|
||||
public void SortSystems()
|
||||
{
|
||||
if (_sortedVersion == _version)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_systems.Count == 0)
|
||||
{
|
||||
_sortedSystems = [];
|
||||
_sortedVersion = _version;
|
||||
return;
|
||||
}
|
||||
|
||||
_sortedSystems = Sort(_systems);
|
||||
_sortedVersion = _version;
|
||||
}
|
||||
|
||||
private void ThrowIfNotSorted()
|
||||
{
|
||||
if (_sortedSystems == null || _sortedVersion != _version)
|
||||
{
|
||||
throw new InvalidOperationException("Systems must be sorted before calling this method. Call SortSystems() after adding all systems.");
|
||||
}
|
||||
}
|
||||
|
||||
public void Initialize(ref readonly SystemAPI systemAPI)
|
||||
{
|
||||
ThrowIfNotSorted();
|
||||
|
||||
foreach (var system in _sortedSystems!)
|
||||
{
|
||||
system.Initialize(in systemAPI);
|
||||
}
|
||||
}
|
||||
|
||||
public void Update(ref readonly SystemAPI systemAPI)
|
||||
{
|
||||
ThrowIfNotSorted();
|
||||
|
||||
foreach (var system in _sortedSystems!)
|
||||
{
|
||||
system.Update(in systemAPI);
|
||||
}
|
||||
}
|
||||
|
||||
public void Cleanup(ref readonly SystemAPI systemAPI)
|
||||
{
|
||||
ThrowIfNotSorted();
|
||||
|
||||
foreach (var system in _sortedSystems!)
|
||||
{
|
||||
system.Cleanup(in systemAPI);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class DefaultSystemGroup : SystemGroup
|
||||
{
|
||||
}
|
||||
|
||||
public class SystemManager
|
||||
{
|
||||
private readonly World _world;
|
||||
|
||||
private readonly List<ISystem> _systems = [];
|
||||
|
||||
internal SystemManager(World world)
|
||||
{
|
||||
_world = world;
|
||||
AddSystem<DefaultSystemGroup>();
|
||||
}
|
||||
|
||||
public void AddSystem<T>()
|
||||
where T : ISystem, new()
|
||||
{
|
||||
_systems.Add(new T()
|
||||
{
|
||||
World = _world
|
||||
});
|
||||
}
|
||||
|
||||
public T GetSystem<T>()
|
||||
where T : ISystem
|
||||
{
|
||||
foreach (var system in _systems)
|
||||
{
|
||||
if (system is T typedSystem)
|
||||
{
|
||||
return typedSystem;
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidOperationException($"System of type {typeof(T).FullName} not found in SystemManager.");
|
||||
}
|
||||
|
||||
internal void InitializeAll(ref readonly SystemAPI systemAPI)
|
||||
{
|
||||
foreach (var system in _systems)
|
||||
{
|
||||
system.Initialize(in systemAPI);
|
||||
}
|
||||
}
|
||||
|
||||
internal void UpdateAll(ref readonly SystemAPI systemAPI)
|
||||
{
|
||||
foreach (var system in _systems)
|
||||
{
|
||||
system.Update(in systemAPI);
|
||||
}
|
||||
}
|
||||
|
||||
internal void CleanupAll(ref readonly SystemAPI systemAPI)
|
||||
{
|
||||
foreach (var system in _systems)
|
||||
{
|
||||
system.Cleanup(in systemAPI);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
namespace Ghost.Entities.Systems;
|
||||
|
||||
/// <summary>
|
||||
/// Attribute to declare that a system depends on one or more other systems.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Struct | AttributeTargets.Class, AllowMultiple = false)]
|
||||
public class DependsOnAttribute : Attribute
|
||||
{
|
||||
public Type[] Prerequisites
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public DependsOnAttribute(params Type[] prerequisites)
|
||||
{
|
||||
Prerequisites = prerequisites;
|
||||
}
|
||||
}
|
||||
|
||||
public interface ISystem
|
||||
{
|
||||
public void OnCreate(in SystemState state);
|
||||
|
||||
public void OnUpdate(in SystemState state);
|
||||
|
||||
public void OnDestroy(in SystemState state);
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
using System.Reflection;
|
||||
|
||||
namespace Ghost.Entities.Systems;
|
||||
|
||||
internal class SystemDependencyBuilder
|
||||
{
|
||||
private readonly Dictionary<Type, List<Type>> _dependencies = new();
|
||||
private readonly List<Type> _systemTypes;
|
||||
|
||||
public SystemDependencyBuilder(List<Type> allSystemTypes)
|
||||
{
|
||||
_systemTypes = allSystemTypes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds a dependency graph for all system types that implement the <see cref="ISystem"/> interface.
|
||||
/// </summary>
|
||||
/// <remarks>This method analyzes all system types and their dependencies, as defined by the <see
|
||||
/// cref="DependsOnAttribute"/>. It validates that each system type is a concrete implementation of <see
|
||||
/// cref="ISystem"/> and constructs a mapping of each system type to its direct dependencies.</remarks>
|
||||
/// <exception cref="ArgumentException">Thrown if a type in <c>allSystemTypes</c> is not a concrete implementation of <see cref="ISystem"/>.</exception>
|
||||
public void BuildDependencyGraph()
|
||||
{
|
||||
foreach (var systemType in _systemTypes)
|
||||
{
|
||||
if (!typeof(ISystem).IsAssignableFrom(systemType) || systemType.IsAbstract || systemType.IsInterface)
|
||||
{
|
||||
throw new ArgumentException($"{systemType.Name} is not a concrete ISystem type.");
|
||||
}
|
||||
|
||||
var directDependencies = new List<Type>();
|
||||
var dependsOnAttributes = systemType.GetCustomAttributes<DependsOnAttribute>(false);
|
||||
foreach (var attr in dependsOnAttributes)
|
||||
{
|
||||
directDependencies.AddRange(attr.Prerequisites);
|
||||
}
|
||||
|
||||
_dependencies[systemType] = directDependencies;
|
||||
}
|
||||
}
|
||||
|
||||
private void Visit(Type systemType, HashSet<Type> visited, HashSet<Type> permanentMark, List<Type> executionOrder)
|
||||
{
|
||||
if (permanentMark.Contains(systemType))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (visited.Contains(systemType))
|
||||
{
|
||||
throw new InvalidOperationException($"Circular dependency detected involving system: {systemType.Name}");
|
||||
}
|
||||
|
||||
visited.Add(systemType); // Mark as currently visiting
|
||||
|
||||
if (_dependencies.TryGetValue(systemType, out var directDependencies))
|
||||
{
|
||||
foreach (var dependencyType in directDependencies)
|
||||
{
|
||||
// Ensure the dependency is a registered system type
|
||||
if (!_systemTypes.Contains(dependencyType))
|
||||
{
|
||||
throw new InvalidOperationException($"System {systemType.Name} depends on unregistered system {dependencyType.Name}.");
|
||||
}
|
||||
|
||||
Visit(dependencyType, visited, permanentMark, executionOrder);
|
||||
}
|
||||
}
|
||||
|
||||
visited.Remove(systemType); // Done visiting this node in the current path
|
||||
permanentMark.Add(systemType); // Mark as permanently processed
|
||||
executionOrder.Add(systemType); // Add to the sorted list (this will be reversed later for correct order)
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds the topological order of systems.
|
||||
/// </summary>
|
||||
/// <returns>A list of system types in the order they should be executed.</returns>
|
||||
/// <exception cref="InvalidOperationException">Thrown if a circular dependency is detected.</exception>"
|
||||
public List<Type> BuildExecutionOrder()
|
||||
{
|
||||
var executionOrder = new List<Type>(_systemTypes.Count);
|
||||
var visited = new HashSet<Type>(); // Tracks visited nodes in the current DFS path (for cycle detection)
|
||||
var permanentMark = new HashSet<Type>(); // Tracks nodes whose dependencies have been fully resolved
|
||||
|
||||
// Initialize dependencies for all registered systems, even those without explicit attributes
|
||||
foreach (var sysType in _systemTypes)
|
||||
{
|
||||
if (!_dependencies.ContainsKey(sysType))
|
||||
{
|
||||
_dependencies[sysType] = new();
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var systemType in _systemTypes)
|
||||
{
|
||||
if (!permanentMark.Contains(systemType))
|
||||
{
|
||||
Visit(systemType, visited, permanentMark, executionOrder);
|
||||
}
|
||||
}
|
||||
|
||||
return executionOrder;
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
namespace Ghost.Entities.Systems;
|
||||
|
||||
public struct SystemState
|
||||
{
|
||||
public World World
|
||||
{
|
||||
get;
|
||||
init;
|
||||
}
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ghost.Entities.Systems;
|
||||
|
||||
[SkipLocalsInit]
|
||||
public readonly struct SystemStorage
|
||||
{
|
||||
private readonly List<Type> _systems = new();
|
||||
private readonly List<ISystem> _executionList = new();
|
||||
|
||||
private readonly World _world;
|
||||
|
||||
internal ReadOnlySpan<Type> Systems => CollectionsMarshal.AsSpan(_systems);
|
||||
|
||||
internal SystemStorage(World world)
|
||||
{
|
||||
_world = world;
|
||||
}
|
||||
|
||||
public readonly void AddSystem(Type systemType)
|
||||
{
|
||||
_systems.Add(systemType);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly void AddSystem<T>()
|
||||
where T : ISystem, new()
|
||||
{
|
||||
AddSystem(typeof(T));
|
||||
}
|
||||
|
||||
public readonly void RemoveSystem(Type systemType)
|
||||
{
|
||||
_systems.Remove(systemType);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly void RemoveSystem<T>()
|
||||
where T : ISystem, new()
|
||||
{
|
||||
RemoveSystem(typeof(T));
|
||||
}
|
||||
|
||||
internal void CreateSystems()
|
||||
{
|
||||
var builder = new SystemDependencyBuilder(_systems);
|
||||
builder.BuildDependencyGraph();
|
||||
var executionOrder = builder.BuildExecutionOrder();
|
||||
|
||||
var state = new SystemState()
|
||||
{
|
||||
World = _world,
|
||||
};
|
||||
|
||||
foreach (var systemType in executionOrder)
|
||||
{
|
||||
var system = (ISystem?)Activator.CreateInstance(systemType) ?? throw new InvalidOperationException($"Failed to create instance of system type {systemType.Name}.");
|
||||
|
||||
_executionList.Add(system);
|
||||
system.OnCreate(in state);
|
||||
}
|
||||
}
|
||||
|
||||
internal void UpdateSystems()
|
||||
{
|
||||
var state = new SystemState()
|
||||
{
|
||||
World = _world,
|
||||
};
|
||||
|
||||
foreach (var system in _executionList)
|
||||
{
|
||||
system.OnUpdate(in state);
|
||||
}
|
||||
}
|
||||
|
||||
internal void Dispose()
|
||||
{
|
||||
var state = new SystemState()
|
||||
{
|
||||
World = _world,
|
||||
};
|
||||
|
||||
foreach (var system in _executionList)
|
||||
{
|
||||
system.OnDestroy(in state);
|
||||
}
|
||||
|
||||
_systems.Clear();
|
||||
_executionList.Clear();
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
|
||||
|
||||
namespace Ghost.Entities;
|
||||
|
||||
public delegate void ForEach<T0>(ref T0 t0Component);
|
||||
public delegate void ForEach<T0, T1>(ref T0 t0Component, ref T1 t1Component);
|
||||
public delegate void ForEach<T0, T1, T2>(ref T0 t0Component, ref T1 t1Component, ref T2 t2Component);
|
||||
public delegate void ForEach<T0, T1, T2, T3>(ref T0 t0Component, ref T1 t1Component, ref T2 t2Component, ref T3 t3Component);
|
||||
public delegate void ForEach<T0, T1, T2, T3, T4>(ref T0 t0Component, ref T1 t1Component, ref T2 t2Component, ref T3 t3Component, ref T4 t4Component);
|
||||
public delegate void ForEach<T0, T1, T2, T3, T4, T5>(ref T0 t0Component, ref T1 t1Component, ref T2 t2Component, ref T3 t3Component, ref T4 t4Component, ref T5 t5Component);
|
||||
public delegate void ForEach<T0, T1, T2, T3, T4, T5, T6>(ref T0 t0Component, ref T1 t1Component, ref T2 t2Component, ref T3 t3Component, ref T4 t4Component, ref T5 t5Component, ref T6 t6Component);
|
||||
public delegate void ForEach<T0, T1, T2, T3, T4, T5, T6, T7>(ref T0 t0Component, ref T1 t1Component, ref T2 t2Component, ref T3 t3Component, ref T4 t4Component, ref T5 t5Component, ref T6 t6Component, ref T7 t7Component);
|
||||
@@ -1,16 +0,0 @@
|
||||
<#@ template language="C#" #>
|
||||
<#@ output extension=".cs" #>
|
||||
<#@ assembly name="System.Core" #>
|
||||
<#@ import namespace="System.Linq" #>
|
||||
<#@ import namespace="System.Text" #>
|
||||
<#@ include file="Helpers.ttinclude" #>
|
||||
|
||||
namespace Ghost.Entities;
|
||||
|
||||
<# for (var i = 1; i <= Amount; i++)
|
||||
{
|
||||
var generics = AppendGenerics(i);
|
||||
var compGenerics = AppendGenericRefParameters(i);
|
||||
#>
|
||||
public delegate void ForEach<<#= generics #>>(<#= compGenerics #>);
|
||||
<# } #>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,175 +0,0 @@
|
||||
<#@ template language="C#" #>
|
||||
<#@ output extension=".cs" #>
|
||||
<#@ assembly name="System.Core" #>
|
||||
<#@ import namespace="System.Text" #>
|
||||
<#@ import namespace="System.Linq" #>
|
||||
<#@ include file="Helpers.ttinclude" #>
|
||||
|
||||
using Ghost.Core;
|
||||
using Ghost.Entities.Components;
|
||||
using Ghost.Entities.Query;
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
|
||||
namespace Ghost.Entities;
|
||||
|
||||
<# for (int arity = 1; arity <= Amount; arity++) {
|
||||
var generics = AppendGenerics(arity);
|
||||
var restrictions = AppendGenericRestrictions(arity, "unmanaged, IComponentData");
|
||||
var poolParams = Enumerable.Range(0, arity)
|
||||
.Select(i => $"ComponentPool<T{i}> pool{i}")
|
||||
.Aggregate((a, b) => a + ", " + b);
|
||||
var constructorParams = Enumerable.Range(0, arity)
|
||||
.Select(i => $"_pool{i}")
|
||||
.Aggregate((a, b) => a + ", " + b);
|
||||
|
||||
#>
|
||||
public unsafe ref struct QueryEnumerable<<#= generics #>>
|
||||
<#= restrictions #>
|
||||
{
|
||||
private QueryFilter _filter;
|
||||
|
||||
private readonly World _world;
|
||||
|
||||
<# for (int i = 0; i < arity; i++){ #>
|
||||
private readonly ComponentPool<T<#= i #>> _pool<#= i #>;
|
||||
<# } #>
|
||||
private readonly int _count;
|
||||
|
||||
internal QueryEnumerable(World world, <#= poolParams #>, int count)
|
||||
{
|
||||
_filter = new();
|
||||
<# for (int i = 0; i < arity; i++) {#>
|
||||
_filter._all.Add(TypeHandle.Get<T<#= i #>>());
|
||||
<# } #>
|
||||
|
||||
_world = world;
|
||||
|
||||
<# for (int i = 0; i < arity; i++) { #>
|
||||
_pool<#= i #> = pool<#= i #>;
|
||||
<# } #>
|
||||
|
||||
_count = count;
|
||||
}
|
||||
|
||||
internal QueryEnumerable(World world, <#= poolParams #>, int count, ref readonly QueryFilter filter)
|
||||
{
|
||||
_filter = filter;
|
||||
|
||||
_world = world;
|
||||
|
||||
<# for (int i = 0; i < arity; i++) { #>
|
||||
_pool<#= i #> = pool<#= i #>;
|
||||
<# } #>
|
||||
|
||||
_count = count;
|
||||
}
|
||||
|
||||
#pragma warning disable CS9084 // Struct member returns 'this' or other instance members by reference
|
||||
public Enumerator GetEnumerator() => new(_world, <#= constructorParams #>, _count, ref _filter);
|
||||
#pragma warning restore CS9084 // Struct member returns 'this' or other instance members by reference
|
||||
|
||||
public ref struct Enumerator
|
||||
{
|
||||
private ref QueryFilter _filter;
|
||||
private UnsafeBitSet _filterMask;
|
||||
|
||||
private readonly ReadOnlySpan<Entity> _entities;
|
||||
private readonly Stack.Scope _stackScope;
|
||||
|
||||
<# for (int i = 0; i < arity; i++){ #>
|
||||
private readonly ComponentPool<T<#= i #>> _pool<#= i #>;
|
||||
<# } #>
|
||||
|
||||
private int _index;
|
||||
private int _count;
|
||||
|
||||
public QueryItem<<#= generics #>> Current
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
internal Enumerator(World world, <#= poolParams #>, int count, ref QueryFilter filter)
|
||||
{
|
||||
_stackScope = AllocationManager.CreateStackScope();
|
||||
|
||||
_filter = ref filter;
|
||||
_filterMask = _filter.ComputeFilterBitMask(world, Allocator.Stack);
|
||||
|
||||
_entities = world.EntityManager.Entities;
|
||||
|
||||
<# for (int i = 0; i < arity; i++){ #>
|
||||
_pool<#= i #> = pool<#= i #>;
|
||||
<# } #>
|
||||
|
||||
_count = count;
|
||||
_index = -1;
|
||||
|
||||
Current = default;
|
||||
}
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
_index = _filterMask.NextSetBit(_index + 1);
|
||||
if (_index < 0 || _count <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_count--;
|
||||
Current = new QueryItem<<#= generics #>>(_entities[_index], <#= constructorParams #>);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public readonly void Dispose()
|
||||
{
|
||||
_stackScope.Dispose();
|
||||
_filter.Dispose();
|
||||
}
|
||||
}
|
||||
<# for (int i = 1; i <= ExtensionAmount; i++) {
|
||||
var compGenerics = AppendGenerics(i, "TComponent");
|
||||
var compRestrictions = AppendGenericRestrictions(i, "TComponent", "unmanaged, IComponentData");
|
||||
#>
|
||||
|
||||
public QueryEnumerable<<#= generics #>> WithAll<<#= compGenerics #>>()
|
||||
<#= compRestrictions #>
|
||||
{
|
||||
<# for (int j = 0; j < i; j++) {#>
|
||||
_filter._all.Add(TypeHandle.Get<TComponent<#= j #>>());
|
||||
<# } #>
|
||||
return this;
|
||||
}
|
||||
|
||||
public QueryEnumerable<<#= generics #>> WithAny<<#= compGenerics #>>()
|
||||
<#= compRestrictions #>
|
||||
{
|
||||
<# for (int j = 0; j < i; j++) {#>
|
||||
_filter._any.Add(TypeHandle.Get<TComponent<#= j #>>());
|
||||
<# } #>
|
||||
return this;
|
||||
}
|
||||
|
||||
public QueryEnumerable<<#= generics #>> WithAbsent<<#= compGenerics #>>()
|
||||
<#= compRestrictions #>
|
||||
{
|
||||
<# for (int j = 0; j < i; j++) {#>
|
||||
_filter._absent.Add(TypeHandle.Get<TComponent<#= j #>>());
|
||||
<# } #>
|
||||
return this;
|
||||
}
|
||||
|
||||
public QueryEnumerable<<#= generics #>> WithDisabled<<#= compGenerics #>>()
|
||||
<#= compRestrictions #>
|
||||
{
|
||||
<# for (int j = 0; j < i; j++) {#>
|
||||
_filter._disabled.Add(TypeHandle.Get<TComponent<#= j #>>());
|
||||
<# } #>
|
||||
return this;
|
||||
}
|
||||
<# } #>
|
||||
}
|
||||
|
||||
<# } #>
|
||||
@@ -1,319 +0,0 @@
|
||||
|
||||
|
||||
using Ghost.Entities.Components;
|
||||
using Ghost.Entities.Query;
|
||||
|
||||
namespace Ghost.Entities;
|
||||
|
||||
public readonly struct QueryItem<T0>
|
||||
where T0 : unmanaged, IComponentData
|
||||
{
|
||||
private readonly Entity _entity;
|
||||
private readonly ComponentPool<T0> _pool0;
|
||||
|
||||
internal QueryItem(Entity entity, ComponentPool<T0> pool0)
|
||||
{
|
||||
_entity = entity;
|
||||
_pool0 = pool0;
|
||||
}
|
||||
|
||||
public Entity Entity => _entity;
|
||||
|
||||
public ref T0 Component0 => ref _pool0.GetRef(_entity);
|
||||
|
||||
// Deconstruct into tuple-like values
|
||||
public void Deconstruct(out Entity entity, out CompRef<T0> c0)
|
||||
{
|
||||
entity = _entity;
|
||||
|
||||
c0 = new(ref _pool0.GetRef(_entity));
|
||||
}
|
||||
}
|
||||
|
||||
public readonly struct QueryItem<T0, T1>
|
||||
where T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData
|
||||
{
|
||||
private readonly Entity _entity;
|
||||
private readonly ComponentPool<T0> _pool0;
|
||||
private readonly ComponentPool<T1> _pool1;
|
||||
|
||||
internal QueryItem(Entity entity, ComponentPool<T0> pool0, ComponentPool<T1> pool1)
|
||||
{
|
||||
_entity = entity;
|
||||
_pool0 = pool0;
|
||||
_pool1 = pool1;
|
||||
}
|
||||
|
||||
public Entity Entity => _entity;
|
||||
|
||||
public ref T0 Component0 => ref _pool0.GetRef(_entity);
|
||||
public ref T1 Component1 => ref _pool1.GetRef(_entity);
|
||||
|
||||
// Deconstruct into tuple-like values
|
||||
public void Deconstruct(out Entity entity, out CompRef<T0> c0, out CompRef<T1> c1)
|
||||
{
|
||||
entity = _entity;
|
||||
|
||||
c0 = new(ref _pool0.GetRef(_entity));
|
||||
c1 = new(ref _pool1.GetRef(_entity));
|
||||
}
|
||||
}
|
||||
|
||||
public readonly struct QueryItem<T0, T1, T2>
|
||||
where T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData where T2 : unmanaged, IComponentData
|
||||
{
|
||||
private readonly Entity _entity;
|
||||
private readonly ComponentPool<T0> _pool0;
|
||||
private readonly ComponentPool<T1> _pool1;
|
||||
private readonly ComponentPool<T2> _pool2;
|
||||
|
||||
internal QueryItem(Entity entity, ComponentPool<T0> pool0, ComponentPool<T1> pool1, ComponentPool<T2> pool2)
|
||||
{
|
||||
_entity = entity;
|
||||
_pool0 = pool0;
|
||||
_pool1 = pool1;
|
||||
_pool2 = pool2;
|
||||
}
|
||||
|
||||
public Entity Entity => _entity;
|
||||
|
||||
public ref T0 Component0 => ref _pool0.GetRef(_entity);
|
||||
public ref T1 Component1 => ref _pool1.GetRef(_entity);
|
||||
public ref T2 Component2 => ref _pool2.GetRef(_entity);
|
||||
|
||||
// Deconstruct into tuple-like values
|
||||
public void Deconstruct(out Entity entity, out CompRef<T0> c0, out CompRef<T1> c1, out CompRef<T2> c2)
|
||||
{
|
||||
entity = _entity;
|
||||
|
||||
c0 = new(ref _pool0.GetRef(_entity));
|
||||
c1 = new(ref _pool1.GetRef(_entity));
|
||||
c2 = new(ref _pool2.GetRef(_entity));
|
||||
}
|
||||
}
|
||||
|
||||
public readonly struct QueryItem<T0, T1, T2, T3>
|
||||
where T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData where T2 : unmanaged, IComponentData where T3 : unmanaged, IComponentData
|
||||
{
|
||||
private readonly Entity _entity;
|
||||
private readonly ComponentPool<T0> _pool0;
|
||||
private readonly ComponentPool<T1> _pool1;
|
||||
private readonly ComponentPool<T2> _pool2;
|
||||
private readonly ComponentPool<T3> _pool3;
|
||||
|
||||
internal QueryItem(Entity entity, ComponentPool<T0> pool0, ComponentPool<T1> pool1, ComponentPool<T2> pool2, ComponentPool<T3> pool3)
|
||||
{
|
||||
_entity = entity;
|
||||
_pool0 = pool0;
|
||||
_pool1 = pool1;
|
||||
_pool2 = pool2;
|
||||
_pool3 = pool3;
|
||||
}
|
||||
|
||||
public Entity Entity => _entity;
|
||||
|
||||
public ref T0 Component0 => ref _pool0.GetRef(_entity);
|
||||
public ref T1 Component1 => ref _pool1.GetRef(_entity);
|
||||
public ref T2 Component2 => ref _pool2.GetRef(_entity);
|
||||
public ref T3 Component3 => ref _pool3.GetRef(_entity);
|
||||
|
||||
// Deconstruct into tuple-like values
|
||||
public void Deconstruct(out Entity entity, out CompRef<T0> c0, out CompRef<T1> c1, out CompRef<T2> c2, out CompRef<T3> c3)
|
||||
{
|
||||
entity = _entity;
|
||||
|
||||
c0 = new(ref _pool0.GetRef(_entity));
|
||||
c1 = new(ref _pool1.GetRef(_entity));
|
||||
c2 = new(ref _pool2.GetRef(_entity));
|
||||
c3 = new(ref _pool3.GetRef(_entity));
|
||||
}
|
||||
}
|
||||
|
||||
public readonly struct QueryItem<T0, T1, T2, T3, T4>
|
||||
where T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData where T2 : unmanaged, IComponentData where T3 : unmanaged, IComponentData where T4 : unmanaged, IComponentData
|
||||
{
|
||||
private readonly Entity _entity;
|
||||
private readonly ComponentPool<T0> _pool0;
|
||||
private readonly ComponentPool<T1> _pool1;
|
||||
private readonly ComponentPool<T2> _pool2;
|
||||
private readonly ComponentPool<T3> _pool3;
|
||||
private readonly ComponentPool<T4> _pool4;
|
||||
|
||||
internal QueryItem(Entity entity, ComponentPool<T0> pool0, ComponentPool<T1> pool1, ComponentPool<T2> pool2, ComponentPool<T3> pool3, ComponentPool<T4> pool4)
|
||||
{
|
||||
_entity = entity;
|
||||
_pool0 = pool0;
|
||||
_pool1 = pool1;
|
||||
_pool2 = pool2;
|
||||
_pool3 = pool3;
|
||||
_pool4 = pool4;
|
||||
}
|
||||
|
||||
public Entity Entity => _entity;
|
||||
|
||||
public ref T0 Component0 => ref _pool0.GetRef(_entity);
|
||||
public ref T1 Component1 => ref _pool1.GetRef(_entity);
|
||||
public ref T2 Component2 => ref _pool2.GetRef(_entity);
|
||||
public ref T3 Component3 => ref _pool3.GetRef(_entity);
|
||||
public ref T4 Component4 => ref _pool4.GetRef(_entity);
|
||||
|
||||
// Deconstruct into tuple-like values
|
||||
public void Deconstruct(out Entity entity, out CompRef<T0> c0, out CompRef<T1> c1, out CompRef<T2> c2, out CompRef<T3> c3, out CompRef<T4> c4)
|
||||
{
|
||||
entity = _entity;
|
||||
|
||||
c0 = new(ref _pool0.GetRef(_entity));
|
||||
c1 = new(ref _pool1.GetRef(_entity));
|
||||
c2 = new(ref _pool2.GetRef(_entity));
|
||||
c3 = new(ref _pool3.GetRef(_entity));
|
||||
c4 = new(ref _pool4.GetRef(_entity));
|
||||
}
|
||||
}
|
||||
|
||||
public readonly struct QueryItem<T0, T1, T2, T3, T4, T5>
|
||||
where T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData where T2 : unmanaged, IComponentData where T3 : unmanaged, IComponentData where T4 : unmanaged, IComponentData where T5 : unmanaged, IComponentData
|
||||
{
|
||||
private readonly Entity _entity;
|
||||
private readonly ComponentPool<T0> _pool0;
|
||||
private readonly ComponentPool<T1> _pool1;
|
||||
private readonly ComponentPool<T2> _pool2;
|
||||
private readonly ComponentPool<T3> _pool3;
|
||||
private readonly ComponentPool<T4> _pool4;
|
||||
private readonly ComponentPool<T5> _pool5;
|
||||
|
||||
internal QueryItem(Entity entity, ComponentPool<T0> pool0, ComponentPool<T1> pool1, ComponentPool<T2> pool2, ComponentPool<T3> pool3, ComponentPool<T4> pool4, ComponentPool<T5> pool5)
|
||||
{
|
||||
_entity = entity;
|
||||
_pool0 = pool0;
|
||||
_pool1 = pool1;
|
||||
_pool2 = pool2;
|
||||
_pool3 = pool3;
|
||||
_pool4 = pool4;
|
||||
_pool5 = pool5;
|
||||
}
|
||||
|
||||
public Entity Entity => _entity;
|
||||
|
||||
public ref T0 Component0 => ref _pool0.GetRef(_entity);
|
||||
public ref T1 Component1 => ref _pool1.GetRef(_entity);
|
||||
public ref T2 Component2 => ref _pool2.GetRef(_entity);
|
||||
public ref T3 Component3 => ref _pool3.GetRef(_entity);
|
||||
public ref T4 Component4 => ref _pool4.GetRef(_entity);
|
||||
public ref T5 Component5 => ref _pool5.GetRef(_entity);
|
||||
|
||||
// Deconstruct into tuple-like values
|
||||
public void Deconstruct(out Entity entity, out CompRef<T0> c0, out CompRef<T1> c1, out CompRef<T2> c2, out CompRef<T3> c3, out CompRef<T4> c4, out CompRef<T5> c5)
|
||||
{
|
||||
entity = _entity;
|
||||
|
||||
c0 = new(ref _pool0.GetRef(_entity));
|
||||
c1 = new(ref _pool1.GetRef(_entity));
|
||||
c2 = new(ref _pool2.GetRef(_entity));
|
||||
c3 = new(ref _pool3.GetRef(_entity));
|
||||
c4 = new(ref _pool4.GetRef(_entity));
|
||||
c5 = new(ref _pool5.GetRef(_entity));
|
||||
}
|
||||
}
|
||||
|
||||
public readonly struct QueryItem<T0, T1, T2, T3, T4, T5, T6>
|
||||
where T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData where T2 : unmanaged, IComponentData where T3 : unmanaged, IComponentData where T4 : unmanaged, IComponentData where T5 : unmanaged, IComponentData where T6 : unmanaged, IComponentData
|
||||
{
|
||||
private readonly Entity _entity;
|
||||
private readonly ComponentPool<T0> _pool0;
|
||||
private readonly ComponentPool<T1> _pool1;
|
||||
private readonly ComponentPool<T2> _pool2;
|
||||
private readonly ComponentPool<T3> _pool3;
|
||||
private readonly ComponentPool<T4> _pool4;
|
||||
private readonly ComponentPool<T5> _pool5;
|
||||
private readonly ComponentPool<T6> _pool6;
|
||||
|
||||
internal QueryItem(Entity entity, ComponentPool<T0> pool0, ComponentPool<T1> pool1, ComponentPool<T2> pool2, ComponentPool<T3> pool3, ComponentPool<T4> pool4, ComponentPool<T5> pool5, ComponentPool<T6> pool6)
|
||||
{
|
||||
_entity = entity;
|
||||
_pool0 = pool0;
|
||||
_pool1 = pool1;
|
||||
_pool2 = pool2;
|
||||
_pool3 = pool3;
|
||||
_pool4 = pool4;
|
||||
_pool5 = pool5;
|
||||
_pool6 = pool6;
|
||||
}
|
||||
|
||||
public Entity Entity => _entity;
|
||||
|
||||
public ref T0 Component0 => ref _pool0.GetRef(_entity);
|
||||
public ref T1 Component1 => ref _pool1.GetRef(_entity);
|
||||
public ref T2 Component2 => ref _pool2.GetRef(_entity);
|
||||
public ref T3 Component3 => ref _pool3.GetRef(_entity);
|
||||
public ref T4 Component4 => ref _pool4.GetRef(_entity);
|
||||
public ref T5 Component5 => ref _pool5.GetRef(_entity);
|
||||
public ref T6 Component6 => ref _pool6.GetRef(_entity);
|
||||
|
||||
// Deconstruct into tuple-like values
|
||||
public void Deconstruct(out Entity entity, out CompRef<T0> c0, out CompRef<T1> c1, out CompRef<T2> c2, out CompRef<T3> c3, out CompRef<T4> c4, out CompRef<T5> c5, out CompRef<T6> c6)
|
||||
{
|
||||
entity = _entity;
|
||||
|
||||
c0 = new(ref _pool0.GetRef(_entity));
|
||||
c1 = new(ref _pool1.GetRef(_entity));
|
||||
c2 = new(ref _pool2.GetRef(_entity));
|
||||
c3 = new(ref _pool3.GetRef(_entity));
|
||||
c4 = new(ref _pool4.GetRef(_entity));
|
||||
c5 = new(ref _pool5.GetRef(_entity));
|
||||
c6 = new(ref _pool6.GetRef(_entity));
|
||||
}
|
||||
}
|
||||
|
||||
public readonly struct QueryItem<T0, T1, T2, T3, T4, T5, T6, T7>
|
||||
where T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData where T2 : unmanaged, IComponentData where T3 : unmanaged, IComponentData where T4 : unmanaged, IComponentData where T5 : unmanaged, IComponentData where T6 : unmanaged, IComponentData where T7 : unmanaged, IComponentData
|
||||
{
|
||||
private readonly Entity _entity;
|
||||
private readonly ComponentPool<T0> _pool0;
|
||||
private readonly ComponentPool<T1> _pool1;
|
||||
private readonly ComponentPool<T2> _pool2;
|
||||
private readonly ComponentPool<T3> _pool3;
|
||||
private readonly ComponentPool<T4> _pool4;
|
||||
private readonly ComponentPool<T5> _pool5;
|
||||
private readonly ComponentPool<T6> _pool6;
|
||||
private readonly ComponentPool<T7> _pool7;
|
||||
|
||||
internal QueryItem(Entity entity, ComponentPool<T0> pool0, ComponentPool<T1> pool1, ComponentPool<T2> pool2, ComponentPool<T3> pool3, ComponentPool<T4> pool4, ComponentPool<T5> pool5, ComponentPool<T6> pool6, ComponentPool<T7> pool7)
|
||||
{
|
||||
_entity = entity;
|
||||
_pool0 = pool0;
|
||||
_pool1 = pool1;
|
||||
_pool2 = pool2;
|
||||
_pool3 = pool3;
|
||||
_pool4 = pool4;
|
||||
_pool5 = pool5;
|
||||
_pool6 = pool6;
|
||||
_pool7 = pool7;
|
||||
}
|
||||
|
||||
public Entity Entity => _entity;
|
||||
|
||||
public ref T0 Component0 => ref _pool0.GetRef(_entity);
|
||||
public ref T1 Component1 => ref _pool1.GetRef(_entity);
|
||||
public ref T2 Component2 => ref _pool2.GetRef(_entity);
|
||||
public ref T3 Component3 => ref _pool3.GetRef(_entity);
|
||||
public ref T4 Component4 => ref _pool4.GetRef(_entity);
|
||||
public ref T5 Component5 => ref _pool5.GetRef(_entity);
|
||||
public ref T6 Component6 => ref _pool6.GetRef(_entity);
|
||||
public ref T7 Component7 => ref _pool7.GetRef(_entity);
|
||||
|
||||
// Deconstruct into tuple-like values
|
||||
public void Deconstruct(out Entity entity, out CompRef<T0> c0, out CompRef<T1> c1, out CompRef<T2> c2, out CompRef<T3> c3, out CompRef<T4> c4, out CompRef<T5> c5, out CompRef<T6> c6, out CompRef<T7> c7)
|
||||
{
|
||||
entity = _entity;
|
||||
|
||||
c0 = new(ref _pool0.GetRef(_entity));
|
||||
c1 = new(ref _pool1.GetRef(_entity));
|
||||
c2 = new(ref _pool2.GetRef(_entity));
|
||||
c3 = new(ref _pool3.GetRef(_entity));
|
||||
c4 = new(ref _pool4.GetRef(_entity));
|
||||
c5 = new(ref _pool5.GetRef(_entity));
|
||||
c6 = new(ref _pool6.GetRef(_entity));
|
||||
c7 = new(ref _pool7.GetRef(_entity));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
<#@ template language="C#" debug="false" hostspecific="true" #>
|
||||
<#@ assembly name="System.Core" #>
|
||||
<#@ import namespace="System.Linq" #>
|
||||
<#@ include file="Helpers.ttinclude" #>
|
||||
<#@ output extension=".cs" #>
|
||||
|
||||
using Ghost.Entities.Components;
|
||||
using Ghost.Entities.Query;
|
||||
|
||||
namespace Ghost.Entities;
|
||||
|
||||
<# for (int arity = 1; arity <= Amount; arity++)
|
||||
{
|
||||
var generics = AppendGenerics(arity);
|
||||
var restrictions = AppendGenericRestrictions(arity, "unmanaged, IComponentData");
|
||||
var constructorParams = Enumerable.Range(0, arity)
|
||||
.Select(i => $"ComponentPool<T{i}> pool{i}")
|
||||
.Aggregate((a, b) => a + ", " + b);
|
||||
var deconstructParams = Enumerable.Range(0, arity)
|
||||
.Select(i => {
|
||||
var name = $"c{i}";
|
||||
return arity == 1
|
||||
? $"out CompRef<T0> {name}"
|
||||
: $"out CompRef<T{i}> {name}";
|
||||
})
|
||||
.Aggregate((a, b) => a + ", " + b);
|
||||
#>
|
||||
public readonly struct QueryItem<<#= generics #>>
|
||||
<#= restrictions #>
|
||||
{
|
||||
private readonly Entity _entity;
|
||||
<# for (int i = 0; i < arity; i++){ #>
|
||||
private readonly ComponentPool<T<#= i #>> _pool<#= i #>;
|
||||
<# } #>
|
||||
|
||||
internal QueryItem(Entity entity, <#= constructorParams #>)
|
||||
{
|
||||
_entity = entity;
|
||||
<# for (int i = 0; i < arity; i++){ #>
|
||||
_pool<#= i #> = pool<#= i #>;
|
||||
<# } #>
|
||||
}
|
||||
|
||||
public Entity Entity => _entity;
|
||||
|
||||
<# for (int i = 0; i < arity; i++){ #>
|
||||
public ref T<#= i #> Component<#= i #> => ref _pool<#= i #>.GetRef(_entity);
|
||||
<# } #>
|
||||
|
||||
// Deconstruct into tuple-like values
|
||||
public void Deconstruct(out Entity entity, <#= deconstructParams #>)
|
||||
{
|
||||
entity = _entity;
|
||||
|
||||
<# for (int i = 0; i < arity; i++){ #>
|
||||
c<#= i #> = new (ref _pool<#= i #>.GetRef(_entity));
|
||||
<# } #>
|
||||
}
|
||||
}
|
||||
|
||||
<# } #>
|
||||
@@ -1,22 +0,0 @@
|
||||
|
||||
|
||||
using Ghost.Entities.Components;
|
||||
|
||||
namespace Ghost.Entities;
|
||||
|
||||
public delegate void QueryRefComponent<T0>(Entity entity, ref T0 t0Component)
|
||||
where T0 : unmanaged, IComponentData;
|
||||
public delegate void QueryRefComponent<T0, T1>(Entity entity, ref T0 t0Component, ref T1 t1Component)
|
||||
where T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData;
|
||||
public delegate void QueryRefComponent<T0, T1, T2>(Entity entity, ref T0 t0Component, ref T1 t1Component, ref T2 t2Component)
|
||||
where T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData where T2 : unmanaged, IComponentData;
|
||||
public delegate void QueryRefComponent<T0, T1, T2, T3>(Entity entity, ref T0 t0Component, ref T1 t1Component, ref T2 t2Component, ref T3 t3Component)
|
||||
where T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData where T2 : unmanaged, IComponentData where T3 : unmanaged, IComponentData;
|
||||
public delegate void QueryRefComponent<T0, T1, T2, T3, T4>(Entity entity, ref T0 t0Component, ref T1 t1Component, ref T2 t2Component, ref T3 t3Component, ref T4 t4Component)
|
||||
where T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData where T2 : unmanaged, IComponentData where T3 : unmanaged, IComponentData where T4 : unmanaged, IComponentData;
|
||||
public delegate void QueryRefComponent<T0, T1, T2, T3, T4, T5>(Entity entity, ref T0 t0Component, ref T1 t1Component, ref T2 t2Component, ref T3 t3Component, ref T4 t4Component, ref T5 t5Component)
|
||||
where T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData where T2 : unmanaged, IComponentData where T3 : unmanaged, IComponentData where T4 : unmanaged, IComponentData where T5 : unmanaged, IComponentData;
|
||||
public delegate void QueryRefComponent<T0, T1, T2, T3, T4, T5, T6>(Entity entity, ref T0 t0Component, ref T1 t1Component, ref T2 t2Component, ref T3 t3Component, ref T4 t4Component, ref T5 t5Component, ref T6 t6Component)
|
||||
where T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData where T2 : unmanaged, IComponentData where T3 : unmanaged, IComponentData where T4 : unmanaged, IComponentData where T5 : unmanaged, IComponentData where T6 : unmanaged, IComponentData;
|
||||
public delegate void QueryRefComponent<T0, T1, T2, T3, T4, T5, T6, T7>(Entity entity, ref T0 t0Component, ref T1 t1Component, ref T2 t2Component, ref T3 t3Component, ref T4 t4Component, ref T5 t5Component, ref T6 t6Component, ref T7 t7Component)
|
||||
where T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData where T2 : unmanaged, IComponentData where T3 : unmanaged, IComponentData where T4 : unmanaged, IComponentData where T5 : unmanaged, IComponentData where T6 : unmanaged, IComponentData where T7 : unmanaged, IComponentData;
|
||||
@@ -1,21 +0,0 @@
|
||||
<#@ template language="C#" #>
|
||||
<#@ output extension=".cs" #>
|
||||
<#@ import namespace="System.Text" #>
|
||||
<#@ include file="Helpers.ttinclude" #>
|
||||
|
||||
using Ghost.Entities.Components;
|
||||
|
||||
namespace Ghost.Entities;
|
||||
|
||||
<#
|
||||
for (var index = 1; index <= Amount; index++)
|
||||
{
|
||||
var generics = AppendGenerics(index);
|
||||
var parameters = AppendGenericRefParameters(index);
|
||||
var restrictions = AppendGenericRestrictions(index, "unmanaged, IComponentData");
|
||||
#>
|
||||
public delegate void QueryRefComponent<<#= generics #>>(Entity entity, <#= parameters.ToString() #>)
|
||||
<#= restrictions.ToString() #>;
|
||||
<#
|
||||
}
|
||||
#>
|
||||
@@ -1,242 +0,0 @@
|
||||
|
||||
|
||||
using Ghost.Entities.Components;
|
||||
using Ghost.Entities.Query;
|
||||
|
||||
namespace Ghost.Entities;
|
||||
|
||||
public partial class World
|
||||
{
|
||||
public QueryEnumerable<T0> Query<T0>()
|
||||
where T0 : unmanaged, IComponentData
|
||||
{
|
||||
if (!(_componentStorage.TryGetPool<T0>(out var pool0)))
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
return new QueryEnumerable<T0>(
|
||||
this,
|
||||
pool0,
|
||||
pool0.Count);
|
||||
}
|
||||
|
||||
public QueryEnumerable<T0> QueryFilter<T0>(ref readonly QueryFilter filter)
|
||||
where T0 : unmanaged, IComponentData
|
||||
{
|
||||
if (!(_componentStorage.TryGetPool<T0>(out var pool0)))
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
return new QueryEnumerable<T0>(
|
||||
this,
|
||||
pool0,
|
||||
pool0.Count,
|
||||
in filter);
|
||||
}
|
||||
|
||||
public QueryEnumerable<T0, T1> Query<T0, T1>()
|
||||
where T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData
|
||||
{
|
||||
if (!(_componentStorage.TryGetPool<T0>(out var pool0) && _componentStorage.TryGetPool<T1>(out var pool1)))
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
return new QueryEnumerable<T0, T1>(
|
||||
this,
|
||||
pool0, pool1,
|
||||
pool0.Count);
|
||||
}
|
||||
|
||||
public QueryEnumerable<T0, T1> QueryFilter<T0, T1>(ref readonly QueryFilter filter)
|
||||
where T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData
|
||||
{
|
||||
if (!(_componentStorage.TryGetPool<T0>(out var pool0) && _componentStorage.TryGetPool<T1>(out var pool1)))
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
return new QueryEnumerable<T0, T1>(
|
||||
this,
|
||||
pool0, pool1,
|
||||
pool0.Count,
|
||||
in filter);
|
||||
}
|
||||
|
||||
public QueryEnumerable<T0, T1, T2> Query<T0, T1, T2>()
|
||||
where T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData where T2 : unmanaged, IComponentData
|
||||
{
|
||||
if (!(_componentStorage.TryGetPool<T0>(out var pool0) && _componentStorage.TryGetPool<T1>(out var pool1) && _componentStorage.TryGetPool<T2>(out var pool2)))
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
return new QueryEnumerable<T0, T1, T2>(
|
||||
this,
|
||||
pool0, pool1, pool2,
|
||||
pool0.Count);
|
||||
}
|
||||
|
||||
public QueryEnumerable<T0, T1, T2> QueryFilter<T0, T1, T2>(ref readonly QueryFilter filter)
|
||||
where T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData where T2 : unmanaged, IComponentData
|
||||
{
|
||||
if (!(_componentStorage.TryGetPool<T0>(out var pool0) && _componentStorage.TryGetPool<T1>(out var pool1) && _componentStorage.TryGetPool<T2>(out var pool2)))
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
return new QueryEnumerable<T0, T1, T2>(
|
||||
this,
|
||||
pool0, pool1, pool2,
|
||||
pool0.Count,
|
||||
in filter);
|
||||
}
|
||||
|
||||
public QueryEnumerable<T0, T1, T2, T3> Query<T0, T1, T2, T3>()
|
||||
where T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData where T2 : unmanaged, IComponentData where T3 : unmanaged, IComponentData
|
||||
{
|
||||
if (!(_componentStorage.TryGetPool<T0>(out var pool0) && _componentStorage.TryGetPool<T1>(out var pool1) && _componentStorage.TryGetPool<T2>(out var pool2) && _componentStorage.TryGetPool<T3>(out var pool3)))
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
return new QueryEnumerable<T0, T1, T2, T3>(
|
||||
this,
|
||||
pool0, pool1, pool2, pool3,
|
||||
pool0.Count);
|
||||
}
|
||||
|
||||
public QueryEnumerable<T0, T1, T2, T3> QueryFilter<T0, T1, T2, T3>(ref readonly QueryFilter filter)
|
||||
where T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData where T2 : unmanaged, IComponentData where T3 : unmanaged, IComponentData
|
||||
{
|
||||
if (!(_componentStorage.TryGetPool<T0>(out var pool0) && _componentStorage.TryGetPool<T1>(out var pool1) && _componentStorage.TryGetPool<T2>(out var pool2) && _componentStorage.TryGetPool<T3>(out var pool3)))
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
return new QueryEnumerable<T0, T1, T2, T3>(
|
||||
this,
|
||||
pool0, pool1, pool2, pool3,
|
||||
pool0.Count,
|
||||
in filter);
|
||||
}
|
||||
|
||||
public QueryEnumerable<T0, T1, T2, T3, T4> Query<T0, T1, T2, T3, T4>()
|
||||
where T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData where T2 : unmanaged, IComponentData where T3 : unmanaged, IComponentData where T4 : unmanaged, IComponentData
|
||||
{
|
||||
if (!(_componentStorage.TryGetPool<T0>(out var pool0) && _componentStorage.TryGetPool<T1>(out var pool1) && _componentStorage.TryGetPool<T2>(out var pool2) && _componentStorage.TryGetPool<T3>(out var pool3) && _componentStorage.TryGetPool<T4>(out var pool4)))
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
return new QueryEnumerable<T0, T1, T2, T3, T4>(
|
||||
this,
|
||||
pool0, pool1, pool2, pool3, pool4,
|
||||
pool0.Count);
|
||||
}
|
||||
|
||||
public QueryEnumerable<T0, T1, T2, T3, T4> QueryFilter<T0, T1, T2, T3, T4>(ref readonly QueryFilter filter)
|
||||
where T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData where T2 : unmanaged, IComponentData where T3 : unmanaged, IComponentData where T4 : unmanaged, IComponentData
|
||||
{
|
||||
if (!(_componentStorage.TryGetPool<T0>(out var pool0) && _componentStorage.TryGetPool<T1>(out var pool1) && _componentStorage.TryGetPool<T2>(out var pool2) && _componentStorage.TryGetPool<T3>(out var pool3) && _componentStorage.TryGetPool<T4>(out var pool4)))
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
return new QueryEnumerable<T0, T1, T2, T3, T4>(
|
||||
this,
|
||||
pool0, pool1, pool2, pool3, pool4,
|
||||
pool0.Count,
|
||||
in filter);
|
||||
}
|
||||
|
||||
public QueryEnumerable<T0, T1, T2, T3, T4, T5> Query<T0, T1, T2, T3, T4, T5>()
|
||||
where T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData where T2 : unmanaged, IComponentData where T3 : unmanaged, IComponentData where T4 : unmanaged, IComponentData where T5 : unmanaged, IComponentData
|
||||
{
|
||||
if (!(_componentStorage.TryGetPool<T0>(out var pool0) && _componentStorage.TryGetPool<T1>(out var pool1) && _componentStorage.TryGetPool<T2>(out var pool2) && _componentStorage.TryGetPool<T3>(out var pool3) && _componentStorage.TryGetPool<T4>(out var pool4) && _componentStorage.TryGetPool<T5>(out var pool5)))
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
return new QueryEnumerable<T0, T1, T2, T3, T4, T5>(
|
||||
this,
|
||||
pool0, pool1, pool2, pool3, pool4, pool5,
|
||||
pool0.Count);
|
||||
}
|
||||
|
||||
public QueryEnumerable<T0, T1, T2, T3, T4, T5> QueryFilter<T0, T1, T2, T3, T4, T5>(ref readonly QueryFilter filter)
|
||||
where T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData where T2 : unmanaged, IComponentData where T3 : unmanaged, IComponentData where T4 : unmanaged, IComponentData where T5 : unmanaged, IComponentData
|
||||
{
|
||||
if (!(_componentStorage.TryGetPool<T0>(out var pool0) && _componentStorage.TryGetPool<T1>(out var pool1) && _componentStorage.TryGetPool<T2>(out var pool2) && _componentStorage.TryGetPool<T3>(out var pool3) && _componentStorage.TryGetPool<T4>(out var pool4) && _componentStorage.TryGetPool<T5>(out var pool5)))
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
return new QueryEnumerable<T0, T1, T2, T3, T4, T5>(
|
||||
this,
|
||||
pool0, pool1, pool2, pool3, pool4, pool5,
|
||||
pool0.Count,
|
||||
in filter);
|
||||
}
|
||||
|
||||
public QueryEnumerable<T0, T1, T2, T3, T4, T5, T6> Query<T0, T1, T2, T3, T4, T5, T6>()
|
||||
where T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData where T2 : unmanaged, IComponentData where T3 : unmanaged, IComponentData where T4 : unmanaged, IComponentData where T5 : unmanaged, IComponentData where T6 : unmanaged, IComponentData
|
||||
{
|
||||
if (!(_componentStorage.TryGetPool<T0>(out var pool0) && _componentStorage.TryGetPool<T1>(out var pool1) && _componentStorage.TryGetPool<T2>(out var pool2) && _componentStorage.TryGetPool<T3>(out var pool3) && _componentStorage.TryGetPool<T4>(out var pool4) && _componentStorage.TryGetPool<T5>(out var pool5) && _componentStorage.TryGetPool<T6>(out var pool6)))
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
return new QueryEnumerable<T0, T1, T2, T3, T4, T5, T6>(
|
||||
this,
|
||||
pool0, pool1, pool2, pool3, pool4, pool5, pool6,
|
||||
pool0.Count);
|
||||
}
|
||||
|
||||
public QueryEnumerable<T0, T1, T2, T3, T4, T5, T6> QueryFilter<T0, T1, T2, T3, T4, T5, T6>(ref readonly QueryFilter filter)
|
||||
where T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData where T2 : unmanaged, IComponentData where T3 : unmanaged, IComponentData where T4 : unmanaged, IComponentData where T5 : unmanaged, IComponentData where T6 : unmanaged, IComponentData
|
||||
{
|
||||
if (!(_componentStorage.TryGetPool<T0>(out var pool0) && _componentStorage.TryGetPool<T1>(out var pool1) && _componentStorage.TryGetPool<T2>(out var pool2) && _componentStorage.TryGetPool<T3>(out var pool3) && _componentStorage.TryGetPool<T4>(out var pool4) && _componentStorage.TryGetPool<T5>(out var pool5) && _componentStorage.TryGetPool<T6>(out var pool6)))
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
return new QueryEnumerable<T0, T1, T2, T3, T4, T5, T6>(
|
||||
this,
|
||||
pool0, pool1, pool2, pool3, pool4, pool5, pool6,
|
||||
pool0.Count,
|
||||
in filter);
|
||||
}
|
||||
|
||||
public QueryEnumerable<T0, T1, T2, T3, T4, T5, T6, T7> Query<T0, T1, T2, T3, T4, T5, T6, T7>()
|
||||
where T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData where T2 : unmanaged, IComponentData where T3 : unmanaged, IComponentData where T4 : unmanaged, IComponentData where T5 : unmanaged, IComponentData where T6 : unmanaged, IComponentData where T7 : unmanaged, IComponentData
|
||||
{
|
||||
if (!(_componentStorage.TryGetPool<T0>(out var pool0) && _componentStorage.TryGetPool<T1>(out var pool1) && _componentStorage.TryGetPool<T2>(out var pool2) && _componentStorage.TryGetPool<T3>(out var pool3) && _componentStorage.TryGetPool<T4>(out var pool4) && _componentStorage.TryGetPool<T5>(out var pool5) && _componentStorage.TryGetPool<T6>(out var pool6) && _componentStorage.TryGetPool<T7>(out var pool7)))
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
return new QueryEnumerable<T0, T1, T2, T3, T4, T5, T6, T7>(
|
||||
this,
|
||||
pool0, pool1, pool2, pool3, pool4, pool5, pool6, pool7,
|
||||
pool0.Count);
|
||||
}
|
||||
|
||||
public QueryEnumerable<T0, T1, T2, T3, T4, T5, T6, T7> QueryFilter<T0, T1, T2, T3, T4, T5, T6, T7>(ref readonly QueryFilter filter)
|
||||
where T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData where T2 : unmanaged, IComponentData where T3 : unmanaged, IComponentData where T4 : unmanaged, IComponentData where T5 : unmanaged, IComponentData where T6 : unmanaged, IComponentData where T7 : unmanaged, IComponentData
|
||||
{
|
||||
if (!(_componentStorage.TryGetPool<T0>(out var pool0) && _componentStorage.TryGetPool<T1>(out var pool1) && _componentStorage.TryGetPool<T2>(out var pool2) && _componentStorage.TryGetPool<T3>(out var pool3) && _componentStorage.TryGetPool<T4>(out var pool4) && _componentStorage.TryGetPool<T5>(out var pool5) && _componentStorage.TryGetPool<T6>(out var pool6) && _componentStorage.TryGetPool<T7>(out var pool7)))
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
return new QueryEnumerable<T0, T1, T2, T3, T4, T5, T6, T7>(
|
||||
this,
|
||||
pool0, pool1, pool2, pool3, pool4, pool5, pool6, pool7,
|
||||
pool0.Count,
|
||||
in filter);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
<#@ template language="C#" #>
|
||||
<#@ output extension=".cs" #>
|
||||
<#@ assembly name="System.Core" #>
|
||||
<#@ import namespace="System.Linq" #>
|
||||
<#@ import namespace="System.Text" #>
|
||||
<#@ include file="Helpers.ttinclude" #>
|
||||
|
||||
using Ghost.Entities.Components;
|
||||
using Ghost.Entities.Query;
|
||||
|
||||
namespace Ghost.Entities;
|
||||
|
||||
public partial class World
|
||||
{
|
||||
<# for (var index = 1; index <= Amount; index++) {
|
||||
var generics = AppendGenerics(index);
|
||||
var restrictions = AppendGenericRestrictions(index, "unmanaged, IComponentData");
|
||||
var tryGetPools = TryGetComponentPools(index);
|
||||
var poolParams = Enumerable.Range(0, index)
|
||||
.Select(i => $"pool{i}")
|
||||
.Aggregate((a,b)=> a + ", " + b);
|
||||
var countSource = "pool0.Count";
|
||||
#>
|
||||
public QueryEnumerable<<#= generics #>> Query<<#= generics #>>()
|
||||
<#= restrictions #>
|
||||
{
|
||||
if (!(<#= tryGetPools #>))
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
return new QueryEnumerable<<#= generics #>>(
|
||||
this,
|
||||
<#= poolParams #>,
|
||||
<#= countSource #>);
|
||||
}
|
||||
|
||||
public QueryEnumerable<<#= generics #>> QueryFilter<<#= generics #>>(ref readonly QueryFilter filter)
|
||||
<#= restrictions #>
|
||||
{
|
||||
if (!(<#= tryGetPools #>))
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
return new QueryEnumerable<<#= generics #>>(
|
||||
this,
|
||||
<#= poolParams #>,
|
||||
<#= countSource #>,
|
||||
in filter);
|
||||
}
|
||||
|
||||
<# } #>
|
||||
}
|
||||
1771
Ghost.Entities/Templates/EntityQuery.ComponentIterator.gen.cs
Normal file
1771
Ghost.Entities/Templates/EntityQuery.ComponentIterator.gen.cs
Normal file
File diff suppressed because it is too large
Load Diff
228
Ghost.Entities/Templates/EntityQuery.ComponentIterator.tt
Normal file
228
Ghost.Entities/Templates/EntityQuery.ComponentIterator.tt
Normal file
@@ -0,0 +1,228 @@
|
||||
<#@ template language="C#" #>
|
||||
<#@ output extension="gen.cs" #>
|
||||
<#@ assembly name="System.Core" #>
|
||||
<#@ import namespace="System.Linq" #>
|
||||
<#@ import namespace="System.Text" #>
|
||||
<#@ include file="Helpers.ttinclude" #>
|
||||
using Ghost.Core;
|
||||
using Misaki.HighPerformance.LowLevel;
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ghost.Entities;
|
||||
|
||||
public unsafe partial struct EntityQuery
|
||||
{
|
||||
<# for (var i = 1; i <= Amount; i++)
|
||||
{
|
||||
var generics = AppendParameters(i, "T{0}");
|
||||
var compGenerics = AppendParameters(i, "ref T{0} component{0}");
|
||||
var deconstrictOutPrams = AppendParameters(i, "out Ref<T{0}> component{0}");
|
||||
var restrictions = AppendGenericRestrictionsMultiline(i, "unmanaged, IComponent", 2);
|
||||
#>
|
||||
public readonly ref struct ComponentIterator<<#= generics#>>
|
||||
<#= restrictions #>
|
||||
{
|
||||
<# if (i > 1) { #>
|
||||
public ref struct QueryItem
|
||||
{
|
||||
<# for (var j = 0; j < i; j++) { #>
|
||||
public ref T<#= j #> component<#= j #>;
|
||||
<# } #>
|
||||
internal QueryItem(<#= compGenerics #>)
|
||||
{
|
||||
<# for (var j = 0; j < i; j++) { #>
|
||||
this.component<#= j #> = ref component<#= j #>;
|
||||
<# } #>
|
||||
}
|
||||
|
||||
public void Deconstruct(<#= deconstrictOutPrams #>)
|
||||
{
|
||||
<# for (var j = 0; j < i; j++) { #>
|
||||
component<#= j #> = new Ref<T<#= j #>>(ref this.component<#= j #>);
|
||||
<# } #>
|
||||
}
|
||||
}
|
||||
|
||||
<# } #>
|
||||
public ref struct Enumerator : IDisposable
|
||||
{
|
||||
private fixed int _compTypeIDs[<#= i #>];
|
||||
private fixed int _offsets[<#= i #>];
|
||||
private fixed long _compBasePtrs[<#= i #>];
|
||||
|
||||
private readonly ReadOnlyUnsafeCollection<Identifier<Archetype>> _matchingArchetypes;
|
||||
private readonly EntityQueryMask _mask;
|
||||
private readonly World _world;
|
||||
|
||||
private readonly Stack.Scope _scope;
|
||||
private UnsafeList<int> _changedComponentIDs;
|
||||
|
||||
private ref Archetype _currentArchetype;
|
||||
private ref Chunk _currentChunk;
|
||||
private byte* _chunkBasePtr;
|
||||
|
||||
private int _currentChunkEntityCount;
|
||||
private int _currentArchetypeIndex;
|
||||
private int _currentChunkIndex;
|
||||
private int _currentEntityIndex;
|
||||
|
||||
internal Enumerator(ReadOnlyUnsafeCollection<Identifier<Archetype>> matchingArchetypes, EntityQueryMask mask, World world)
|
||||
{
|
||||
<# for (var j = 0; j < i; j++) { #>
|
||||
_compTypeIDs[<#= j #>] = ComponentTypeID<T<#= j #>>.value;
|
||||
_offsets[<#= j #>] = 0;
|
||||
_compBasePtrs[<#= j #>] = 0;
|
||||
|
||||
<# } #>
|
||||
_matchingArchetypes = matchingArchetypes;
|
||||
_mask = mask;
|
||||
_world = world;
|
||||
|
||||
_scope = AllocationManager.CreateStackScope();
|
||||
_changedComponentIDs = new UnsafeList<int>(<#= i #>, _scope.AllocationHandle);
|
||||
|
||||
var it = _mask.writeAccess.GetIterator();
|
||||
while (it.Next(out var id))
|
||||
{
|
||||
for (var i = 0; i < <#= i #>; i++)
|
||||
{
|
||||
if (id == _compTypeIDs[i])
|
||||
{
|
||||
_changedComponentIDs.Add(id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reset();
|
||||
}
|
||||
|
||||
<# if (i > 1) { #>
|
||||
public QueryItem Current => new(
|
||||
<# for (var j = 0; j < i; j++) { #>
|
||||
ref *(T<#= j #>*)(_compBasePtrs[<#= j #>] + _currentEntityIndex * sizeof(T<#= j #>))<#= j < i - 1 ? "," : "" #>
|
||||
<# } #>
|
||||
);
|
||||
<# } else { #>
|
||||
public ref T0 Current => ref *(T0*)(_compBasePtrs[0] + _currentEntityIndex * sizeof(T0));
|
||||
<# } #>
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void SetChunk(int chunkIndex)
|
||||
{
|
||||
_currentChunk = ref _currentArchetype.GetChunkReference(chunkIndex);
|
||||
_chunkBasePtr = _currentChunk.GetUnsafePtr();
|
||||
_currentChunkEntityCount = _currentChunk._count;
|
||||
|
||||
for (var index = 0; index < <#= i #>; index++)
|
||||
{
|
||||
var layout = _currentArchetype.GetLayout(_compTypeIDs[index])
|
||||
.GetValueOrThrow();
|
||||
_offsets[index] = layout.offset;
|
||||
_compBasePtrs[index] = (long)(_chunkBasePtr + _offsets[index]);
|
||||
}
|
||||
|
||||
for (var i = 0; i < _changedComponentIDs.Count; i++)
|
||||
{
|
||||
_currentArchetype.MarkChanged(_currentChunkIndex, _changedComponentIDs[i], _world.Version);
|
||||
}
|
||||
}
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
_currentEntityIndex++;
|
||||
if (_currentEntityIndex < _currentChunk._count)
|
||||
{
|
||||
var pChunkData = _currentChunk.GetUnsafePtr();
|
||||
if (IsEntityValid(pChunkData, _currentEntityIndex, in _currentArchetype, in _mask))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
_currentChunkIndex++;
|
||||
if (!Unsafe.IsNullRef(ref _currentArchetype) && _currentChunkIndex < _currentArchetype.ChunkCount)
|
||||
{
|
||||
SetChunk(_currentChunkIndex);
|
||||
_currentEntityIndex = -1; // Reset for new chunk
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
_currentArchetypeIndex++;
|
||||
if (_currentArchetypeIndex < _matchingArchetypes.Count)
|
||||
{
|
||||
_currentArchetype = ref _world.GetArchetypeReference(_matchingArchetypes[_currentArchetypeIndex]);
|
||||
|
||||
_currentChunkIndex = 0;
|
||||
if (_currentArchetype.ChunkCount > 0)
|
||||
{
|
||||
SetChunk(0);
|
||||
_currentEntityIndex = -1;
|
||||
continue;
|
||||
}
|
||||
|
||||
// If archetype has no chunks, loop will try next archetype
|
||||
}
|
||||
else
|
||||
{
|
||||
return false; // End of all data
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_currentArchetype = ref Unsafe.NullRef<Archetype>();
|
||||
_currentChunk = ref Unsafe.NullRef<Chunk>();
|
||||
_currentArchetypeIndex = 0;
|
||||
_currentChunkIndex = 0;
|
||||
_currentEntityIndex = -1;
|
||||
|
||||
if (_matchingArchetypes.Count > 0)
|
||||
{
|
||||
_currentArchetype = ref _world.GetArchetypeReference(_matchingArchetypes[0]);
|
||||
if (_currentArchetype.ChunkCount > 0)
|
||||
{
|
||||
SetChunk(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public readonly void Dispose()
|
||||
{
|
||||
_scope.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private readonly ReadOnlyUnsafeCollection<Identifier<Archetype>> _matchingArchetypes;
|
||||
private readonly EntityQueryMask _mask;
|
||||
private readonly World _world;
|
||||
|
||||
internal ComponentIterator(ReadOnlyUnsafeCollection<Identifier<Archetype>> matchingArchetypes, EntityQueryMask mask, World world)
|
||||
{
|
||||
_matchingArchetypes = matchingArchetypes;
|
||||
_mask = mask;
|
||||
_world = world;
|
||||
}
|
||||
|
||||
public Enumerator GetEnumerator()
|
||||
{
|
||||
return new Enumerator(_matchingArchetypes, _mask, _world);
|
||||
}
|
||||
}
|
||||
|
||||
public readonly ComponentIterator<<#= generics#>> GetComponentIterator<<#= generics#>>()
|
||||
<#= restrictions #>
|
||||
{
|
||||
return new ComponentIterator<<#= generics#>>(_matchingArchetypes.AsReadOnly(), _mask, World.GetWorld(_worldID).GetValueOrThrow());
|
||||
}
|
||||
|
||||
<# } #>
|
||||
}
|
||||
1843
Ghost.Entities/Templates/EntityQuery.EntityComponentIterator.gen.cs
Normal file
1843
Ghost.Entities/Templates/EntityQuery.EntityComponentIterator.gen.cs
Normal file
File diff suppressed because it is too large
Load Diff
229
Ghost.Entities/Templates/EntityQuery.EntityComponentIterator.tt
Normal file
229
Ghost.Entities/Templates/EntityQuery.EntityComponentIterator.tt
Normal file
@@ -0,0 +1,229 @@
|
||||
<#@ template language="C#" #>
|
||||
<#@ output extension="gen.cs" #>
|
||||
<#@ assembly name="System.Core" #>
|
||||
<#@ import namespace="System.Linq" #>
|
||||
<#@ import namespace="System.Text" #>
|
||||
<#@ include file="Helpers.ttinclude" #>
|
||||
using Ghost.Core;
|
||||
using Misaki.HighPerformance.LowLevel;
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ghost.Entities;
|
||||
|
||||
public unsafe partial struct EntityQuery
|
||||
{
|
||||
<# for (var i = 1; i <= Amount; i++)
|
||||
{
|
||||
var generics = AppendParameters(i, "T{0}");
|
||||
var compGenerics = AppendParameters(i, "ref T{0} component{0}");
|
||||
var deconstrictOutPrams = AppendParameters(i, "out Ref<T{0}> component{0}");
|
||||
var restrictions = AppendGenericRestrictionsMultiline(i, "unmanaged, IComponent", 2);
|
||||
#>
|
||||
public readonly ref struct EntityComponentIterator<<#= generics #>>
|
||||
<#= restrictions #>
|
||||
{
|
||||
public ref struct QueryItem
|
||||
{
|
||||
public Entity entity;
|
||||
|
||||
<# for (var j = 0; j < i; j++) { #>
|
||||
public ref T<#= j #> component<#= j #>;
|
||||
<# } #>
|
||||
internal QueryItem(Entity entity, <#= compGenerics #>)
|
||||
{
|
||||
this.entity = entity;
|
||||
|
||||
<# for (var j = 0; j < i; j++) { #>
|
||||
this.component<#= j #> = ref component<#= j #>;
|
||||
<# } #>
|
||||
}
|
||||
|
||||
public void Deconstruct(out Entity entity, <#= deconstrictOutPrams #>)
|
||||
{
|
||||
entity = this.entity;
|
||||
|
||||
<# for (var j = 0; j < i; j++) { #>
|
||||
component<#= j #> = new Ref<T<#= j #>>(ref this.component<#= j #>);
|
||||
<# } #>
|
||||
}
|
||||
}
|
||||
|
||||
public ref struct Enumerator : IDisposable
|
||||
{
|
||||
private fixed int _compTypeIDs[<#= i #>];
|
||||
private fixed int _offsets[<#= i #>];
|
||||
private fixed long _compBasePtrs[<#= i #>];
|
||||
|
||||
private readonly ReadOnlyUnsafeCollection<Identifier<Archetype>> _matchingArchetypes;
|
||||
private readonly EntityQueryMask _mask;
|
||||
private readonly World _world;
|
||||
|
||||
private readonly Stack.Scope _scope;
|
||||
private UnsafeList<int> _changedComponentIDs;
|
||||
|
||||
private ref Archetype _currentArchetype;
|
||||
private ref Chunk _currentChunk;
|
||||
private byte* _chunkBasePtr;
|
||||
|
||||
private int _currentChunkEntityCount;
|
||||
private int _currentArchetypeIndex;
|
||||
private int _currentChunkIndex;
|
||||
private int _currentEntityIndex;
|
||||
|
||||
internal Enumerator(ReadOnlyUnsafeCollection<Identifier<Archetype>> matchingArchetypes, EntityQueryMask mask, World world)
|
||||
{
|
||||
<# for (var j = 0; j < i; j++) { #>
|
||||
_compTypeIDs[<#= j #>] = ComponentTypeID<T<#= j #>>.value;
|
||||
_offsets[<#= j #>] = 0;
|
||||
_compBasePtrs[<#= j #>] = 0;
|
||||
|
||||
<# } #>
|
||||
_matchingArchetypes = matchingArchetypes;
|
||||
_mask = mask;
|
||||
_world = world;
|
||||
|
||||
_scope = AllocationManager.CreateStackScope();
|
||||
_changedComponentIDs = new UnsafeList<int>(<#= i #>, _scope.AllocationHandle);
|
||||
|
||||
var it = _mask.writeAccess.GetIterator();
|
||||
while (it.Next(out var id))
|
||||
{
|
||||
for (var i = 0; i < <#= i #>; i++)
|
||||
{
|
||||
if (id == _compTypeIDs[i])
|
||||
{
|
||||
_changedComponentIDs.Add(id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reset();
|
||||
}
|
||||
|
||||
public QueryItem Current => new(
|
||||
*(Entity*)(_chunkBasePtr + _currentArchetype.EntityIDsOffset + _currentEntityIndex * sizeof(Entity)),
|
||||
<# for (var j = 0; j < i; j++) { #>
|
||||
ref *(T<#= j #>*)(_compBasePtrs[<#= j #>] + _currentEntityIndex * sizeof(T<#= j #>))<#= j < i - 1 ? "," : "" #>
|
||||
<# } #>
|
||||
);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void SetChunk(int chunkIndex)
|
||||
{
|
||||
_currentChunk = ref _currentArchetype.GetChunkReference(chunkIndex);
|
||||
_chunkBasePtr = _currentChunk.GetUnsafePtr();
|
||||
_currentChunkEntityCount = _currentChunk._count;
|
||||
|
||||
for (var index = 0; index < <#= i #>; index++)
|
||||
{
|
||||
var layout = _currentArchetype.GetLayout(_compTypeIDs[index])
|
||||
.GetValueOrThrow();
|
||||
_offsets[index] = layout.offset;
|
||||
_compBasePtrs[index] = (long)(_chunkBasePtr + _offsets[index]);
|
||||
}
|
||||
|
||||
for (var i = 0; i < _changedComponentIDs.Count; i++)
|
||||
{
|
||||
_currentArchetype.MarkChanged(_currentChunkIndex, _changedComponentIDs[i], _world.Version);
|
||||
}
|
||||
}
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
_currentEntityIndex++;
|
||||
if (_currentEntityIndex < _currentChunk._count)
|
||||
{
|
||||
var pChunkData = _currentChunk.GetUnsafePtr();
|
||||
if (IsEntityValid(pChunkData, _currentEntityIndex, in _currentArchetype, in _mask))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
_currentChunkIndex++;
|
||||
if (!Unsafe.IsNullRef(ref _currentArchetype) && _currentChunkIndex < _currentArchetype.ChunkCount)
|
||||
{
|
||||
SetChunk(_currentChunkIndex);
|
||||
_currentEntityIndex = -1; // Reset for new chunk
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
_currentArchetypeIndex++;
|
||||
if (_currentArchetypeIndex < _matchingArchetypes.Count)
|
||||
{
|
||||
_currentArchetype = ref _world.GetArchetypeReference(_matchingArchetypes[_currentArchetypeIndex]);
|
||||
|
||||
_currentChunkIndex = 0;
|
||||
if (_currentArchetype.ChunkCount > 0)
|
||||
{
|
||||
SetChunk(0);
|
||||
_currentEntityIndex = -1;
|
||||
continue;
|
||||
}
|
||||
|
||||
// If archetype has no chunks, loop will try next archetype
|
||||
}
|
||||
else
|
||||
{
|
||||
return false; // End of all data
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_currentArchetype = ref Unsafe.NullRef<Archetype>();
|
||||
_currentChunk = ref Unsafe.NullRef<Chunk>();
|
||||
_currentArchetypeIndex = 0;
|
||||
_currentChunkIndex = 0;
|
||||
_currentEntityIndex = -1;
|
||||
|
||||
if (_matchingArchetypes.Count > 0)
|
||||
{
|
||||
_currentArchetype = ref _world.GetArchetypeReference(_matchingArchetypes[0]);
|
||||
if (_currentArchetype.ChunkCount > 0)
|
||||
{
|
||||
SetChunk(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public readonly void Dispose()
|
||||
{
|
||||
_scope.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private readonly ReadOnlyUnsafeCollection<Identifier<Archetype>> _matchingArchetypes;
|
||||
private readonly EntityQueryMask _mask;
|
||||
private readonly World _world;
|
||||
|
||||
internal EntityComponentIterator(ReadOnlyUnsafeCollection<Identifier<Archetype>> matchingArchetypes, EntityQueryMask mask, World world)
|
||||
{
|
||||
_matchingArchetypes = matchingArchetypes;
|
||||
_mask = mask;
|
||||
_world = world;
|
||||
}
|
||||
|
||||
public Enumerator GetEnumerator()
|
||||
{
|
||||
return new Enumerator(_matchingArchetypes, _mask, _world);
|
||||
}
|
||||
}
|
||||
|
||||
public readonly EntityComponentIterator<<#= generics#>> GetEntityComponentIterator<<#= generics#>>()
|
||||
<#= restrictions #>
|
||||
{
|
||||
return new EntityComponentIterator<<#= generics#>>(_matchingArchetypes.AsReadOnly(), _mask, World.GetWorld(_worldID).GetValueOrThrow());
|
||||
}
|
||||
|
||||
<# } #>
|
||||
}
|
||||
1581
Ghost.Entities/Templates/EntityQuery.ForEach.gen.cs
Normal file
1581
Ghost.Entities/Templates/EntityQuery.ForEach.gen.cs
Normal file
File diff suppressed because it is too large
Load Diff
119
Ghost.Entities/Templates/EntityQuery.ForEach.tt
Normal file
119
Ghost.Entities/Templates/EntityQuery.ForEach.tt
Normal file
@@ -0,0 +1,119 @@
|
||||
<#@ template language="C#" #>
|
||||
<#@ output extension="gen.cs" #>
|
||||
<#@ assembly name="System.Core" #>
|
||||
<#@ import namespace="System.Linq" #>
|
||||
<#@ import namespace="System.Text" #>
|
||||
<#@ include file="Helpers.ttinclude" #>
|
||||
namespace Ghost.Entities;
|
||||
|
||||
public unsafe partial struct EntityQuery
|
||||
{
|
||||
<# for (var f = 0; f < 2; f++)
|
||||
{
|
||||
var isForEachWithEntity = f != 0;
|
||||
#>
|
||||
<# for (var i = 1; i <= Amount; i++)
|
||||
{
|
||||
var generics = AppendParameters(i, "T{0}");
|
||||
var restrictions = AppendGenericRestrictionsMultiline(i, "unmanaged, IComponent", 2);
|
||||
|
||||
var delegateTupe = isForEachWithEntity ? "ForEachWithEntity" : "ForEach";
|
||||
#>
|
||||
public readonly void ForEach<<#= generics #>>(<#= delegateTupe #><<#= generics #>> action)
|
||||
<#= restrictions #>
|
||||
{
|
||||
var world = World.GetWorldUncheck(_worldID);
|
||||
var globalVersion = world.Version;
|
||||
|
||||
<# for (var localIndex = 0; localIndex < i; localIndex++) { #>
|
||||
var comp<#= localIndex #>TypeID = ComponentTypeID<T<#= localIndex #>>.value;
|
||||
<# } #>
|
||||
|
||||
var compTypeIDs = stackalloc int[]
|
||||
{
|
||||
<# for (var localIndex = 0; localIndex < i; localIndex++) { #>
|
||||
comp<#= localIndex #>TypeID.value,
|
||||
<# } #>
|
||||
};
|
||||
|
||||
var changedCompIDs = stackalloc int[<#= i #>];
|
||||
var offsets = stackalloc int[<#= i #>];
|
||||
var basePtrs = stackalloc byte*[<#= i #>];
|
||||
|
||||
var changedCompCount = 0;
|
||||
|
||||
var it = _mask.writeAccess.GetIterator();
|
||||
while (it.Next(out var id))
|
||||
{
|
||||
for (var i =0; i < <#= i #>; i++)
|
||||
{
|
||||
if (id == compTypeIDs[i])
|
||||
{
|
||||
changedCompIDs[changedCompCount] = id;
|
||||
changedCompCount++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < _matchingArchetypes.Count; i++)
|
||||
{
|
||||
ref var archetype = ref world.GetArchetypeReference(_matchingArchetypes[i]);
|
||||
var hasAllComponents = true;
|
||||
for (var index = 0; index < <#= i #>; index++)
|
||||
{
|
||||
var layoutResult = archetype.GetLayout(compTypeIDs[index]);
|
||||
if (!layoutResult)
|
||||
{
|
||||
hasAllComponents = false;
|
||||
break;
|
||||
}
|
||||
|
||||
offsets[index] = layoutResult.Value.offset;
|
||||
}
|
||||
|
||||
if (!hasAllComponents)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
for (var chunkIndex = 0; chunkIndex < archetype.ChunkCount; chunkIndex++)
|
||||
{
|
||||
ref var chunk = ref archetype.GetChunkReference(chunkIndex);
|
||||
var pChunkData = chunk.GetUnsafePtr();
|
||||
|
||||
for (var j = 0; j < changedCompCount; j++)
|
||||
{
|
||||
archetype.MarkChanged(chunkIndex, changedCompIDs[j], globalVersion);
|
||||
}
|
||||
|
||||
for (var index = 0; index < <#= i #>; index++)
|
||||
{
|
||||
basePtrs[index] = pChunkData + offsets[index];
|
||||
}
|
||||
|
||||
for (var entityIndex = 0; entityIndex < chunk._count; entityIndex++)
|
||||
{
|
||||
if (!IsEntityValid(pChunkData, entityIndex, in archetype, in _mask))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
<# for (var localIndex = 0; localIndex < i; localIndex++) { #>
|
||||
var pComp<#= localIndex #> = (T<#= localIndex #>*)(basePtrs[<#= localIndex #>] + (sizeof(T<#= localIndex #>) * entityIndex));
|
||||
<# } #>
|
||||
|
||||
<# if (isForEachWithEntity) { #>
|
||||
var pEntity = (Entity*)(pChunkData + archetype.EntityIDsOffset + (sizeof(Entity) * entityIndex));
|
||||
action(*pEntity, <#= AppendParameters(i, "ref *pComp{0}") #>);
|
||||
<# } else { #>
|
||||
action(<#= AppendParameters(i, "ref *pComp{0}") #>);
|
||||
<# } #>
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
<# } #>
|
||||
<# } #>
|
||||
}
|
||||
2817
Ghost.Entities/Templates/EntityQuery.JobEntity.gen.cs
Normal file
2817
Ghost.Entities/Templates/EntityQuery.JobEntity.gen.cs
Normal file
File diff suppressed because it is too large
Load Diff
234
Ghost.Entities/Templates/EntityQuery.JobEntity.tt
Normal file
234
Ghost.Entities/Templates/EntityQuery.JobEntity.tt
Normal file
@@ -0,0 +1,234 @@
|
||||
<#@ template language="C#" #>
|
||||
<#@ output extension="gen.cs" #>
|
||||
<#@ assembly name="System.Core" #>
|
||||
<#@ import namespace="System.Linq" #>
|
||||
<#@ import namespace="System.Text" #>
|
||||
<#@ include file="Helpers.ttinclude" #>
|
||||
using Ghost.Core;
|
||||
using Misaki.HighPerformance.Jobs;
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
|
||||
namespace Ghost.Entities;
|
||||
|
||||
<# for (var i = 1; i <= Amount; i++)
|
||||
{
|
||||
var generics = AppendGenerics(i);
|
||||
var restrictions = AppendGenericRestrictionsMultiline(i, "unmanaged, IComponent", 1);
|
||||
#>
|
||||
public interface IJobEntity<<#= generics #>>
|
||||
<#= restrictions #>
|
||||
{
|
||||
void Execute(Entity entity, <#= AppendParameters(i, "ref T{0} component{0}") #>, int threadIndex);
|
||||
}
|
||||
|
||||
internal unsafe struct JobEntityBatch<TJob, <#= generics #>> : IJobParallelFor
|
||||
where TJob : unmanaged, IJobEntity<<#= generics #>>
|
||||
<#= restrictions #>
|
||||
{
|
||||
public fixed int componentIDs[<#= i #>];
|
||||
public fixed bool componentRW[<#= i #>];
|
||||
|
||||
public TJob userJob;
|
||||
|
||||
public UnsafeList<IntPtr> chunks;
|
||||
public UnsafeList<IntPtr> chunkVersions;
|
||||
public UnsafeList<int> chunkCount;
|
||||
public UnsafeList<int> entityOffset;
|
||||
|
||||
<# for (var j = 0; j < i; j++){ #>
|
||||
public UnsafeList<int> offsets<#= j #>;
|
||||
public UnsafeList<int> bitsOffsets<#= j #>;
|
||||
public UnsafeList<int> versionindices<#= j #>;
|
||||
|
||||
<# } #>
|
||||
public int version;
|
||||
|
||||
public void Execute(int loopIndex, int threadIndex)
|
||||
{
|
||||
// 1. Get the specific pChunk for this thread
|
||||
var pChunk = (byte*)chunks[loopIndex];
|
||||
var pVersions = (int*)chunkVersions[loopIndex];
|
||||
var count = chunkCount[loopIndex];
|
||||
|
||||
<# for (var j = 0; j < i; j++){ #>
|
||||
var off<#= j #> = offsets<#= j #>[loopIndex];
|
||||
var enableOff<#= j #> = bitsOffsets<#= j #>[loopIndex];
|
||||
var versionIndex<#= j #> = versionindices<#= j #>[loopIndex];
|
||||
|
||||
<# } #>
|
||||
var pEntity = (Entity*)(pChunk + entityOffset[loopIndex]);
|
||||
<# for (var j = 0; j < i; j++){ #>
|
||||
var ptr<#= j #> = (<#= "T" + j #>*)(pChunk + off<#= j #>);
|
||||
<# } #>
|
||||
|
||||
// 2. Update versions for RW components
|
||||
<# for (var j = 0; j < i; j++){ #>
|
||||
if (componentRW[<#= j #>])
|
||||
{
|
||||
pVersions[versionIndex<#= j #>] = version;
|
||||
}
|
||||
|
||||
<# } #>
|
||||
// 3. Iterate all entities in this chunk
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
<# for (var j = 0; j < i; j++){ #>
|
||||
if (enableOff<#= j #> != -1 && !EntityQuery.CheckBit(pChunk + enableOff<#= j #>, i))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
<# } #>
|
||||
userJob.Execute(pEntity[i], <#= AppendParameters(i, "ref ptr{0}[i]") #>, threadIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
<# } #>
|
||||
public unsafe partial struct EntityQuery
|
||||
{
|
||||
<# for (var i = 1; i <= Amount; i++)
|
||||
{
|
||||
var generics = AppendGenerics(i);
|
||||
var restrictions = AppendGenericRestrictionsMultiline(i, "unmanaged, IComponent", 2);
|
||||
#>
|
||||
private struct DisposeJobEntity<#= i #> : IJob
|
||||
{
|
||||
public UnsafeList<IntPtr> chunks;
|
||||
public UnsafeList<IntPtr> chunkVersions;
|
||||
public UnsafeList<int> chunkEntityCounts;
|
||||
public UnsafeList<int> entityOffsets;
|
||||
|
||||
<# for (var j = 0; j < i; j++){ #>
|
||||
public UnsafeList<int> offsets<#= j #>;
|
||||
public UnsafeList<int> bitsOffsets<#= j #>;
|
||||
public UnsafeList<int> versionindices<#= j #>;
|
||||
|
||||
<# } #>
|
||||
public void Execute(int threadIndex)
|
||||
{
|
||||
chunks.Dispose();
|
||||
chunkVersions.Dispose();
|
||||
chunkEntityCounts.Dispose();
|
||||
entityOffsets.Dispose();
|
||||
|
||||
<# for (var j = 0; j < i; j++){ #>
|
||||
offsets<#= j #>.Dispose();
|
||||
bitsOffsets<#= j #>.Dispose();
|
||||
versionindices<#= j #>.Dispose();
|
||||
|
||||
<# } #>
|
||||
}
|
||||
}
|
||||
|
||||
public JobHandle ScheduleEntityParallel<TJob, <#= generics #>>(TJob jobData, int batchSize, JobHandle dependency)
|
||||
where TJob : unmanaged, IJobEntity<<#= generics #>>
|
||||
<#= restrictions #>
|
||||
{
|
||||
var world = World.GetWorld(_worldID).GetValueOrThrow();
|
||||
|
||||
// 1. Flatten the World
|
||||
var chunks = new UnsafeList<IntPtr>(128, JobScheduler.TempAllocatorHandle);
|
||||
var chunkVersions = new UnsafeList<IntPtr>(128, JobScheduler.TempAllocatorHandle);
|
||||
var chunkEntityCounts = new UnsafeList<int>(128, JobScheduler.TempAllocatorHandle);
|
||||
var entityOffsets = new UnsafeList<int>(128, JobScheduler.TempAllocatorHandle);
|
||||
|
||||
<# for (var j = 0; j < i; j++){ #>
|
||||
var offsets<#= j #> = new UnsafeList<int>(128, JobScheduler.TempAllocatorHandle);
|
||||
var bitsOffsets<#= j #> = new UnsafeList<int>(128, JobScheduler.TempAllocatorHandle);
|
||||
var versionIndices<#= j #> = new UnsafeList<int>(128, JobScheduler.TempAllocatorHandle);
|
||||
|
||||
<# } #>
|
||||
// Iterate the Query's matching archetypes
|
||||
foreach (var archID in _matchingArchetypes)
|
||||
{
|
||||
ref var arch = ref world.GetArchetypeReference(archID);
|
||||
|
||||
if (arch.ChunkCount == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get offsets ONCE per archetype
|
||||
<# for (var j = 0; j < i; j++){ #>
|
||||
var layout<#= j #> = arch.GetLayout(ComponentTypeID<T<#= j #>>.value)
|
||||
.GetValueOrThrow();
|
||||
<# } #>
|
||||
|
||||
// Add all chunks from this archetype
|
||||
for (var i = 0; i < arch.ChunkCount; i++)
|
||||
{
|
||||
ref var chunkRef = ref arch.GetChunkReference(i);
|
||||
|
||||
chunks.Add((IntPtr)chunkRef.GetUnsafePtr());
|
||||
chunkVersions.Add((IntPtr)chunkRef.GetVersionUnsafePtr());
|
||||
chunkEntityCounts.Add(chunkRef._count);
|
||||
entityOffsets.Add(arch.EntityIDsOffset);
|
||||
|
||||
<# for (var j = 0; j < i; j++){ #>
|
||||
offsets<#= j #>.Add(layout<#= j #>.offset);
|
||||
bitsOffsets<#= j #>.Add(layout<#= j #>.enableBitsOffset);
|
||||
versionIndices<#= j #>.Add(layout<#= j #>.versionIndex);
|
||||
|
||||
<# } #>
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Create the Runner
|
||||
var runner = new JobEntityBatch<TJob, <#= generics #>>
|
||||
{
|
||||
userJob = jobData,
|
||||
chunks = chunks,
|
||||
chunkVersions = chunkVersions,
|
||||
chunkCount = chunkEntityCounts,
|
||||
entityOffset = entityOffsets,
|
||||
|
||||
<# for (var j = 0; j < i; j++){ #>
|
||||
offsets<#= j #> = offsets<#= j #>,
|
||||
bitsOffsets<#= j #> = bitsOffsets<#= j #>,
|
||||
versionindices<#= j #> = versionIndices<#= j #>,
|
||||
|
||||
<# } #>
|
||||
version = world.Version,
|
||||
};
|
||||
|
||||
runner.componentIDs[0] = ComponentTypeID<T0>.value;
|
||||
|
||||
var it = _mask.writeAccess.GetIterator();
|
||||
while (it.Next(out var id))
|
||||
{
|
||||
for (var i =0; i < 1; i++)
|
||||
{
|
||||
if (id == runner.componentIDs[i])
|
||||
{
|
||||
runner.componentRW[i] = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var jobHandle = world.JobScheduler.ScheduleParallel(ref runner, chunks.Count, batchSize, dependency);
|
||||
|
||||
// 3. Dispose the temp lists
|
||||
var disposeJob = new DisposeJobEntity<#= i #>
|
||||
{
|
||||
chunks = chunks,
|
||||
chunkVersions = chunkVersions,
|
||||
chunkEntityCounts = chunkEntityCounts,
|
||||
entityOffsets = entityOffsets,
|
||||
|
||||
<# for (var j = 0; j < i; j++){ #>
|
||||
offsets<#= j #> = offsets<#= j #>,
|
||||
bitsOffsets<#= j #> = bitsOffsets<#= j #>,
|
||||
versionindices<#= j #> = versionIndices<#= j #>,
|
||||
|
||||
<# } #>
|
||||
};
|
||||
|
||||
world.JobScheduler.Schedule(ref disposeJob, jobHandle);
|
||||
|
||||
return jobHandle;
|
||||
}
|
||||
|
||||
<# } #>
|
||||
}
|
||||
35
Ghost.Entities/Templates/ForEach.gen.cs
Normal file
35
Ghost.Entities/Templates/ForEach.gen.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
namespace Ghost.Entities;
|
||||
|
||||
public delegate void ForEach<T0>(ref T0 component0)
|
||||
where T0 : unmanaged, IComponent;
|
||||
public delegate void ForEach<T0, T1>(ref T0 component0, ref T1 component1)
|
||||
where T0 : unmanaged, IComponent where T1 : unmanaged, IComponent;
|
||||
public delegate void ForEach<T0, T1, T2>(ref T0 component0, ref T1 component1, ref T2 component2)
|
||||
where T0 : unmanaged, IComponent where T1 : unmanaged, IComponent where T2 : unmanaged, IComponent;
|
||||
public delegate void ForEach<T0, T1, T2, T3>(ref T0 component0, ref T1 component1, ref T2 component2, ref T3 component3)
|
||||
where T0 : unmanaged, IComponent where T1 : unmanaged, IComponent where T2 : unmanaged, IComponent where T3 : unmanaged, IComponent;
|
||||
public delegate void ForEach<T0, T1, T2, T3, T4>(ref T0 component0, ref T1 component1, ref T2 component2, ref T3 component3, ref T4 component4)
|
||||
where T0 : unmanaged, IComponent where T1 : unmanaged, IComponent where T2 : unmanaged, IComponent where T3 : unmanaged, IComponent where T4 : unmanaged, IComponent;
|
||||
public delegate void ForEach<T0, T1, T2, T3, T4, T5>(ref T0 component0, ref T1 component1, ref T2 component2, ref T3 component3, ref T4 component4, ref T5 component5)
|
||||
where T0 : unmanaged, IComponent where T1 : unmanaged, IComponent where T2 : unmanaged, IComponent where T3 : unmanaged, IComponent where T4 : unmanaged, IComponent where T5 : unmanaged, IComponent;
|
||||
public delegate void ForEach<T0, T1, T2, T3, T4, T5, T6>(ref T0 component0, ref T1 component1, ref T2 component2, ref T3 component3, ref T4 component4, ref T5 component5, ref T6 component6)
|
||||
where T0 : unmanaged, IComponent where T1 : unmanaged, IComponent where T2 : unmanaged, IComponent where T3 : unmanaged, IComponent where T4 : unmanaged, IComponent where T5 : unmanaged, IComponent where T6 : unmanaged, IComponent;
|
||||
public delegate void ForEach<T0, T1, T2, T3, T4, T5, T6, T7>(ref T0 component0, ref T1 component1, ref T2 component2, ref T3 component3, ref T4 component4, ref T5 component5, ref T6 component6, ref T7 component7)
|
||||
where T0 : unmanaged, IComponent where T1 : unmanaged, IComponent where T2 : unmanaged, IComponent where T3 : unmanaged, IComponent where T4 : unmanaged, IComponent where T5 : unmanaged, IComponent where T6 : unmanaged, IComponent where T7 : unmanaged, IComponent;
|
||||
|
||||
public delegate void ForEachWithEntity<T0>(Entity entity, ref T0 component0)
|
||||
where T0 : unmanaged, IComponent;
|
||||
public delegate void ForEachWithEntity<T0, T1>(Entity entity, ref T0 component0, ref T1 component1)
|
||||
where T0 : unmanaged, IComponent where T1 : unmanaged, IComponent;
|
||||
public delegate void ForEachWithEntity<T0, T1, T2>(Entity entity, ref T0 component0, ref T1 component1, ref T2 component2)
|
||||
where T0 : unmanaged, IComponent where T1 : unmanaged, IComponent where T2 : unmanaged, IComponent;
|
||||
public delegate void ForEachWithEntity<T0, T1, T2, T3>(Entity entity, ref T0 component0, ref T1 component1, ref T2 component2, ref T3 component3)
|
||||
where T0 : unmanaged, IComponent where T1 : unmanaged, IComponent where T2 : unmanaged, IComponent where T3 : unmanaged, IComponent;
|
||||
public delegate void ForEachWithEntity<T0, T1, T2, T3, T4>(Entity entity, ref T0 component0, ref T1 component1, ref T2 component2, ref T3 component3, ref T4 component4)
|
||||
where T0 : unmanaged, IComponent where T1 : unmanaged, IComponent where T2 : unmanaged, IComponent where T3 : unmanaged, IComponent where T4 : unmanaged, IComponent;
|
||||
public delegate void ForEachWithEntity<T0, T1, T2, T3, T4, T5>(Entity entity, ref T0 component0, ref T1 component1, ref T2 component2, ref T3 component3, ref T4 component4, ref T5 component5)
|
||||
where T0 : unmanaged, IComponent where T1 : unmanaged, IComponent where T2 : unmanaged, IComponent where T3 : unmanaged, IComponent where T4 : unmanaged, IComponent where T5 : unmanaged, IComponent;
|
||||
public delegate void ForEachWithEntity<T0, T1, T2, T3, T4, T5, T6>(Entity entity, ref T0 component0, ref T1 component1, ref T2 component2, ref T3 component3, ref T4 component4, ref T5 component5, ref T6 component6)
|
||||
where T0 : unmanaged, IComponent where T1 : unmanaged, IComponent where T2 : unmanaged, IComponent where T3 : unmanaged, IComponent where T4 : unmanaged, IComponent where T5 : unmanaged, IComponent where T6 : unmanaged, IComponent;
|
||||
public delegate void ForEachWithEntity<T0, T1, T2, T3, T4, T5, T6, T7>(Entity entity, ref T0 component0, ref T1 component1, ref T2 component2, ref T3 component3, ref T4 component4, ref T5 component5, ref T6 component6, ref T7 component7)
|
||||
where T0 : unmanaged, IComponent where T1 : unmanaged, IComponent where T2 : unmanaged, IComponent where T3 : unmanaged, IComponent where T4 : unmanaged, IComponent where T5 : unmanaged, IComponent where T6 : unmanaged, IComponent where T7 : unmanaged, IComponent;
|
||||
27
Ghost.Entities/Templates/ForEach.tt
Normal file
27
Ghost.Entities/Templates/ForEach.tt
Normal file
@@ -0,0 +1,27 @@
|
||||
<#@ template language="C#" #>
|
||||
<#@ output extension="gen.cs" #>
|
||||
<#@ assembly name="System.Core" #>
|
||||
<#@ import namespace="System.Linq" #>
|
||||
<#@ import namespace="System.Text" #>
|
||||
<#@ include file="Helpers.ttinclude" #>
|
||||
namespace Ghost.Entities;
|
||||
|
||||
<# for (var i = 1; i <= Amount; i++)
|
||||
{
|
||||
var generics = AppendParameters(i, "T{0}");
|
||||
var compGenerics = AppendParameters(i, "ref T{0} component{0}");
|
||||
var restrictions = AppendGenericRestrictions(i, "unmanaged, IComponent");
|
||||
#>
|
||||
public delegate void ForEach<<#= generics #>>(<#= compGenerics #>)
|
||||
<#= restrictions #>;
|
||||
<# } #>
|
||||
|
||||
<# for (var i = 1; i <= Amount; i++)
|
||||
{
|
||||
var generics = AppendParameters(i, "T{0}");
|
||||
var compGenerics = AppendParameters(i, "ref T{0} component{0}");
|
||||
var restrictions = AppendGenericRestrictions(i, "unmanaged, IComponent");
|
||||
#>
|
||||
public delegate void ForEachWithEntity<<#= generics #>>(Entity entity, <#= compGenerics #>)
|
||||
<#= restrictions #>;
|
||||
<# } #>
|
||||
@@ -1,4 +1,4 @@
|
||||
<#@ import namespace="System.Text" #>
|
||||
<#@ import namespace="System.Text" #>
|
||||
<#@ import namespace="System.Collections.Generic" #>
|
||||
<#+
|
||||
|
||||
@@ -17,37 +17,29 @@
|
||||
for (var i = 0; i < amount; i++)
|
||||
{
|
||||
if (i > 0) sb.Append(", ");
|
||||
sb.Append($"{template}{i}");
|
||||
sb.Append(string.Format(template, i));
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
string AppendGenerics(int amount)
|
||||
{
|
||||
return AppendGenerics(amount, "T");
|
||||
return AppendGenerics(amount, "T{0}");
|
||||
}
|
||||
|
||||
public StringBuilder AppendGenericRefParameters(int amount)
|
||||
public StringBuilder AppendParameters(int amount, string template)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
for (var localIndex = 0; localIndex < amount; localIndex++)
|
||||
{
|
||||
sb.Append($"ref T{localIndex} t{localIndex}Component,");
|
||||
sb.Append(string.Format(template, localIndex));
|
||||
if (localIndex < amount - 1)
|
||||
{
|
||||
sb.Append(", ");
|
||||
}
|
||||
}
|
||||
|
||||
sb.Length--;
|
||||
return sb;
|
||||
}
|
||||
|
||||
public StringBuilder AppendRefParameters(int amount)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
for (var localIndex = 0; localIndex < amount; localIndex++)
|
||||
{
|
||||
sb.Append($"ref component{localIndex},");
|
||||
}
|
||||
|
||||
sb.Length--;
|
||||
return sb;
|
||||
}
|
||||
|
||||
@@ -62,6 +54,7 @@
|
||||
sb.Append(' ');
|
||||
}
|
||||
}
|
||||
|
||||
return sb;
|
||||
}
|
||||
|
||||
@@ -70,6 +63,28 @@
|
||||
return AppendGenericRestrictions(amount, "T", template);
|
||||
}
|
||||
|
||||
public StringBuilder AppendGenericRestrictionsMultiline(int amount, string Ttemplate, string template, int indentation)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
var spaces = new string(' ', indentation * 4);
|
||||
|
||||
for (var localIndex = 0; localIndex < amount; localIndex++)
|
||||
{
|
||||
sb.Append($"{spaces}where {Ttemplate}{localIndex} : {template}");
|
||||
if (localIndex < amount - 1)
|
||||
{
|
||||
sb.AppendLine();
|
||||
}
|
||||
}
|
||||
|
||||
return sb;
|
||||
}
|
||||
|
||||
public StringBuilder AppendGenericRestrictionsMultiline(int amount, string template, int indentation)
|
||||
{
|
||||
return AppendGenericRestrictionsMultiline(amount, "T", template, indentation);
|
||||
}
|
||||
|
||||
public StringBuilder TryGetComponentPools(int amount)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
@@ -81,6 +96,7 @@
|
||||
sb.Append(" && ");
|
||||
}
|
||||
}
|
||||
|
||||
return sb;
|
||||
}
|
||||
|
||||
@@ -95,6 +111,7 @@
|
||||
sb.Append(" && ");
|
||||
}
|
||||
}
|
||||
|
||||
return sb;
|
||||
}
|
||||
|
||||
@@ -109,10 +126,11 @@
|
||||
sb.Append(", ");
|
||||
}
|
||||
}
|
||||
|
||||
return sb;
|
||||
}
|
||||
|
||||
public StringBuilder GetComponentRef(int amount)
|
||||
public StringBuilder GetComponentRef(int amount)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
for (var localIndex = 0; localIndex < amount; localIndex++)
|
||||
@@ -123,6 +141,7 @@
|
||||
sb.Append(", ");
|
||||
}
|
||||
}
|
||||
|
||||
return sb;
|
||||
}
|
||||
#>
|
||||
380
Ghost.Entities/Templates/QueryBuilder.With.gen.cs
Normal file
380
Ghost.Entities/Templates/QueryBuilder.With.gen.cs
Normal file
@@ -0,0 +1,380 @@
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ghost.Entities;
|
||||
|
||||
public ref partial struct QueryBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds the specified component type(s) to the 'All' filter of the query.
|
||||
/// Targets entities that have all of the specified component types and those component(s) must be enabled.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public QueryBuilder WithAll<T0>()
|
||||
where T0 : unmanaged, IComponent
|
||||
{
|
||||
_all.Add(ComponentTypeID<T0>.value);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified component type(s) to the 'All' filter of the query and requires read-write access.
|
||||
/// Targets entities that have all of the specified component types and those component(s) must be enabled.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public QueryBuilder WithAllRW<T0>()
|
||||
where T0 : unmanaged, IComponent
|
||||
{
|
||||
_all.Add(ComponentTypeID<T0>.value);
|
||||
_rw.Add(ComponentTypeID<T0>.value);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified component type(s) to the 'Any' filter of the query.
|
||||
/// Targets entities that have at least one of the specified component types and those component(s) must be enabled.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public QueryBuilder WithAny<T0>()
|
||||
where T0 : unmanaged, IComponent
|
||||
{
|
||||
_any.Add(ComponentTypeID<T0>.value);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified component type(s) to the 'Absent' filter of the query.
|
||||
/// Targets entities that do not have any of the specified component types.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public QueryBuilder WithAbsent<T0>()
|
||||
where T0 : unmanaged, IComponent
|
||||
{
|
||||
_absent.Add(ComponentTypeID<T0>.value);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified component type(s) to the 'None' filter of the query.
|
||||
/// Targets entities that do not have any of the specified component types, or those component(s) are disabled.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public QueryBuilder WithNone<T0>()
|
||||
where T0 : unmanaged, IComponent
|
||||
{
|
||||
_none.Add(ComponentTypeID<T0>.value);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified component type(s) to the 'Disabled' filter of the query.
|
||||
/// Targets entities that have all of the specified component types and those component(s) are disabled.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public QueryBuilder WithDisabled<T0>()
|
||||
where T0 : unmanaged, IEnableableComponent
|
||||
{
|
||||
_disabled.Add(ComponentTypeID<T0>.value);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified component type(s) to the 'Present' filter of the query.
|
||||
/// Targets entities that have all of the specified component types, regardless of whether those component(s) are enabled or disabled.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public QueryBuilder WithPresent<T0>()
|
||||
where T0 : unmanaged, IComponent
|
||||
{
|
||||
_present.Add(ComponentTypeID<T0>.value);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified component type(s) to the 'Present' filter of the query and requires read-write access.
|
||||
/// Targets entities that have all of the specified component types, regardless of whether those component(s) are enabled or disabled.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public QueryBuilder WithPresentRW<T0>()
|
||||
where T0 : unmanaged, IComponent
|
||||
{
|
||||
_present.Add(ComponentTypeID<T0>.value);
|
||||
_rw.Add(ComponentTypeID<T0>.value);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified component type(s) to the 'All' filter of the query.
|
||||
/// Targets entities that have all of the specified component types and those component(s) must be enabled.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public QueryBuilder WithAll<T0, T1>()
|
||||
where T0 : unmanaged, IComponent
|
||||
where T1 : unmanaged, IComponent
|
||||
{
|
||||
_all.Add(ComponentTypeID<T0>.value);
|
||||
_all.Add(ComponentTypeID<T1>.value);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified component type(s) to the 'All' filter of the query and requires read-write access.
|
||||
/// Targets entities that have all of the specified component types and those component(s) must be enabled.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public QueryBuilder WithAllRW<T0, T1>()
|
||||
where T0 : unmanaged, IComponent
|
||||
where T1 : unmanaged, IComponent
|
||||
{
|
||||
_all.Add(ComponentTypeID<T0>.value);
|
||||
_rw.Add(ComponentTypeID<T0>.value);
|
||||
_all.Add(ComponentTypeID<T1>.value);
|
||||
_rw.Add(ComponentTypeID<T1>.value);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified component type(s) to the 'Any' filter of the query.
|
||||
/// Targets entities that have at least one of the specified component types and those component(s) must be enabled.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public QueryBuilder WithAny<T0, T1>()
|
||||
where T0 : unmanaged, IComponent
|
||||
where T1 : unmanaged, IComponent
|
||||
{
|
||||
_any.Add(ComponentTypeID<T0>.value);
|
||||
_any.Add(ComponentTypeID<T1>.value);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified component type(s) to the 'Absent' filter of the query.
|
||||
/// Targets entities that do not have any of the specified component types.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public QueryBuilder WithAbsent<T0, T1>()
|
||||
where T0 : unmanaged, IComponent
|
||||
where T1 : unmanaged, IComponent
|
||||
{
|
||||
_absent.Add(ComponentTypeID<T0>.value);
|
||||
_absent.Add(ComponentTypeID<T1>.value);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified component type(s) to the 'None' filter of the query.
|
||||
/// Targets entities that do not have any of the specified component types, or those component(s) are disabled.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public QueryBuilder WithNone<T0, T1>()
|
||||
where T0 : unmanaged, IComponent
|
||||
where T1 : unmanaged, IComponent
|
||||
{
|
||||
_none.Add(ComponentTypeID<T0>.value);
|
||||
_none.Add(ComponentTypeID<T1>.value);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified component type(s) to the 'Disabled' filter of the query.
|
||||
/// Targets entities that have all of the specified component types and those component(s) are disabled.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public QueryBuilder WithDisabled<T0, T1>()
|
||||
where T0 : unmanaged, IEnableableComponent
|
||||
where T1 : unmanaged, IEnableableComponent
|
||||
{
|
||||
_disabled.Add(ComponentTypeID<T0>.value);
|
||||
_disabled.Add(ComponentTypeID<T1>.value);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified component type(s) to the 'Present' filter of the query.
|
||||
/// Targets entities that have all of the specified component types, regardless of whether those component(s) are enabled or disabled.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public QueryBuilder WithPresent<T0, T1>()
|
||||
where T0 : unmanaged, IComponent
|
||||
where T1 : unmanaged, IComponent
|
||||
{
|
||||
_present.Add(ComponentTypeID<T0>.value);
|
||||
_present.Add(ComponentTypeID<T1>.value);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified component type(s) to the 'Present' filter of the query and requires read-write access.
|
||||
/// Targets entities that have all of the specified component types, regardless of whether those component(s) are enabled or disabled.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public QueryBuilder WithPresentRW<T0, T1>()
|
||||
where T0 : unmanaged, IComponent
|
||||
where T1 : unmanaged, IComponent
|
||||
{
|
||||
_present.Add(ComponentTypeID<T0>.value);
|
||||
_rw.Add(ComponentTypeID<T0>.value);
|
||||
_present.Add(ComponentTypeID<T1>.value);
|
||||
_rw.Add(ComponentTypeID<T1>.value);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified component type(s) to the 'All' filter of the query.
|
||||
/// Targets entities that have all of the specified component types and those component(s) must be enabled.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public QueryBuilder WithAll<T0, T1, T2>()
|
||||
where T0 : unmanaged, IComponent
|
||||
where T1 : unmanaged, IComponent
|
||||
where T2 : unmanaged, IComponent
|
||||
{
|
||||
_all.Add(ComponentTypeID<T0>.value);
|
||||
_all.Add(ComponentTypeID<T1>.value);
|
||||
_all.Add(ComponentTypeID<T2>.value);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified component type(s) to the 'All' filter of the query and requires read-write access.
|
||||
/// Targets entities that have all of the specified component types and those component(s) must be enabled.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public QueryBuilder WithAllRW<T0, T1, T2>()
|
||||
where T0 : unmanaged, IComponent
|
||||
where T1 : unmanaged, IComponent
|
||||
where T2 : unmanaged, IComponent
|
||||
{
|
||||
_all.Add(ComponentTypeID<T0>.value);
|
||||
_rw.Add(ComponentTypeID<T0>.value);
|
||||
_all.Add(ComponentTypeID<T1>.value);
|
||||
_rw.Add(ComponentTypeID<T1>.value);
|
||||
_all.Add(ComponentTypeID<T2>.value);
|
||||
_rw.Add(ComponentTypeID<T2>.value);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified component type(s) to the 'Any' filter of the query.
|
||||
/// Targets entities that have at least one of the specified component types and those component(s) must be enabled.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public QueryBuilder WithAny<T0, T1, T2>()
|
||||
where T0 : unmanaged, IComponent
|
||||
where T1 : unmanaged, IComponent
|
||||
where T2 : unmanaged, IComponent
|
||||
{
|
||||
_any.Add(ComponentTypeID<T0>.value);
|
||||
_any.Add(ComponentTypeID<T1>.value);
|
||||
_any.Add(ComponentTypeID<T2>.value);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified component type(s) to the 'Absent' filter of the query.
|
||||
/// Targets entities that do not have any of the specified component types.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public QueryBuilder WithAbsent<T0, T1, T2>()
|
||||
where T0 : unmanaged, IComponent
|
||||
where T1 : unmanaged, IComponent
|
||||
where T2 : unmanaged, IComponent
|
||||
{
|
||||
_absent.Add(ComponentTypeID<T0>.value);
|
||||
_absent.Add(ComponentTypeID<T1>.value);
|
||||
_absent.Add(ComponentTypeID<T2>.value);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified component type(s) to the 'None' filter of the query.
|
||||
/// Targets entities that do not have any of the specified component types, or those component(s) are disabled.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public QueryBuilder WithNone<T0, T1, T2>()
|
||||
where T0 : unmanaged, IComponent
|
||||
where T1 : unmanaged, IComponent
|
||||
where T2 : unmanaged, IComponent
|
||||
{
|
||||
_none.Add(ComponentTypeID<T0>.value);
|
||||
_none.Add(ComponentTypeID<T1>.value);
|
||||
_none.Add(ComponentTypeID<T2>.value);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified component type(s) to the 'Disabled' filter of the query.
|
||||
/// Targets entities that have all of the specified component types and those component(s) are disabled.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public QueryBuilder WithDisabled<T0, T1, T2>()
|
||||
where T0 : unmanaged, IEnableableComponent
|
||||
where T1 : unmanaged, IEnableableComponent
|
||||
where T2 : unmanaged, IEnableableComponent
|
||||
{
|
||||
_disabled.Add(ComponentTypeID<T0>.value);
|
||||
_disabled.Add(ComponentTypeID<T1>.value);
|
||||
_disabled.Add(ComponentTypeID<T2>.value);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified component type(s) to the 'Present' filter of the query.
|
||||
/// Targets entities that have all of the specified component types, regardless of whether those component(s) are enabled or disabled.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public QueryBuilder WithPresent<T0, T1, T2>()
|
||||
where T0 : unmanaged, IComponent
|
||||
where T1 : unmanaged, IComponent
|
||||
where T2 : unmanaged, IComponent
|
||||
{
|
||||
_present.Add(ComponentTypeID<T0>.value);
|
||||
_present.Add(ComponentTypeID<T1>.value);
|
||||
_present.Add(ComponentTypeID<T2>.value);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified component type(s) to the 'Present' filter of the query and requires read-write access.
|
||||
/// Targets entities that have all of the specified component types, regardless of whether those component(s) are enabled or disabled.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public QueryBuilder WithPresentRW<T0, T1, T2>()
|
||||
where T0 : unmanaged, IComponent
|
||||
where T1 : unmanaged, IComponent
|
||||
where T2 : unmanaged, IComponent
|
||||
{
|
||||
_present.Add(ComponentTypeID<T0>.value);
|
||||
_rw.Add(ComponentTypeID<T0>.value);
|
||||
_present.Add(ComponentTypeID<T1>.value);
|
||||
_rw.Add(ComponentTypeID<T1>.value);
|
||||
_present.Add(ComponentTypeID<T2>.value);
|
||||
_rw.Add(ComponentTypeID<T2>.value);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
142
Ghost.Entities/Templates/QueryBuilder.With.tt
Normal file
142
Ghost.Entities/Templates/QueryBuilder.With.tt
Normal file
@@ -0,0 +1,142 @@
|
||||
<#@ template language="C#" #>
|
||||
<#@ output extension="gen.cs" #>
|
||||
<#@ assembly name="System.Core" #>
|
||||
<#@ import namespace="System.Linq" #>
|
||||
<#@ import namespace="System.Text" #>
|
||||
<#@ include file="Helpers.ttinclude" #>
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ghost.Entities;
|
||||
|
||||
public ref partial struct QueryBuilder
|
||||
{
|
||||
<# for (var i = 1; i <= 3; i++)
|
||||
{
|
||||
var generics = AppendGenerics(i);
|
||||
var restrictions = AppendGenericRestrictionsMultiline(i, "unmanaged, IComponent", 2);
|
||||
var enableRestrictions = AppendGenericRestrictionsMultiline(i, "unmanaged, IEnableableComponent", 2);
|
||||
#>
|
||||
/// <summary>
|
||||
/// Adds the specified component type(s) to the 'All' filter of the query.
|
||||
/// Targets entities that have all of the specified component types and those component(s) must be enabled.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public QueryBuilder WithAll<<#= generics #>>()
|
||||
<#= restrictions #>
|
||||
{
|
||||
<# for (var j = 0; j < i; j++) { #>
|
||||
_all.Add(ComponentTypeID<T<#= j #>>.value);
|
||||
<# } #>
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified component type(s) to the 'All' filter of the query and requires read-write access.
|
||||
/// Targets entities that have all of the specified component types and those component(s) must be enabled.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public QueryBuilder WithAllRW<<#= generics #>>()
|
||||
<#= restrictions #>
|
||||
{
|
||||
<# for (var j = 0; j < i; j++) { #>
|
||||
_all.Add(ComponentTypeID<T<#= j #>>.value);
|
||||
_rw.Add(ComponentTypeID<T<#= j #>>.value);
|
||||
<# } #>
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified component type(s) to the 'Any' filter of the query.
|
||||
/// Targets entities that have at least one of the specified component types and those component(s) must be enabled.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public QueryBuilder WithAny<<#= generics #>>()
|
||||
<#= restrictions #>
|
||||
{
|
||||
<# for (var j = 0; j < i; j++) { #>
|
||||
_any.Add(ComponentTypeID<T<#= j #>>.value);
|
||||
<# } #>
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified component type(s) to the 'Absent' filter of the query.
|
||||
/// Targets entities that do not have any of the specified component types.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public QueryBuilder WithAbsent<<#= generics #>>()
|
||||
<#= restrictions #>
|
||||
{
|
||||
<# for (var j = 0; j < i; j++) { #>
|
||||
_absent.Add(ComponentTypeID<T<#= j #>>.value);
|
||||
<# } #>
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified component type(s) to the 'None' filter of the query.
|
||||
/// Targets entities that do not have any of the specified component types, or those component(s) are disabled.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public QueryBuilder WithNone<<#= generics #>>()
|
||||
<#= restrictions #>
|
||||
{
|
||||
<# for (var j = 0; j < i; j++) { #>
|
||||
_none.Add(ComponentTypeID<T<#= j #>>.value);
|
||||
<# } #>
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified component type(s) to the 'Disabled' filter of the query.
|
||||
/// Targets entities that have all of the specified component types and those component(s) are disabled.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public QueryBuilder WithDisabled<<#= generics #>>()
|
||||
<#= enableRestrictions #>
|
||||
{
|
||||
<# for (var j = 0; j < i; j++) { #>
|
||||
_disabled.Add(ComponentTypeID<T<#= j #>>.value);
|
||||
<# } #>
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified component type(s) to the 'Present' filter of the query.
|
||||
/// Targets entities that have all of the specified component types, regardless of whether those component(s) are enabled or disabled.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public QueryBuilder WithPresent<<#= generics #>>()
|
||||
<#= restrictions #>
|
||||
{
|
||||
<# for (var j = 0; j < i; j++) { #>
|
||||
_present.Add(ComponentTypeID<T<#= j #>>.value);
|
||||
<# } #>
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified component type(s) to the 'Present' filter of the query and requires read-write access.
|
||||
/// Targets entities that have all of the specified component types, regardless of whether those component(s) are enabled or disabled.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public QueryBuilder WithPresentRW<<#= generics #>>()
|
||||
<#= restrictions #>
|
||||
{
|
||||
<# for (var j = 0; j < i; j++) { #>
|
||||
_present.Add(ComponentTypeID<T<#= j #>>.value);
|
||||
_rw.Add(ComponentTypeID<T<#= j #>>.value);
|
||||
<# } #>
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
<# } #>
|
||||
}
|
||||
@@ -1,73 +1,160 @@
|
||||
using Ghost.Entities.Components;
|
||||
using Ghost.Entities.Query;
|
||||
using Ghost.Entities.Systems;
|
||||
using System.Diagnostics;
|
||||
using Ghost.Core;
|
||||
using Misaki.HighPerformance.Jobs;
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ghost.Entities;
|
||||
|
||||
// TODO: Archetype system for better performance
|
||||
public partial class World
|
||||
{
|
||||
private static List<World> s_worlds = new(4);
|
||||
private static Queue<WorldID> s_freeWorldSlots = new();
|
||||
private static readonly List<World?> s_worlds = new(4);
|
||||
private static readonly Queue<Identifier<World>> s_freeWorldSlots = new();
|
||||
|
||||
internal static Identifier<Archetype> EmptyArchetypeID => new (0);
|
||||
|
||||
public static int WorldCount => s_worlds.Count - s_freeWorldSlots.Count;
|
||||
|
||||
public static World Create(int entityCapacity = 16)
|
||||
public static World Create(JobScheduler jobScheduler, int entityCapacity = 16)
|
||||
{
|
||||
lock (s_worlds)
|
||||
{
|
||||
if (s_freeWorldSlots.TryDequeue(out var index))
|
||||
{
|
||||
s_worlds[index] = new World(index, entityCapacity);
|
||||
s_worlds[index.value] = new World(index, entityCapacity, jobScheduler);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (s_worlds.Count >= WorldID.MaxValue)
|
||||
{
|
||||
throw new InvalidOperationException("Maximum number of worlds reached");
|
||||
}
|
||||
|
||||
index = (WorldID)s_worlds.Count;
|
||||
s_worlds.Add(new World(index, entityCapacity));
|
||||
index = new Identifier<World>(s_worlds.Count);
|
||||
s_worlds.Add(new World(index, entityCapacity, jobScheduler));
|
||||
}
|
||||
|
||||
return s_worlds[index];
|
||||
return s_worlds[index.value]!;
|
||||
}
|
||||
}
|
||||
|
||||
public static void Destroy(Identifier<World> id)
|
||||
{
|
||||
lock (s_worlds)
|
||||
{
|
||||
if (id.value < 0 || id.value >= s_worlds.Count)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var world = s_worlds[id.value];
|
||||
world?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static World GetWorld(int index)
|
||||
internal static World GetWorldUncheck(Identifier<World> id)
|
||||
{
|
||||
return s_worlds[index];
|
||||
#if DEBUG || GHOST_EDITOR
|
||||
if (id.value < 0 || id.value >= s_worlds.Count)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(id), "World ID is out of range.");
|
||||
}
|
||||
|
||||
var world = s_worlds[id.value];
|
||||
return world is null ? throw new InvalidOperationException("World not found.") : world;
|
||||
#else
|
||||
return s_worlds[id.value]!;
|
||||
#endif
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Result<World, ErrorStatus> GetWorld(Identifier<World> id)
|
||||
{
|
||||
if (id.value < 0 || id.value >= s_worlds.Count)
|
||||
{
|
||||
return ErrorStatus.InvalidArgument;
|
||||
}
|
||||
|
||||
var world = s_worlds[id.value];
|
||||
return world is null ? ErrorStatus.NotFound : world;
|
||||
}
|
||||
}
|
||||
|
||||
public partial class World : IDisposable, IEquatable<World>
|
||||
{
|
||||
private readonly WorldID _id;
|
||||
private readonly Identifier<World> _id;
|
||||
private readonly JobScheduler _jobScheduler;
|
||||
|
||||
private readonly EntityManager _entityManager;
|
||||
private readonly ComponentStorage _componentStorage;
|
||||
private readonly SystemStorage _systemStorage;
|
||||
private readonly EntityCommandBuffer _entityCommandBuffer;
|
||||
private readonly EntityCommandBuffer[] _threadLocalECBs;
|
||||
|
||||
private bool _isDisposed = false;
|
||||
private readonly SystemManager _systemManager;
|
||||
|
||||
internal ComponentStorage ComponentStorage => _componentStorage;
|
||||
private UnsafeList<Archetype> _archetypes;
|
||||
private UnsafeList<EntityQuery> _entityQueries;
|
||||
|
||||
public WorldID ID => _id;
|
||||
private UnsafeHashMap<int, Identifier<Archetype>> _archetypeLookup; // Signature Hash to Archetype ID
|
||||
private UnsafeHashMap<int, Identifier<EntityQuery>> _querieLookup; // Query Mask Hash to Query ID
|
||||
|
||||
private int _version;
|
||||
private bool _disposed = false;
|
||||
|
||||
internal int ArchetypeCount => _archetypes.Count;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the unique identifier of this world.
|
||||
/// </summary>
|
||||
public Identifier<World> ID => _id;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the job scheduler associated with this world.
|
||||
/// </summary>
|
||||
public JobScheduler JobScheduler => _jobScheduler;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the publicntity manager for this world.
|
||||
/// </summary>
|
||||
public EntityManager EntityManager => _entityManager;
|
||||
public SystemStorage SystemStorage => _systemStorage;
|
||||
|
||||
public event Action<World, Entity, Type>? ComponentChanged;
|
||||
/// <summary>
|
||||
/// Gets the system manager for this world.
|
||||
/// </summary>
|
||||
public SystemManager SystemManager => _systemManager;
|
||||
|
||||
private World(WorldID id, int entityCapacity)
|
||||
/// <summary>
|
||||
/// Gets the current version number of the world.
|
||||
/// </summary>
|
||||
public int Version => Interlocked.CompareExchange(ref _version, 0, 0);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the main entity command buffer for this world.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Use <see cref="GetThreadLocalEntityCommandBuffer(int)"/> to get thread-local command buffers for multi-threaded jobs.
|
||||
/// </remarks>
|
||||
public EntityCommandBuffer EntityCommandBuffer => _entityCommandBuffer;
|
||||
|
||||
private World(Identifier<World> id, int entityCapacity, JobScheduler jobScheduler)
|
||||
{
|
||||
_id = id;
|
||||
_jobScheduler = jobScheduler;
|
||||
|
||||
_entityManager = new EntityManager(this, entityCapacity);
|
||||
_componentStorage = new ComponentStorage(this);
|
||||
_systemStorage = new SystemStorage(this);
|
||||
_entityCommandBuffer = new EntityCommandBuffer(_entityManager);
|
||||
_threadLocalECBs = new EntityCommandBuffer[jobScheduler.WorkerCount];
|
||||
|
||||
_systemManager = new SystemManager(this);
|
||||
|
||||
_archetypes = new UnsafeList<Archetype>(16, Allocator.Persistent);
|
||||
_entityQueries = new UnsafeList<EntityQuery>(16, Allocator.Persistent);
|
||||
|
||||
_archetypeLookup = new UnsafeHashMap<int, Identifier<Archetype>>(16, Allocator.Persistent);
|
||||
_querieLookup = new UnsafeHashMap<int, Identifier<EntityQuery>>(16, Allocator.Persistent);
|
||||
|
||||
for (var i = 0; i < jobScheduler.WorkerCount; i++)
|
||||
{
|
||||
_threadLocalECBs[i] = new EntityCommandBuffer(_entityManager);
|
||||
}
|
||||
|
||||
// Create the empty archetype
|
||||
CreateArchetype(ReadOnlySpan<Identifier<IComponent>>.Empty, 0);
|
||||
}
|
||||
|
||||
~World()
|
||||
@@ -76,52 +163,103 @@ public partial class World : IDisposable, IEquatable<World>
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public CompRef<T> GetSingleton<T>()
|
||||
where T : unmanaged, IComponentData
|
||||
internal Identifier<Archetype> CreateArchetype(ReadOnlySpan<Identifier<IComponent>> componentTypeIDs, int signatureHash)
|
||||
{
|
||||
ref var component = ref CollectionsMarshal.GetValueRefOrAddDefault(SingletonContainer<T>.container, _id, out _);
|
||||
return new CompRef<T>(ref component);
|
||||
}
|
||||
var arcID = new Identifier<Archetype>(_archetypes.Count);
|
||||
_archetypes.Add(new Archetype(arcID, _id, componentTypeIDs));
|
||||
_archetypeLookup.Add(signatureHash, arcID);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public IEnumerable<ScriptComponent> QueryScript()
|
||||
{
|
||||
if (_componentStorage.ScriptComponentPool.IsInitialized)
|
||||
for (int i = 0; i < _entityQueries.Count; i++)
|
||||
{
|
||||
return _componentStorage.ScriptComponentPool.ExecutionList!;
|
||||
ref var query = ref _entityQueries[i];
|
||||
query.AddArchetypeIfMatch(in _archetypes[arcID.value]);
|
||||
}
|
||||
|
||||
return Enumerable.Empty<ScriptComponent>();
|
||||
return arcID;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
[Conditional("GHOST_EDITOR")]
|
||||
public void NotifyComponentChanged(Entity entity, Type type)
|
||||
internal Identifier<Archetype> GetArchetypeIDBySignatureHash(int signatureHash)
|
||||
{
|
||||
ComponentChanged?.Invoke(this, entity, type);
|
||||
if (_archetypeLookup.TryGetValue(signatureHash, out var arcID))
|
||||
{
|
||||
return arcID;
|
||||
}
|
||||
|
||||
return Identifier<Archetype>.Invalid;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
[Conditional("GHOST_EDITOR")]
|
||||
public void NotifyComponentChanged<T>(Entity entity)
|
||||
where T : unmanaged, IComponentData
|
||||
internal ref Archetype GetArchetypeReference(Identifier<Archetype> id)
|
||||
{
|
||||
NotifyComponentChanged(entity, typeof(T));
|
||||
return ref _archetypes[id.value];
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal Identifier<EntityQuery> CreateEntityQuery(EntityQueryMask mask, int maskHash)
|
||||
{
|
||||
var queryID = new Identifier<EntityQuery>(_entityQueries.Count);
|
||||
_entityQueries.Add(new EntityQuery(queryID, _id, mask));
|
||||
_querieLookup.Add(maskHash, queryID);
|
||||
|
||||
ref var query = ref _entityQueries[queryID.value];
|
||||
for (var i = 0; i < _archetypes.Count; i++)
|
||||
{
|
||||
query.AddArchetypeIfMatch(in _archetypes[i]);
|
||||
}
|
||||
|
||||
return queryID;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal Identifier<EntityQuery> GetEntityQueryIDByMaskHash(int maskHash)
|
||||
{
|
||||
if (_querieLookup.TryGetValue(maskHash, out var queryID))
|
||||
{
|
||||
return queryID;
|
||||
}
|
||||
|
||||
return Identifier<EntityQuery>.Invalid;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal void PlaybackEntityCommandBuffers()
|
||||
{
|
||||
_entityCommandBuffer.Playback();
|
||||
|
||||
for (var i = 0; i < _threadLocalECBs.Length; i++)
|
||||
{
|
||||
_threadLocalECBs[i].Playback();
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal int AdvanceVersion()
|
||||
{
|
||||
return Interlocked.Increment(ref _version);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a reference to the entity query with the specified identifier.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ref EntityQuery GetEntityQueryReference(Identifier<EntityQuery> id)
|
||||
{
|
||||
return ref _entityQueries[id.value];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the thread-local entity command buffer for the specified thread index.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public EntityCommandBuffer GetThreadLocalEntityCommandBuffer(int threadIndex)
|
||||
{
|
||||
return _threadLocalECBs[threadIndex];
|
||||
}
|
||||
|
||||
public bool Equals(World? other)
|
||||
{
|
||||
if (other is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ReferenceEquals(this, other))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return _id == other._id;
|
||||
return other is not null && _id == other._id;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
@@ -146,19 +284,39 @@ public partial class World : IDisposable, IEquatable<World>
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_isDisposed)
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (ref var archetype in _archetypes)
|
||||
{
|
||||
archetype.Dispose();
|
||||
}
|
||||
|
||||
foreach (ref var query in _entityQueries)
|
||||
{
|
||||
query.Dispose();
|
||||
}
|
||||
|
||||
_entityManager.Dispose();
|
||||
_componentStorage.Dispose();
|
||||
_systemStorage.Dispose();
|
||||
_entityCommandBuffer.Dispose();
|
||||
foreach (var v in _threadLocalECBs)
|
||||
{
|
||||
v.Dispose();
|
||||
}
|
||||
|
||||
_archetypes.Dispose();
|
||||
_entityQueries.Dispose();
|
||||
_archetypeLookup.Dispose();
|
||||
_querieLookup.Dispose();
|
||||
|
||||
s_freeWorldSlots.Enqueue(_id);
|
||||
s_worlds[_id] = null;
|
||||
|
||||
_isDisposed = true;
|
||||
_disposed = true;
|
||||
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Graphics.RHI;
|
||||
using Misaki.HighPerformance.LowLevel;
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
using System.Runtime.CompilerServices;
|
||||
@@ -83,15 +84,15 @@ public struct Material : IResourceReleasable, IHandleType
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly unsafe Result<T, ResultStatus> GetPropertyCache<T>()
|
||||
public readonly unsafe Result<T, ErrorStatus> GetPropertyCache<T>()
|
||||
where T : unmanaged
|
||||
{
|
||||
if (sizeof(T) != _cBufferCache.Size)
|
||||
{
|
||||
return Result.Create(default(T), ResultStatus.InvalidArgument);
|
||||
return ErrorStatus.InvalidArgument;
|
||||
}
|
||||
|
||||
return Result.Create(*(T*)_cBufferCache.CpuData.GetUnsafePtr(), ResultStatus.Success);
|
||||
return *(T*)_cBufferCache.CpuData.GetUnsafePtr();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
@@ -106,28 +107,28 @@ public struct Material : IResourceReleasable, IHandleType
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly unsafe ResultStatus SetPropertyCache<T>(ref readonly T data)
|
||||
public readonly unsafe ErrorStatus SetPropertyCache<T>(ref readonly T data)
|
||||
where T : unmanaged
|
||||
{
|
||||
if (sizeof(T) != _cBufferCache.Size)
|
||||
{
|
||||
return ResultStatus.InvalidArgument;
|
||||
return ErrorStatus.InvalidArgument;
|
||||
}
|
||||
|
||||
Unsafe.WriteUnaligned(_cBufferCache.CpuData.GetUnsafePtr(), data);
|
||||
return ResultStatus.Success;
|
||||
return ErrorStatus.None;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly unsafe ResultStatus SetRawPropertyCache(ReadOnlySpan<byte> data)
|
||||
public readonly unsafe ErrorStatus SetRawPropertyCache(ReadOnlySpan<byte> data)
|
||||
{
|
||||
if (data.Length != _cBufferCache.Size)
|
||||
{
|
||||
return ResultStatus.InvalidArgument;
|
||||
return ErrorStatus.InvalidArgument;
|
||||
}
|
||||
|
||||
Unsafe.WriteUnaligned(_cBufferCache.CpuData.GetUnsafePtr(), data);
|
||||
return ResultStatus.Success;
|
||||
return ErrorStatus.None;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
|
||||
@@ -49,7 +49,7 @@ public readonly unsafe ref struct RenderingContext
|
||||
CommandBufferType.Graphics => _engine.Device.GraphicsQueue,
|
||||
CommandBufferType.Compute => _engine.Device.ComputeQueue,
|
||||
CommandBufferType.Copy => _engine.Device.CopyQueue,
|
||||
_ => throw new ArgumentOutOfRangeException(),
|
||||
_ => throw new InvalidOperationException("Unknown command buffer type."),
|
||||
};
|
||||
|
||||
queue.Submit(commandBuffer);
|
||||
@@ -123,8 +123,8 @@ public readonly unsafe ref struct RenderingContext
|
||||
localToWorld = localToWorld,
|
||||
worldBoundsMin = meshData.BoundingBox.Min,
|
||||
worldBoundsMax = meshData.BoundingBox.Max,
|
||||
vertexBuffer = _engine.ResourceDatabase.GetBindlessIndex(meshData.VertexBuffer.AsResource()).GetValueOrThrow(ResultStatus.Success),
|
||||
indexBuffer = _engine.ResourceDatabase.GetBindlessIndex(meshData.IndexBuffer.AsResource()).GetValueOrThrow(ResultStatus.Success),
|
||||
vertexBuffer = _engine.ResourceDatabase.GetBindlessIndex(meshData.VertexBuffer.AsResource()).GetValueOrThrow(),
|
||||
indexBuffer = _engine.ResourceDatabase.GetBindlessIndex(meshData.IndexBuffer.AsResource()).GetValueOrThrow(),
|
||||
};
|
||||
|
||||
var bufferHandle = meshData.ObjectDataBuffer.AsResource();
|
||||
@@ -147,7 +147,7 @@ public readonly unsafe ref struct RenderingContext
|
||||
where T : unmanaged
|
||||
{
|
||||
var desc = ResourceDatabase.GetResourceDescription(texture.AsResource())
|
||||
.GetValueOrThrow(ResultStatus.Success);
|
||||
.GetValueOrThrow();
|
||||
|
||||
if (data.Length * sizeof(T) != desc.TextureDescription.GetTotalBytes())
|
||||
{
|
||||
@@ -180,7 +180,7 @@ public readonly unsafe ref struct RenderingContext
|
||||
var shader = ResourceDatabase.GetShaderReference(materialRef.Shader);
|
||||
|
||||
var keyResult = shader.TryGetPassKey(passName, out var passIndex);
|
||||
if (keyResult.Status != ResultStatus.Success)
|
||||
if (keyResult.Error != ErrorStatus.None)
|
||||
{
|
||||
throw new Exception(keyResult.ToString());
|
||||
}
|
||||
|
||||
@@ -101,17 +101,17 @@ public class Shader : IResourceReleasable, IIdentifierType
|
||||
return ref _passes[index];
|
||||
}
|
||||
|
||||
public RefResult<ShaderPass, ResultStatus> TryGetPassKey(string passName, out int passIndex)
|
||||
public RefResult<ShaderPass, ErrorStatus> TryGetPassKey(string passName, out int passIndex)
|
||||
{
|
||||
var index = _passLookup.GetValueOrDefault(passName, -1);
|
||||
if (index == -1)
|
||||
{
|
||||
passIndex = -1;
|
||||
return Result.CreateRef(ref Unsafe.NullRef<ShaderPass>(), ResultStatus.NotFound);
|
||||
return ErrorStatus.NotFound;
|
||||
}
|
||||
|
||||
passIndex = index;
|
||||
return Result.CreateRef(ref _passes[index], ResultStatus.Success);
|
||||
return RefResult<ShaderPass, ErrorStatus>.Success(ref _passes[index]);
|
||||
}
|
||||
|
||||
void IResourceReleasable.ReleaseResource(IResourceDatabase database)
|
||||
|
||||
@@ -130,7 +130,7 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
|
||||
#if DEBUG
|
||||
[DoesNotReturn]
|
||||
#endif
|
||||
private void RecordError(string cmdName, ResultStatus status)
|
||||
private void RecordError(string cmdName, ErrorStatus status)
|
||||
{
|
||||
#if DEBUG
|
||||
throw new InvalidOperationException($"Error at {cmdName} with {status}");
|
||||
@@ -183,7 +183,7 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
|
||||
_commandList.Get()->Close();
|
||||
_isRecording = false;
|
||||
|
||||
if (_lastError.Status != ResultStatus.Success)
|
||||
if (_lastError.Status != ErrorStatus.None)
|
||||
{
|
||||
return Result.Failure($"Command buffer ended with errors at {_lastError.CommandIndex}, command '{_lastError.CommandName}': {_lastError.Status}");
|
||||
}
|
||||
@@ -220,21 +220,21 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
|
||||
|
||||
if (!desc.Resource.IsValid)
|
||||
{
|
||||
RecordError(nameof(ResourceBarrier), ResultStatus.InvalidArgument);
|
||||
RecordError(nameof(ResourceBarrier), ErrorStatus.InvalidArgument);
|
||||
continue;
|
||||
}
|
||||
|
||||
var recordResult = _resourceDatabase.GetResourceRecord(desc.Resource);
|
||||
if (recordResult.Status != ResultStatus.Success)
|
||||
if (recordResult.Error != ErrorStatus.None)
|
||||
{
|
||||
RecordError(nameof(ResourceBarrier), recordResult.Status);
|
||||
RecordError(nameof(ResourceBarrier), recordResult.Error);
|
||||
continue;
|
||||
}
|
||||
|
||||
ref var record = ref recordResult.Value;
|
||||
if (record.state != desc.StateBefore)
|
||||
{
|
||||
RecordError(nameof(ResourceBarrier), ResultStatus.InvalidState);
|
||||
RecordError(nameof(ResourceBarrier), ErrorStatus.InvalidState);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -263,9 +263,9 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
|
||||
}
|
||||
|
||||
var recordResult = _resourceDatabase.GetResourceRecord(resource);
|
||||
if (recordResult.Status != ResultStatus.Success)
|
||||
if (recordResult.Error != ErrorStatus.None)
|
||||
{
|
||||
RecordError(nameof(ResourceBarrier), recordResult.Status);
|
||||
RecordError(nameof(ResourceBarrier), recordResult.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -284,9 +284,9 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
|
||||
IncrementCommandCount();
|
||||
|
||||
var recordResult = _resourceDatabase.GetResourceRecord(resource);
|
||||
if (recordResult.Status != ResultStatus.Success)
|
||||
if (recordResult.Error != ErrorStatus.None)
|
||||
{
|
||||
RecordError(nameof(ResourceBarrier), recordResult.Status);
|
||||
RecordError(nameof(ResourceBarrier), recordResult.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -316,14 +316,14 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
|
||||
var handle = renderTargets[i];
|
||||
if (!handle.IsValid)
|
||||
{
|
||||
RecordError(nameof(SetRenderTargets), ResultStatus.InvalidArgument);
|
||||
RecordError(nameof(SetRenderTargets), ErrorStatus.InvalidArgument);
|
||||
continue;
|
||||
}
|
||||
|
||||
var recordResult = _resourceDatabase.GetResourceRecord(handle.AsResource());
|
||||
if (recordResult.Status != ResultStatus.Success)
|
||||
if (recordResult.Error != ErrorStatus.None)
|
||||
{
|
||||
RecordError(nameof(SetRenderTargets), recordResult.Status);
|
||||
RecordError(nameof(SetRenderTargets), recordResult.Error);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -337,9 +337,9 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
|
||||
if (pDsvHandle != null)
|
||||
{
|
||||
var recordResult = _resourceDatabase.GetResourceRecord(depthTarget.AsResource());
|
||||
if (recordResult.Status != ResultStatus.Success)
|
||||
if (recordResult.Error != ErrorStatus.None)
|
||||
{
|
||||
RecordError(nameof(SetRenderTargets), recordResult.Status);
|
||||
RecordError(nameof(SetRenderTargets), recordResult.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -362,14 +362,14 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
|
||||
var rtDesc = rtDescs[i];
|
||||
if (!rtDesc.Texture.IsValid)
|
||||
{
|
||||
RecordError(nameof(BeginRenderPass), ResultStatus.InvalidArgument);
|
||||
RecordError(nameof(BeginRenderPass), ErrorStatus.InvalidArgument);
|
||||
continue;
|
||||
}
|
||||
|
||||
var recordResult = _resourceDatabase.GetResourceRecord(rtDesc.Texture.AsResource());
|
||||
if (recordResult.Status != ResultStatus.Success)
|
||||
if (recordResult.Error != ErrorStatus.None)
|
||||
{
|
||||
RecordError(nameof(BeginRenderPass), recordResult.Status);
|
||||
RecordError(nameof(BeginRenderPass), recordResult.Error);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -402,9 +402,9 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
|
||||
if (pDsvDesc != null)
|
||||
{
|
||||
var recordResult = _resourceDatabase.GetResourceRecord(depthDesc.Texture.AsResource());
|
||||
if (recordResult.Status != ResultStatus.Success)
|
||||
if (recordResult.Error != ErrorStatus.None)
|
||||
{
|
||||
RecordError(nameof(BeginRenderPass), recordResult.Status);
|
||||
RecordError(nameof(BeginRenderPass), recordResult.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -458,10 +458,10 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
|
||||
IncrementCommandCount();
|
||||
|
||||
var psor = _pipelineLibrary.GetGraphicsPSO(pipelineKey);
|
||||
if (psor.Status != ResultStatus.Success)
|
||||
if (psor.Error != ErrorStatus.None)
|
||||
{
|
||||
#if DEBUG || GHOST_EDITOR
|
||||
Logger.LogError($"Failed to get graphics pipeline state object for key {pipelineKey}: {psor.Status}");
|
||||
Logger.LogError($"Failed to get graphics pipeline state object for key {pipelineKey}: {psor.Error}");
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
@@ -487,9 +487,9 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
|
||||
IncrementCommandCount();
|
||||
|
||||
var recordResult = _resourceDatabase.GetResourceRecord(buffer.AsResource());
|
||||
if (recordResult.Status != ResultStatus.Success)
|
||||
if (recordResult.Error != ErrorStatus.None)
|
||||
{
|
||||
RecordError(nameof(BeginRenderPass), recordResult.Status);
|
||||
RecordError(nameof(BeginRenderPass), recordResult.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -601,7 +601,7 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
|
||||
uploadResource.Get()->Map(0, null, &pMappedData);
|
||||
fixed (T* pData = data)
|
||||
{
|
||||
MemoryUtility.MemCpy(pData, pMappedData, sizeInBytes);
|
||||
MemoryUtility.MemCpy(pMappedData, pData, sizeInBytes);
|
||||
}
|
||||
uploadResource.Get()->Unmap(0, null);
|
||||
|
||||
@@ -657,7 +657,7 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
|
||||
var pSrcResource = _resourceDatabase.GetResource(src.AsResource());
|
||||
if (pSrcResource == null || pDestResource == null)
|
||||
{
|
||||
RecordError(nameof(CopyBuffer), ResultStatus.InvalidArgument);
|
||||
RecordError(nameof(CopyBuffer), ErrorStatus.InvalidArgument);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -161,7 +161,7 @@ internal unsafe class D3D12PipelineLibrary : IPipelineLibrary
|
||||
|
||||
if (File.Exists(filePath))
|
||||
{
|
||||
var fileBytes = File.ReadAllBytes(filePath!);
|
||||
var fileBytes = File.ReadAllBytes(filePath);
|
||||
fixed (byte* pFileBytes = fileBytes)
|
||||
{
|
||||
ThrowIfFailed(_device.NativeDevice.Get()->CreatePipelineLibrary(pFileBytes, (nuint)fileBytes.Length, __uuidof(pLibrary), (void**)&pLibrary));
|
||||
@@ -224,7 +224,7 @@ internal unsafe class D3D12PipelineLibrary : IPipelineLibrary
|
||||
RegisterSlot = info.BindPoint,
|
||||
RegisterSpace = info.Space,
|
||||
SizeInBytes = info.Size,
|
||||
Properties = info.Properties ?? Array.Empty<CBufferPropertyInfo>(),
|
||||
Properties = info.Properties ?? [],
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -401,14 +401,14 @@ internal unsafe class D3D12PipelineLibrary : IPipelineLibrary
|
||||
return key;
|
||||
}
|
||||
|
||||
public Result<SharedPtr<ID3D12PipelineState>, ResultStatus> GetGraphicsPSO(GraphicsPipelineKey key)
|
||||
public Result<SharedPtr<ID3D12PipelineState>, ErrorStatus> GetGraphicsPSO(GraphicsPipelineKey key)
|
||||
{
|
||||
if (_pipelineCache.TryGetValue(key, out var cacheEntry))
|
||||
{
|
||||
return Result.Create(new SharedPtr<ID3D12PipelineState>(cacheEntry.pso.Get()), ResultStatus.Success);
|
||||
return cacheEntry.pso.Share();
|
||||
}
|
||||
|
||||
return Result.Create(default(SharedPtr<ID3D12PipelineState>), ResultStatus.NotFound);
|
||||
return ErrorStatus.NotFound;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
||||
@@ -178,17 +178,17 @@ internal class D3D12ResourceDatabase : IResourceDatabase
|
||||
return _resources.Contains(handle.id, handle.generation);
|
||||
}
|
||||
|
||||
public RefResult<ResourceRecord, ResultStatus> GetResourceRecord(Handle<GPUResource> handle)
|
||||
public RefResult<ResourceRecord, ErrorStatus> GetResourceRecord(Handle<GPUResource> handle)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
ref var info = ref _resources.GetElementReferenceAt(handle.id, handle.generation, out var exist);
|
||||
if (!exist)
|
||||
{
|
||||
return Result.CreateRef(ref Unsafe.NullRef<ResourceRecord>(), ResultStatus.NotFound);
|
||||
return ErrorStatus.NotFound;
|
||||
}
|
||||
|
||||
return Result.CreateRef(ref info, ResultStatus.Success);
|
||||
return RefResult<ResourceRecord, ErrorStatus>.Success(ref info);
|
||||
}
|
||||
|
||||
public ref ResourceRecord GetResourceRecord(Handle<GPUResource> handle, out bool exist)
|
||||
@@ -202,7 +202,7 @@ internal class D3D12ResourceDatabase : IResourceDatabase
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
var r = GetResourceRecord(handle);
|
||||
if (r.Status != ResultStatus.Success)
|
||||
if (r.Error != ErrorStatus.None)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
@@ -210,17 +210,17 @@ internal class D3D12ResourceDatabase : IResourceDatabase
|
||||
return r.Value.ResourcePtr;
|
||||
}
|
||||
|
||||
public Result<ResourceState, ResultStatus> GetResourceState(Handle<GPUResource> handle)
|
||||
public Result<ResourceState, ErrorStatus> GetResourceState(Handle<GPUResource> handle)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
var r = GetResourceRecord(handle);
|
||||
if (r.Status != ResultStatus.Success)
|
||||
if (!r)
|
||||
{
|
||||
return Result.Create(ResourceState.Common, r.Status);
|
||||
return r.Error;
|
||||
}
|
||||
|
||||
return Result.Create(r.Value.state, ResultStatus.Success);
|
||||
return r.Value.state;
|
||||
}
|
||||
|
||||
public void SetResourceState(Handle<GPUResource> handle, ResourceState state)
|
||||
@@ -236,31 +236,30 @@ internal class D3D12ResourceDatabase : IResourceDatabase
|
||||
info.state = state;
|
||||
}
|
||||
|
||||
public Result<ResourceDesc, ResultStatus> GetResourceDescription(Handle<GPUResource> handle)
|
||||
public Result<ResourceDesc, ErrorStatus> GetResourceDescription(Handle<GPUResource> handle)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
var r = GetResourceRecord(handle);
|
||||
|
||||
if (r.Status != ResultStatus.Success)
|
||||
if (!r)
|
||||
{
|
||||
return Result.Create(default(ResourceDesc), r.Status);
|
||||
return r.Error;
|
||||
}
|
||||
|
||||
return Result.Create(r.Value.desc, ResultStatus.Success);
|
||||
return r.Value.desc;
|
||||
}
|
||||
|
||||
public Result<uint, ResultStatus> GetBindlessIndex(Handle<GPUResource> handle)
|
||||
public Result<uint, ErrorStatus> GetBindlessIndex(Handle<GPUResource> handle)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
ref var info = ref GetResourceRecord(handle, out var exist);
|
||||
if (!exist || !info.Allocated)
|
||||
{
|
||||
return Result.Create(0u, ResultStatus.NotFound);
|
||||
return ErrorStatus.NotFound;
|
||||
}
|
||||
|
||||
return Result.Create((uint)info.viewGroup.srv.value, ResultStatus.Success);
|
||||
return (uint)info.viewGroup.srv.value;
|
||||
}
|
||||
|
||||
public string? GetResourceName(Handle<GPUResource> handle)
|
||||
|
||||
@@ -16,6 +16,12 @@
|
||||
<IsTrimmable>True</IsTrimmable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Remove="RenderGraphModule\**" />
|
||||
<EmbeddedResource Remove="RenderGraphModule\**" />
|
||||
<None Remove="RenderGraphModule\**" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="runtime\win-x64\native\dxcompiler.dll" />
|
||||
<None Remove="runtime\win-x64\native\dxil.dll" />
|
||||
@@ -48,14 +54,4 @@
|
||||
<Service Include="{508349b6-6b84-4df5-91f0-309beebad82d}" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="RenderGraphModule\" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="Misaki.HighPerformance.LowLevel">
|
||||
<HintPath>..\..\Class\Misaki.HighPerformance\Misaki.HighPerformance.LowLevel\bin\Release\net10.0\Misaki.HighPerformance.LowLevel.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -98,7 +98,7 @@ internal struct GraphicsPipelineHash
|
||||
// Do we need to store blend state?
|
||||
// TODO: Variants
|
||||
|
||||
public GraphicsPipelineKey GetKey()
|
||||
public readonly GraphicsPipelineKey GetKey()
|
||||
{
|
||||
Span<ulong> data = stackalloc ulong[3 + D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT];
|
||||
data[0] = Id.value;
|
||||
@@ -731,7 +731,7 @@ public struct CommandError
|
||||
get; set;
|
||||
}
|
||||
|
||||
public ResultStatus Status
|
||||
public ErrorStatus Status
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ public interface IResourceDatabase : IDisposable
|
||||
/// </summary>
|
||||
/// <param name="handle">The handle that uniquely identifies the resource whose state is to be retrieved.</param>
|
||||
/// <returns>A ResourceState Value representing the current state of the resource associated with the specified handle.</returns>
|
||||
Result<ResourceState, ResultStatus> GetResourceState(Handle<GPUResource> handle);
|
||||
Result<ResourceState, ErrorStatus> GetResourceState(Handle<GPUResource> handle);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the state of the specified resource handle to the given Value.
|
||||
@@ -53,14 +53,14 @@ public interface IResourceDatabase : IDisposable
|
||||
/// </summary>
|
||||
/// <param name="handle">A handle that identifies the GPU resource for which to obtain the description. Must reference a valid resource.</param>
|
||||
/// <returns>A ResourceDesc structure containing details about the specified GPU resource.</returns>
|
||||
Result<ResourceDesc, ResultStatus> GetResourceDescription(Handle<GPUResource> handle);
|
||||
Result<ResourceDesc, ErrorStatus> GetResourceDescription(Handle<GPUResource> handle);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the bindless index associated with the specified GPU resource handle.
|
||||
/// </summary>
|
||||
/// <param name="handle">A handle to the GPU resource for which to obtain the bindless index. Must reference a valid, currently registered resource.</param>
|
||||
/// <returns>The bindless index corresponding to the specified GPU resource handle. -1 if the resource does not support bindless access or is not found.</returns>
|
||||
Result<uint, ResultStatus> GetBindlessIndex(Handle<GPUResource> handle);
|
||||
Result<uint, ErrorStatus> GetBindlessIndex(Handle<GPUResource> handle);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the name of the GPU resource associated with the specified handle.
|
||||
|
||||
@@ -27,9 +27,9 @@ internal class MeshRenderPass : IRenderPass
|
||||
public uint texture4;
|
||||
public uint tex_sampler;
|
||||
|
||||
private uint _padding1;
|
||||
private uint _padding2;
|
||||
private uint _padding3;
|
||||
private readonly uint _padding1;
|
||||
private readonly uint _padding2;
|
||||
private readonly uint _padding3;
|
||||
}
|
||||
|
||||
private Handle<Mesh> _mesh;
|
||||
@@ -121,14 +121,14 @@ internal class MeshRenderPass : IRenderPass
|
||||
var matProps = new ShaderProperties_MyShader_Standard
|
||||
{
|
||||
color = new float4(1.0f, 1.0f, 1.0f, 1.0f),
|
||||
texture1 = ctx.ResourceDatabase.GetBindlessIndex(_textures[0].AsResource()).GetValueOrThrow(ResultStatus.Success),
|
||||
texture2 = ctx.ResourceDatabase.GetBindlessIndex(_textures[1].AsResource()).GetValueOrThrow(ResultStatus.Success),
|
||||
texture3 = ctx.ResourceDatabase.GetBindlessIndex(_textures[2].AsResource()).GetValueOrThrow(ResultStatus.Success),
|
||||
texture4 = ctx.ResourceDatabase.GetBindlessIndex(_textures[3].AsResource()).GetValueOrThrow(ResultStatus.Success),
|
||||
texture1 = ctx.ResourceDatabase.GetBindlessIndex(_textures[0].AsResource()).GetValueOrThrow(),
|
||||
texture2 = ctx.ResourceDatabase.GetBindlessIndex(_textures[1].AsResource()).GetValueOrThrow(),
|
||||
texture3 = ctx.ResourceDatabase.GetBindlessIndex(_textures[2].AsResource()).GetValueOrThrow(),
|
||||
texture4 = ctx.ResourceDatabase.GetBindlessIndex(_textures[3].AsResource()).GetValueOrThrow(),
|
||||
tex_sampler = (uint)sampler.value,
|
||||
};
|
||||
|
||||
Debug.Assert(matRef.SetPropertyCache(in matProps) == ResultStatus.Success);
|
||||
Debug.Assert(matRef.SetPropertyCache(in matProps) == ErrorStatus.None);
|
||||
matRef.UploadData(ctx.DirectCommandBuffer);
|
||||
}
|
||||
|
||||
|
||||
@@ -231,7 +231,7 @@ public unsafe static class MeshBuilder
|
||||
public static void ComputeTangents(UnsafeList<Vertex> vertices, UnsafeList<uint> indices)
|
||||
{
|
||||
using var scope = AllocationManager.CreateStackScope();
|
||||
using var bitangents = new UnsafeArray<float4>(vertices.Count, Allocator.Stack);
|
||||
var bitangents = new UnsafeArray<float4>(vertices.Count, scope.AllocationHandle);
|
||||
|
||||
for (var i = 0; i < indices.Count; i += 3)
|
||||
{
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
<Project Path="Ghost.Zeux.MeshOptimizer/Ghost.Zeux.MeshOptimizer.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Runtime/">
|
||||
<Project Path="Ghost.ArcEntities/Ghost.ArcEntities.csproj" />
|
||||
<Project Path="Ghost.Core/Ghost.Core.csproj" />
|
||||
<Project Path="Ghost.Engine/Ghost.Engine.csproj" />
|
||||
<Project Path="Ghost.Entities/Ghost.Entities.csproj" />
|
||||
|
||||
Reference in New Issue
Block a user