Major architectural update to graphics/material/shader system: - Introduced strongly-typed key structs (Key64/Key128) for passes, variants, and pipelines; removed legacy key types. - Implemented robust hashing and key generation utilities for efficient variant and pipeline lookup/caching. - Shader compiler now compiles/caches all keyword variants using new key system; includes handled as lists. - Switched to push constant root signature for per-draw data; updated HLSL and C# codegen accordingly. - Refactored Material, Shader, and Pass data structures for cache efficiency and variant support. - Pipeline library and PSO management now use 128-bit keys and variant-specific caching. - Replaced WorldNode with SceneNode in editor/scene graph; introduced ComponentManager for archetype/query management. - Migrated math utilities to Misaki.HighPerformance.Mathematics; updated editor controls. - Updated all HLSL and codegen for new buffer/push constant layouts and macros. - Misc: project reference cleanup, D3D12 Work Graph support, doc updates, and code modernization.
331 lines
9.6 KiB
C#
331 lines
9.6 KiB
C#
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 interface IComponent
|
|
{
|
|
}
|
|
|
|
public interface IEnableableComponent : IComponent
|
|
{
|
|
}
|
|
|
|
internal struct ComponentInfo
|
|
{
|
|
// public string stableName; // Do we actually need this?
|
|
public Identifier<IComponent> id;
|
|
public int size;
|
|
public int alignment;
|
|
public bool isEnableable;
|
|
public bool isShared;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Provides a unique identifier for the specified unmanaged component space.
|
|
/// </summary>
|
|
/// <typeparam name="T">The component space for which to obtain an identifier. Must be unmanaged and implement <see cref="IComponent"/>.</typeparam>
|
|
public static class ComponentTypeID<T>
|
|
where T : unmanaged, IComponent
|
|
{
|
|
public static readonly Identifier<IComponent> Value = ComponentRegistry.GetOrRegisterComponentID<T>();
|
|
}
|
|
|
|
internal static class ComponentRegistry
|
|
{
|
|
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();
|
|
|
|
internal static readonly Dictionary<int, Type> s_runtimeIDToType = new();
|
|
|
|
public static unsafe Identifier<IComponent> GetOrRegisterComponentID<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),
|
|
// isShared = typeof(ISharedComponent).IsAssignableFrom(type),
|
|
};
|
|
|
|
s_registeredComponents.Add(info);
|
|
|
|
s_typeHandleToID[typeHandle] = newID;
|
|
s_nameToRuntimeID[stableName] = newID;
|
|
s_runtimeIDToType[newID.Value] = typeof(T);
|
|
|
|
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];
|
|
}
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public static ComponentInfo GetComponentInfo(Type type)
|
|
{
|
|
lock (s_registeredComponents)
|
|
{
|
|
var typeId = GetComponentID(type);
|
|
if (typeId.IsInvalid)
|
|
{
|
|
throw new KeyNotFoundException($"Component type {type.FullName} is not registered.");
|
|
}
|
|
|
|
return s_registeredComponents[typeId];
|
|
}
|
|
}
|
|
|
|
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();
|
|
}
|
|
}
|
|
|
|
public class ComponentManager : IDisposable
|
|
{
|
|
private readonly World _world;
|
|
|
|
private UnsafeList<Archetype> _archetypes;
|
|
private UnsafeList<EntityQuery> _entityQueries;
|
|
|
|
private UnsafeHashMap<int, Identifier<Archetype>> _archetypeLookup; // Signature Hash to Archetype ID
|
|
private UnsafeHashMap<int, Identifier<EntityQuery>> _querieLookup; // Query Mask Hash to Query ID
|
|
|
|
private bool _isDisposed;
|
|
|
|
public int ArchetypeCount => _archetypes.Count;
|
|
|
|
internal ComponentManager(World world)
|
|
{
|
|
_world = world;
|
|
|
|
_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);
|
|
|
|
// Create the empty archetype
|
|
CreateArchetype(ReadOnlySpan<Identifier<IComponent>>.Empty, 0);
|
|
}
|
|
|
|
~ComponentManager()
|
|
{
|
|
Dispose();
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
internal Identifier<Archetype> CreateArchetype(ReadOnlySpan<Identifier<IComponent>> componentTypeIDs, int signatureHash)
|
|
{
|
|
var arcID = new Identifier<Archetype>(_archetypes.Count);
|
|
_archetypes.Add(new Archetype(arcID, _world.ID, componentTypeIDs));
|
|
_archetypeLookup.Add(signatureHash, arcID);
|
|
|
|
for (int i = 0; i < _entityQueries.Count; i++)
|
|
{
|
|
ref var query = ref _entityQueries[i];
|
|
query.AddArchetypeIfMatch(in _archetypes[arcID.Value]);
|
|
}
|
|
|
|
return arcID;
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
internal Identifier<Archetype> GetArchetypeIDBySignatureHash(int signatureHash)
|
|
{
|
|
if (_archetypeLookup.TryGetValue(signatureHash, out var arcID))
|
|
{
|
|
return arcID;
|
|
}
|
|
|
|
return Identifier<Archetype>.Invalid;
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
internal ref Archetype GetArchetypeReference(Identifier<Archetype> id)
|
|
{
|
|
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, _world.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;
|
|
}
|
|
|
|
/// <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];
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
if (_isDisposed)
|
|
{
|
|
return;
|
|
}
|
|
|
|
foreach (ref var archetype in _archetypes)
|
|
{
|
|
archetype.Dispose();
|
|
}
|
|
|
|
foreach (ref var query in _entityQueries)
|
|
{
|
|
query.Dispose();
|
|
}
|
|
|
|
_archetypes.Dispose();
|
|
_entityQueries.Dispose();
|
|
_archetypeLookup.Dispose();
|
|
_querieLookup.Dispose();
|
|
|
|
_isDisposed = true;
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Represents an immutable set of component identifiers used to define a group of components within an entity or system.
|
|
/// </summary>
|
|
public struct ComponentSet : IDisposable, IEquatable<ComponentSet>
|
|
{
|
|
private UnsafeArray<Identifier<IComponent>> _components;
|
|
private int _hashCode;
|
|
|
|
public readonly ReadOnlySpan<Identifier<IComponent>> Components => _components.AsSpan();
|
|
|
|
public ComponentSet(AllocationHandle allocationHandle, params ReadOnlySpan<Identifier<IComponent>> components)
|
|
{
|
|
_components = new UnsafeArray<Identifier<IComponent>>(components.Length, allocationHandle);
|
|
components.CopyTo(_components.AsSpan());
|
|
|
|
_hashCode = -1;
|
|
}
|
|
|
|
public ComponentSet(Allocator allocator, params ReadOnlySpan<Identifier<IComponent>> components)
|
|
: this(AllocationManager.GetAllocationHandle(allocator), components)
|
|
{
|
|
}
|
|
|
|
public readonly bool Equals(ComponentSet other)
|
|
{
|
|
return _hashCode == other._hashCode;
|
|
}
|
|
|
|
public override int GetHashCode()
|
|
{
|
|
if (_hashCode == -1)
|
|
{
|
|
_hashCode = ComponentRegistry.GetHashCode(_components.AsSpan());
|
|
}
|
|
|
|
return _hashCode;
|
|
}
|
|
|
|
public override readonly bool Equals(object? obj)
|
|
{
|
|
return obj is ComponentSet set && Equals(set);
|
|
}
|
|
|
|
public static bool operator ==(ComponentSet left, ComponentSet right)
|
|
{
|
|
return left.Equals(right);
|
|
}
|
|
|
|
public static bool operator !=(ComponentSet left, ComponentSet right)
|
|
{
|
|
return !(left == right);
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
_components.Dispose();
|
|
}
|
|
}
|